Laravel Route model binding - empty user - laravel-5

I have a route
http://192.168.10.15/user/33/edit
I am trying to return the user based on the url id.
public function edit($id, \App\User $user)
{
dd($user->id);
return view('user.update');
}
The id is returning null, how do I do this?

For route binding to work you should have type-hinted variable names match a route segment name, as the doc required :
Laravel automatically resolves Eloquent models defined in routes or
controller actions whose type-hinted variable names match a route
segment name. For example:
Route::get('api/users/{user}', function (App\User $user) {
return $user->email; });
Since the $user variable is type-hinted as the App\User Eloquent model
and the variable name matches the {user} URI segment, Laravel will
automatically inject the model instance that has an ID matching the
corresponding value from the request URI. If a matching model instance
is not found in the database, a 404 HTTP response will automatically be generated.
For your case :
Route::get('/users/{user}/edit', 'YourController#edit');
And in your controller :
public function edit(\App\User $user)
{
dd($user->id);
return view('user.update')->withUser($user);
}

If you recently upgraded to laravel 5.3 and above, you might want to check if you have updated laravel/app/Http/Kernel.php to register route middleware for binding substitution.
Route model binding is now accomplished using middleware. All
applications should add the
Illuminate\Routing\Middleware\SubstituteBindings to your web
middleware group in your app/Http/Kernel.php file:
\Illuminate\Routing\Middleware\SubstituteBindings::class,
You should also register a route middleware for binding substitution
in the $routeMiddleware property of your HTTP kernel:
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
Once this route middleware has been registered, you should add it to
the api middleware group:
'api' => [
'throttle:60,1',
'bindings',
],

I fixed it by adding named route. Model binding didn't work if there was no name specified for route.
Route::get('product/{product}', "ProductController#read")->name('product.read');

Related

In Laravel, it seems the middleware is not applied immediately

I use Laravel 9.
I have this route, included in a group and middleware:
Route::middleware(['auth', 'ensureUserIsAdmin'])->group(function () {
.....
Route::resource('sports', SportController::class);
.....
});
The middleware is only to check if the user is or not an administrator.
When the connected user is not admin, and tries to go to this route for a known sport:
/sports/FOOT/edit
then he receives the response "FORBIDDEN". Perfect, the middleware made his job.
But when the same not admin user tries to go to a route for an unknown sport:
/sports/UNKNOWSPORT/edit
then he receives the response "NOT FOUND". Is it normal? It looks like the framework makes a database request and only after he applies the middleware.
What's wrong in my code?
Every route within web.php file is processed by web group middleware - you may find this in RouteServiceProvider class
Route::middleware('web')
->group(base_path('routes/web.php'));
This group defined within Kernel class and contains some app middleware. They're handled before any route specific middleware fires
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
So when you think your route has 'auth', 'ensureUserIsAdmin' as middleware only it is not quite true
In your case \Illuminate\Routing\Middleware\SubstituteBindings::class middleware is the one is failing and showing 404. And yes, it makes query to database if there are some route parameters in it
For simplicity, let say you have this route and only model with id=1 exists
Route::get('/sports/{sport}', function (Sport $sport) {
dump($sport);
});
/sports/1 is dumping model, sports/2 shows 404 as expected. Let's comment \Illuminate\Routing\Middleware\SubstituteBindings::class
Now both pages are actually dumping model, but sports/2 shows empty default model with no parameters
If you need to change this logic and show 403 for non-existing models, you may add middleware to a group BEFORE \Illuminate\Routing\Middleware\SubstituteBindings::class middleware
For example, lets' create simple middleware which always return 403 like
public function handle(Request $request, Closure $next)
{
abort(403);
}
And change web group to
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
AlwaysFalse::class, // HERE I AM
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
Now no matter what both pages will show 403
But this will be applied for EVERY route within routes/web.php file, so you may change the logic like
public function handle(Request $request, Closure $next)
{
// Applied only in any route named `sports.something` - resource routes including
// is_admin() function doesn't really exists, middleware logic is up to you
abort_if((Route::is('sports.*') && !is_admin()), 403);
return $next($request);
}
Now for admin user /sports/1 shows model, /sports/2 - 404 response.
For non-admin user both pages will return 403.
About is it normal or not - I think yes. Ask yourself - what can you do (what your access level) over a thing which is doesn't even exists? So it better define first does model really exists and after you're sure it is, make something with it. But it is just my opinion, someone may disagree
Hope it'll help

Laravel using user id in route

So I have a route profile/{user_id}
How do I redirect user to that URL when they click on link?
Here's my controller:
{
function checkid($user_id) {
if (Auth::check())
{
$user_id = Auth::id();
return view('profile', [
'id' => $user_id
]);
}
}
}
Bit confused with the question but Laravel uses ID as default for dependency injection and changing it is easy: just change the routeKey in the model BUT in your instance, you're using the signed in user. So forgot the id!
<?php
namespace App\Http\Controllers;
class RandomController extends Controller {
public function index()
{
return view('profile');//use auth facade in blade.
}
}
In your routes use a middleware to prevent none authenticated users from reaching this method
<?php
Route::group('/loggedin', [
'middleware' => 'auth',
], function() {
Route::get('/profile', 'RandomController#index')->name('profile.index');
});
Now to redirect in your blade file use the route function, don't forget to clear your cache if you've cached your routes!
<h1>Hi! {{Auth::user()->name}}</h1>
View profile
Because I used the name method on the Route I can pass that route name into the route function. Using php artisan route:list will list all your route parameters which is cool because it will also tell you the names and middlewares etc.
if I had a route which required a parameter; the route function accepts an array of params as the second parameter. route('profile.index', ['I_am_a_fake_param' => $user->id,]).
Let me know if you need help with anything else.
You can redirect with the redirect() helper method like this:
return redirect()->url('/profile/' . $user_id);
But I'm not really following your usecase? Why do you want to redirect? Do you always want the user to go to their own profile? Because right now you are using the id from the authenticated user, so the user_id parameter is pretty much useless.

Passing two models to the Authorize middleware (also called can)

I am trying to pass two models to the Authorize middleware, used under the name can.
routes/api.php
Route::middleware('can:reach,profile,photo')->resource('users/{user}/profiles/{profile}/photos', 'PhotoController');
Then I try to retrieve arguments like so:
app/Policies/PhotoPolicy.php
public function reach(User $user, Profile $profile, Photo $photo)
{
return $profile->id === $photo->profile->id;
}
But the middleware is totally ignored. I checked the definition of the middleware and I cannot see why this would not work.
Thanks in advance.
I think you have to add the middleware to the resource, not the opposite. Or, you can create a group.
Example:
Route::group(['middleware' => 'can:reach,profile,photo'], function($router){
$router->resource('users/{user}/profiles/{profile}/photos', 'PhotoController');
});

Set username as prefix in URIs Laravel

I want to set prefix in all of the authenticated URIs in Laravel 5.3 app. If I use Route::group(['prefix' => {username}]), then how can I get the username in web.php file.
Assuming you have defined routes like this:
Route::group(['prefix' => '/{user}'], function() {
Route::get('/profile', 'UserController#showProfile')->name('user.profile');
});
You can use Laravel's route-model binding to pass an User instance directly into your routes:
View Profile
Then in the controller , you can easily grab that model instance:
class UserController extends Controller
{
public function showProfile(User $user)
{
return view('user.profile.index', compact('user'));
}
}
Check out the documentation here.
EDIT: By default, Laravel uses the id column when retrieving a given model class. You can change that to any column, in your case username , easily by overriding the getRouteKeyName() method on your User model.
public function getRouteKeyName()
{
return 'username';
}
You can't and you shouldn't hardcore routes with usernames. If you have 100 users, do you plan to create 100 route groups for each user? Assuming each user has 2 routes that's 200 routes.
What you need to do is generate routes with username segment and detect the user on the fly based on the user name.
Route::group(['prefix' => '{username}'], function () {
Route::get('profile', 'UserController#profile');
Route::get('setting', 'UserController#setting');
});
The routes generated would be like
http://example.app/{username}/profile
http://example.app/{username}/setting
So a user with username adam with get the links.
http://example.app/adam/profile
http://example.app/adam/setting
Similarly you can have username based links for all your users and you need to detect them in your controller using route model binding or the standard way.
Edit
For closure based routes you can get the url segment like so
Route::get('{username}/profile', function ($username) {
return 'Username is '.$username;
});
With controllers you get them as the parameters.
public function profile($username)
{
return $username;
}

How to bind a model to resource store method in Laravel 5.3

I am trying to assign a Product model for store route which is made by Route::resource method
public function store(Request $request, Product $product)
I already worked with Route Model Binding a little, and it works very nice with
public function show(Product $product)
But I can't force a store method to use Product model.
Ok, since somebody marked this question as favourite I will explain what I did to make it work
This is how we declare our routes, we need to place out specific route before Route::resource call
Route::post('foo/{baz}', [
'as' => 'foo.store',
'uses' => 'FooController#store']
);
Route::resource('foo', 'FooController');
Our Foo controller store method:
public function store(Baz $baz, Request $request){
}
The $baz variable is just passed after url slash in form action url. it can be like that
<form action="foo/5">
Keep in mind that "foo&baz=5" won't work
For some reason I didn't have to, or at least didn't try to bind a Foo model with controller - it just works.
The order of routes and parameters in controller method if important for PHP reflection

Resources