How to catch get parameter and cast it to int in Laravel? - laravel

I have the following controller:
class CustomerController extends Controller
{
public function index(int $customerId = 0) : Renderable
{
dd($customerId);
...
And I'm requesting it by the following url http://localhost/customers?customerId=123. I would like to catch customerId as int. But It always gives 0 (default value the for method's signature). Ok, I can do something like:
$customerId = $request->input('customerId');
But the approach with getting parameter from the method's signature is more attractive for me.
I have seen this answer. It looks like very nice! But it does not work for my case. Why? Where is the mistake? Thank you for attention!
Update
Here I will show my routes definitions:
Auth::routes(['register' => false, 'reset' => false]);
Route::middleware('auth')->group(function () {
Route::resource('customers', 'CustomerController');
});

If you want to use the Resource Route you have to pass the customerID parameter like below. And also you have to use one of these methods: show, edit,update or destroy in your controller.
Route::resource('customers', 'CustomerController')->parameters([
'customers' => 'customerId'
]);
For more details: https://laravel.com/docs/5.7/controllers#resource-controllers

AFAIK Laravel doesn't inject parameters this way.
If such URI is really needed - you should manually inspect request input for your variables.
If you want to use controller as written, you should use route parameters (e.g. /customers/{id}) instead of parameters after ?.
By default resource controllers use index() for displaying all entities and show() for displaying only one entity.
If you use Eloquent, you can also inject Customer model, e.g.
public function index(Customer $customer) : Renderable
Customer will be automatically found by id and injected in controller method.

Related

Why making tests passed parameter into method is empty?

Making http test in laravel 8 in control I create $itemObject object with factory and pass it for edit method
use Tests\TestCase;
class ControllersItemObjectController extends TestCase
{
use DatabaseMigrations;
/** #test */
public function edit_item_object()
{
$this->withoutMiddleware();
$itemObject = ItemObject::factory()->create();
\Log::info(json_encode($itemObject); // I see valid object logged
\Log::info(gettype($itemObject)); // it shows "object"
$this->asAdmin()->get(route('item-objects.edit', ['item_object' => $itemObject]));
and I got error :
Missing required parameter for [Route: item-objects.update] [URI: item-objects/{item_object}] [Missing parameter: item_object]. (View:
/resources/views/item-objects/edit.blade.php)
in routes I have :
| | GET|HEAD | item-objects/{item_object}/edit | item-objects.edit | App\Http\Controllers\ItemObjectController#edit | web |
But in ItemObjectController controller I have :
public function edit(ItemObject $itemObject)
{
\Log::info( json_encode($itemObject) ); // but it returns empty array
return view('item-objects.edit', [
...
]);
}
As parameter $itemObject above is empty it raise error in /resources/views/item-objects/edit.blade.php template above.
But I do not see why this parameter is empty ? Where I lose this value passing valid object into ItemObjectController.edit method?
If there is a way to debug it ? I can not use TELESCOPE for tests...
In route parameter is "item_object", but var name is "$itemObject" I do not see wht $itemObject is empty ...
Thanks in advance!
First of all, the error is being shown for item-objects.update, but the route you have shared is for item-objects.edit.
So, you have also shared the controller for edit, and you are using item_object in the route but using $itemObject in your controller... When you want to use Implicit Model Binding you have to match the parameter with the variable name, so you either use itemObject or item_object (I would recommend the first one):
public function edit(ItemObject $itemObject)
{
return view('item-objects.edit', [
...
]);
}
Route::get(`item-objects/{itemObject}/edit`, [\App\Http\Controllers\ItemObjectController::class, 'edit'])
->name('item-objects.edit);
After that is done, your test should also match the route:
public function edit_item_object()
{
$this->withoutMiddleware();
$itemObject = ItemObject::factory()->create();
$this->asAdmin()
->get(route('item-objects.edit', ['itemObject' => $itemObject]));
In this case you dont need to encode to json.
You need to pass the attribute which is used for model bindings in your routes file.
as an example, you can have something like this:
Route::bind('item_object', fn (string $value) => ItemObject::query()->findOrFail($value));
The code above, responsible for mapping route param with model from DB.
But in your case it might be different. I do prefer to follow explicit model bindings,instead of magic :)
https://laravel.com/docs/9.x/routing#route-model-binding

Laravel route difference between {id} vs {tag}

I am new in Laravel pardon me if question is silly. I have seen a doc where they used
For get request
Route::get("tags/{id}","TagsController#show");
For put request
Route::put("tags/{tag}","TagsController#update");
What is the difference and benefit between this ? I understood 1st one, confusion on put route.
There’s no real difference as it’s just a parameter name, but you’d need some way to differential parameters if you had more than one in a route, i.e. a nested resource controller:
Route::get('articles/{article}/comments/{comment}', 'ArticleCommentController#show');
Obviously you couldn’t use just {id} for both the article and comment parameters. For this reason, it’s best to use the “slug” version of a model for a parameter name, even if there’s just one in your route:
Route::get('articles/{article}', 'ArticleController#show');
You can also use route model binding. If you add a type-hint to your controller action for the parameter name, Laravel will attempt to look up an instance of the given class with the primary key in the URL.
Given the route in the second code example, if you had a controller that looked like this…
class ArticleController extends Controller
{
public function show(Article $article)
{
//
}
}
…and you requested /articles/123, then Laravel would attempt to look for an Article instance with the primary key of 123.
Route model binding is great as it removes a lot of find / findOrFail method calls in your controller. In most instances, you can reduce your controller actions to be one-liners:
class ArticleController extends Controller
{
public function show(Article $article)
{
return view('article.show', compact('article'));
}
}
Generally there's no practical difference unless you define a custom binding for a route parameter. Typically these bindings are defined in RouteServiceProvider as shown in the example in the docs
public function boot()
{
parent::boot();
Route::model('tag', App\Tag::class);
}
When you bind tag this way then your controller action can use the variable via model resultion:
public function update(Tag $tag) {
// $tag is resolved based on the identifier passed in the url
}
Usually models are automatically bound so doing it manually doesn't really need to be done however you can customise resolution logic if you do it manually
Normal way
Route::get("tags/{id}","TagsController#show");
function($id)
{
$tag = Tag::find($id);
dd($tag); // tag
}
With route model bindings
Route::put("tags/{tag}","TagsController#update");
function(Tag $tag) // Tag model binding
{
dd($tag); // tags
}
ref link https://laravel.com/docs/5.8/routing#implicit-binding
It's just a convention. You can call it all you want. Usually, and {id} refers to the id in your table. A tag, or similarly, a slug, is a string value. A tag could be 'entertainment' for video categories, while 'my-trip-to-spain' is a slug for the description of a video.
You have to chose the words what you are comfortable with. The value will be used to find in your database what record is needed to show the correct request in the view. Likewise you can use video/view/{id}/{slug} or any combination thereof.
Just make sure your URLs don't get too long. Because search engines won't show your website nicely in search results if you do. Find the balance between the unambiguous (for your database) and logic (for your visitors).
Check this out: Route model bindings
Use id, Laravel will get the id from route, and it will be the tag's id, it is integer.
function show($id) {
$tag = Tag::find($id);
}
Use tag, Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
In URL, your tag parameter is integer, however in your controller action $tag will be a model object:
function action(Tag $tag) {
$tag->name;
}
So you don't need to get the $tag by eloquent in your controller action. You just need to specify it is From model Tag $tag
It will do it automatically.

The update nor the destroy methods won't work in laravel eloquent model?

I have a strange situation where eloquent model won't let me update nor destroy while index and create is working fine!
I'm using Vue.js and Laravel API Resource for form control, and while it worked with me before, it won't work here:
Here's my Vue.js Code:
updateFinish(finish) {
axios.patch(`/api/finishes/${finish.id}`, finish).then(response => {
this.fetchFinishes();
}).catch(error => {
// Get laravel validation error
this.errors = error.response.data.errors;
});
},
laravel update code (not working)
public function update(FinishType $finishType)
{
// Don't know why not working
$finishType->update($this->validateRequest());
return new FinishTypeResource($finishType);
}
the response is null:
{"id":null,"name":null}
While this code works:
public function update($id)
{
$finishType = FinishType::find($id);
$validates = $this->validateRequest();
$finishType->name = $validates['name'];
$finishType->save();
return new FinishTypeResource($finishType);
}
public function validateRequest()
{
return request()->validate([
'name' => 'required | unique:finish_types',
]);
}
Note the Model name is FinishType and database table name is finish_types, I even tried to define the table name in the model like so protected $table = 'finish_types' – still not working and I already have defined the $fillable array!!!
Your route model binding is not working correctly, for the implicit binding to work your injected variable should match the route parameter name.
Assuming that your parameter name could be finish (reading the url from your javascript) you have to write the update function using $finish as injected variable, like this:
public function update(FinishType $finish)
{
$finish->update($this->validateRequest());
return new FinishTypeResource($finish);
}
Do the same for destroy():
public function destroy(FinishType $finish)
{
// your destroy code here
}
In any case you can run php artisan route:list to find your parameter name (the part of the URI in braces) and give the same name to the injected variable.
If the two do not match, parameter and injected variable name, laravel injects a void, not loaded, FinishType model so it does not make sense doing an update or a delete on it.
I can't post comments so I'm going to post what I assume is the answer.
Laravel does route model binding automagically when the route url name corresponds to the name of the table I think... or model.
So users/{id} would auto bind the User object when you type it as a param in the controller. Example (User $user)
However, since your URL seems to be "different" from the name of your Model/Table, go to the RouteServiceProvider, and manually do the binding.
So in your case you'd do something like this in the boot function of the RouteServiceProvider class:
public function boot()
{
parent::boot();
Route::model('finishes', FinishType::class);
}
Don't forget your imports :)
You can read more about Explicit Model Binding here: https://laravel.com/docs/5.8/routing#explicit-binding

Laravel 5.7 ApiResource GET params empty

I use Laravel 5.7 for my JSON API web application.
In my routes/api.php file, I created the following route :
Route::apiResource('my_resource', 'API\Resource')->except(['delete']);
I added the corresponding controller and methods (index, show,...) and everythink work perfectly. My issue is the following : I would like to add optional GET params like this :
http://a.x.y.z/my_resource?param=hello&param2=...
And for instance being able to retrieve 'hello' in my index() method. However, when I print the value of $request->input('param'), it's empty. I just don't get anything.
Yet, if I create a route like this, with an optional parameter:
Route::get('/my_resource/{param?}', 'API\Resource');
I'm able to get the parameter value in my controller method.
Here is my index method :
class Resource extends Controller {
public function index(Request $request)
{
print($request->input('param'));
// ...
}
// ...
}
Am I missing something ? I'm still new in Laravel maybe I missed something in the documentation.
Thanking you in advance,
You can use:
$request->route("param");

Implicit route, with firstOrCreate instead of findOrFail

Is it possible to create an implicit route where if it is not found the thing is created? I am using Laravel 5.5.13.
For instance this is my implicit route:
Route::post('thumbs/{player}', 'ThumbController#store');
And in my controller it is this:
public function store(Request $request, Player $player)
{
$thumb = new Thumb($request->all());
$player->thumbs()->save($thumb);
return response()->json($thumb, 201);
}
So now if I go to the endpoint of ..../api/thumb/1 it will create a thumb related with Player of id 1. However instead of a id number I wanted to provide it a string like this:
..../api/thumb/PLAYER_NAME
So example of ..../api/thumb/Blagoh, then my endpoint should first find if a player exists by name "Blagoh", and if it doesn't then it should create it. I couldn't figure this one out.
What you should do is adding into boot method of RouteServiceProvider something like this:
Route::bind('player', function ($value) {
if ($player = \App\Player::find($value)) {
return $player;
}
return Player::create(['name' => $value]);
});
It's called explicit binding and you can update logic as showed above. Reference - Route model binding

Resources