Laravel route group parameters - php

I want an app to use a URL structure something like this:
/account/johnsmith/photos
/account/johnsmith/messages
/account/johnsmith/settings
/account/joebloggs/photos
So users may add multiple accounts and then the route group selects the account automatically.
Route::group(['middleware' => 'auth', 'prefix' => 'account/{username}'], function () {
Route::get('/photos', 'PhotosController#index')->name('photos.index');
});
In the above example I can access the {username} parameter inside of PhotosController#index.
Is there a way to write some middleware that gets the account information automatically and it's accessible to all child routes in the group? Or am is this a bad way to try to build this?

This should be possible with route model binding.
Route::bind('username', function ($username) {
return Account::findByUsername($username);
});
Note: The above code could be put in your route provider or within the route group its self.
When done, you will pass the model Account into your controller methods as the first argument and it will automatically be the one matching that username. Or a 404 if it does not exist.
// If {username} is 'bob', the Account object now references Bob's account.
public function index(Account $account) {}
See Laravel Docs Here

Yes, if you need to perform a operation before all your controller methods get the request o propagate some common data you should use Laravel middlewares. It will not only centralise your logic as well as make your controller code neat.
Even laravel sometimes want you to do that.
In previous versions of Laravel, you could access session variables or the authenticated user in your controller's constructor. This was never intended to be an explicit feature of the framework. In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly in your controller's constructor. Before using this feature, make sure that your application is running Laravel 5.3.4 or above
So you can either write a middleware to calculate account details and if you are using the laravel version 5.3.4 and above you can directly assign it to the controller properties in constructor using closure based middlewares.
Like this:
class ProjectController extends Controller
{
/**
* All of the current user's account details
*/
protected $accountDetails;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->accountDetails= Auth::user()->accountDetails;
return $next($request);
});
}
}
Hope this would help.

Related

Laravel 8 - Run Route Middleware before Constructor

I am using Laravel v8.35. I have created a middleware EnsureTokenIsValid and registered it in app/Http/Kernel.php:
protected $routeMiddleware = [
...
'valid.token' => \App\Http\Middleware\EnsureTokenIsValid::class,
];
Here is the middleware itself:
<?php
namespace App\Http\Middleware;
use Closure;
class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}
return $next($request);
}
}
Essentially this middleware will redirect the user to a login page if the token is not valid. Now I want this middleware to run on specific routes. So I tried doing this:
Route::get('/', [IndexController::class, 'index'])->middleware('valid.token');
However it seems the code in the constructor of the parent controller (app/Http/Controllers/Controller.php) is being called first. My controllers all extend from this parent controller, e.g:
class IndexController extends Controller
I have tried putting the middleware at the very beginning in the constructor of Controller.php, but that does not work either, i.e. it just proceeds to the next line without performing the redirect:
public function __construct()
{
$this->middleware('valid.token');
// code here which should not run if the above middleware performs a redirect
$this->userData = session()->get('userData');
// Error occurs here if 'userData' is null
if ($this->userData->username) {
// do stuff here
}
}
If I put the middleware in my IndexController constructor, it works. However I don't want to do this for every controller - I just want the middleware to exist in the parent controller.
If you have the web middleware group assigned to this route it doesn't have access to the session in the constructor of your controller any way. You will need to use another middleware or a closure based middleware in the constructor so that it will run in the middleware stack, not when the constructor is ran:
protected $userData;
public function __construct(...)
{
...
$this->middleware(function ($request, $next) {
$this->userData = session()->get('userData');
if ($this->userData && $this->userData->username) {
// not sure what you need to be doing
}
// let the request continue through the stack
return $next($request);
});
}
So, I've also run into this problem and from debugging through the laravel framework code. It runs all the global middleware, then gathers the router middleware, constructs the controller, then afterwards runs all the middleware from the router + all the controller middleware configured in the controller constructor.
I personally think this is a bug, but that doesn't really help you since you need a solution and not just complaining.
Basically, your route no doubt targets a method on the controller, put all your dependencies into that function call and any code that relies upon it into that function call too.
If you need to share a common set of code which runs for each method in that controller, just create a private method and call it from each of the methods.
My problem was that I was using the constructor for dependency injection, like we are all expected to do, since a fully constructed object should have all it's dependencies resolved so you don't end up in a half constructed object state where depending on the function calls, depends on whether you have all the dependencies or not. Which is bad.
However, controller methods are a little different than what you'd consider a typical object or service. They are effectively called as endpoints. So perhaps it's acceptable, in a roundabout way, to consider them not like functions of an object. But using (abusing perhaps), PHP classes to group together methods of related functionality merely for convenience because you can autoload PHP classes, but not PHP functions.
Therefore, maybe the better way to think about this is to be a little permissive about what we would typically do with object construction.
Controller methods, are effectively callbacks for the router to trigger when a router is hit. The fact that they are in an object is for convenience because of autoloading. Therefore we should not treat the constructor in the same way we might for a service. But treat each controller endpoint method as the constructor itself and ignore the constructor except for some situations where you know you can do certain things safely.
But in all other cases, you can't use the constructor in the normal way, because of how the framework executes. So therefore we have to make this little accommodation.
I think it's a bug and personally I think it should be fixed. Maybe it will. But for today, with laravel 9, it's still working like this and I think this at least will help to guide people who ran into the same problem.

Laravel boilerplate send object to controller

I am newbie with Laravel. I have just fork laravel 5 boilerplate from https://github.com/rappasoft/laravel-5-boilerplate.
In route files, i see that there is a line like that :
Route::group(['prefix' => 'user/{deletedUser}'], function () {
Route::get('delete', 'UserStatusController#delete')->name('user.delete-permanently');
Route::get('restore', 'UserStatusController#restore')->name('user.restore');
});
I understand it means that, when url catch 'restore' it will use function restore in UserStatusController.
And here it is:
public function restore(User $deletedUser, ManageUserRequest $request)
Can anybody can help me to find out that, how can it send object $deletedUser to restore function. Tks you!
If your look at the route definition:
user/{deletedUser}
That {deletedUser} represents the id of the user to be deleted/restored. Variables are declared between {} in routes as the docs states.
Now in your controller:
public function restore(User $deletedUser, ManageUserRequest $request)
You can see that a User object is declared as an argument. This object is being injected by Laravel, that automatically will look for an User object that has that id. This is called Route Model Binding.
The documentation explains it better:
When injecting a model ID to a route or controller action, you will often query to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a user's ID, you can inject the entire User model instance that matches the given ID.
The same way, the Request class injected in this case is a ManageUserRequest that should be an instance of a FormRequest.
So, returning to your question, you will just have to specify the user id that you want to delete/restore, like this:
someurl.dev/users/5 // <-- for the user of id=5
Now your controller will interact with that specific object to do what you want:
public function restore(User $deletedUser, ManageUserRequest $request)
{
$deletedUser->delete(); // for example
}
There are two things happening here: parameters (docs) and model binding (docs)
First of all, in ['prefix' => 'user/{deletedUser}'] you can see that you are parsing a parameter from the url. This way, when someone navigates to api/user/3, laravel will pass the 3 to your route handler.
Second, it would be very nice to get the User model instance instead of just getting an id number. That's possible and it's called "model binding". Model binding can be
Explicit
You add your bindings to boot method in your RouteServiceProvider class, telling laravel what is the expected type of parameter.
public function boot()
{
parent::boot();
Route::model('deletedUser', App\User::class);
// in older docs I've seen 'App\User' passed as a string instead of as a class
}
Implicit
Laravel automatically figures out what model you need based on type hints.
public function restore(User $deletedUser, ManageUserRequest $request) {}
Here, $deletedUser has is type hinted as User. Laravel sees this, so it will go ahead and convert the id to the Eloquent model for you.
You seem to be using implicit binding, but feel free to check your RouteServiceProvider class.
Check the documentation links for more details, it's pretty well written. (If you are not using version 5.6, then just change the version number in the links).
You Just need to pass ID of the user as a parameter.
And this function
public function restore(User $deletedUser, ManageUserRequest $request)
you can see $deletedUser is of type User Laravel will search for that id ($deletedUser) in Users table and return an object of that user.
If you don't want User object and just need ID that you are passing in URL update restore() function to
public function restore($deletedUser, ManageUserRequest $request)

laravel 5.5 Get user details inside constructor

I am building an application with multiple user roles and actions. I did follow the official laravel doc (https://laravel.com/docs/5.5/middleware#middleware-parameters).
But in my controller's constructor (from where I call the above middleware) I am using Auth facade to get user details. I know how to use Auth facade, I had implemented it on several places inside my application. But when I use it inside the constructor it returns null (in logged in condition - I double checked that).
I implemented it like this, I have to call two controllers(since only registered users can access that page)
public function __construct()
{
$role = Auth::user()->role;
$this->middleware('auth');
$this->middleware('checkRole:$role');
}
PS: I tried to initialize $role variable as protected and outside the constructor , still not working. Any suggestions will be helpful
Thank you.
That's because constructors are created before middlewares,that's why its returning null.
This answer will propably solve your problems: Can't call Auth::user() on controller's constructor
If you are using the same user table for both "front-end" user and "admin" & want to apply condition in admin controller's constructor.
You can use below.
auth()->user()
And in the constructor you can use below code.
public function __construct(){
$this->middleware(function ($request, $next) {
if(auth()->user()->hasRole('frontuser')){
return redirect()->route('home')->withFlashMessage('You are not authorized to access that page.')->withFlashType('warning');
}
return $next($request);
});
}
But I prefer to handle these in separate middleware class instead of writing this in the controllers constructor.

Laravel - Authorization Works from Controller Method only

I have setup my model policy and it seems to be working when I authorize actions from within controller actions.
// create action
public function create()
{
$this->authorize('create', BusinessProfile::class);
return view('business-profile.create');
}
The policy for create simply returns true or false and switching the Boolean seems to be working as I am authorized based on it.
This conforms that my policies are set up correctly.
However, instead of using the authorize method everywhere in my controller, I tried to set up the middleware in my constructor.
The Laravel documentation shows this example.
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
So, I wrote this in my controller constructor.
public function __construct()
{
$this->middleware('auth');
$this->middleware('can:create,BusinessProfile')->only('create');
}
However, when doing this, the action is always unauthorized.
Bonus Information
I went ahead and wrote garbage code in my policy to raise a syntax error and still, I get an unauthorized response which tells me my policy is not firing at all. It could be that I have not registered my policy correctly but as mentioned above, $this->authorize(...) works as expected.
It seems there you used alias for your model while it requires model name. At documentation states:
some actions like create may not require a model instance. In these
situations, you may pass a class name to the middleware. The class
name will be used to determine which policy to use when authorizing
the action:
You can find more information here: https://laravel.com/docs/5.4/authorization#policy-methods
So in the controller constructor this line:
$this->middleware('can:create,BusinessProfile')->only('creat‌​e');
will become:
$this->middleware('can:create,App\BusinessProfile')->only('c‌​reate');

Why can't I get the request attributes back in laravel controller contructor?

I am trying to get the authenticated user in the constuctor of my controller in laravel by doing dd(auth()->user()); and it says null. I even added the user id into a request attribute in one of my middleware like so:
$request->attributes->add(['auth_user_id' => $user_id]);
Even if I do dd($request->get('auth_user_id') in my controller's construct method, I get null. But when I do the same thing in a test route, Both die dump statements work well and give me back the user or the user id, whichever I ask for.
Why am I not able to get these in the construct method of my controller tho? I am even able to get the same user id and auth user in my controller method to which the request goes to. Just not the construct method. What am I missing?
With Laravel 5.3, this change was introduced where middleware are initialized after the controller class is constructed. This means app-critical middleware like Auth --specifically Auth::user() are not available to the controller's __construct() method.
Please refer this documentation.
https://github.com/laravel/docs/blob/5.3/upgrade.md#session-in-the-constructor
Edit
This way you can implement what you needed.
This will allow registering a closure middleware in the controller's constructor, so that any auth/session stuff can be called and set as properties on the controller:
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = $request->user();
return $next($request);
});
}
Refer this link by Controller closure middleware - JosephSilber
I think this is because constructor method called when the object of class initialized and at that time you are not logged in and when you are not logged in you cannot get the auth_user_id.
But In case of normal method, they are called after constructor method, and you are logged in that's why you are able to get the auth_user_id

Categories