The Laravel 5 documentation describes two ways of assigning Middleware:
Assign middleware to the controller's route.
Specify middleware within your controller's constructor.
However, I realised that any code written in the controllers __construct() function will run before the Middleware, even if the Middleware is declared on the first line of the controller's __construct function.
I found a bug report for a similar issue in the Laravel github repository. However a collaborator closed the issue stating "This is the expected behaviour.".
I am thinking that middleware should be "layers" outside the application, while the __construct function is part of the application.
Why is the __construct function executed before the middleware (given it is declared before middleware runs)? and why this is expected?
Another answer to cover another use case to that question
If it's related to the order between middleware it self
You can update the $middlewarePriority in your App\Kernel.
Set Middleware Priority in App\Http\Kernel
For example, here I need my custom auth middleware to run first (before substitute bindings), so I unshift it onto the stack:
public function __construct(Application $app, Router $router)
{
/**
* Because we are using a custom authentication middleware,
* we want to ensure it's executed early in the stack.
*/
array_unshift($this->middlewarePriority, MyCustomApiAuthMiddleware::class);
parent::__construct($app, $router);
}
Alternatively, you could override that entire priority structure if you needed explicit control (not recommended because you'll have to pay closer attention during upgrades to see if the framework changes). Specific to this issue is the SubstituteBindings class that handles route model binding, so just make sure your auth middleware comes sometime before that.
/**
* The priority-sorted list of middleware.
*
* Forces the listed middleware to always be in the given order.
*
* #var array
*/
protected $middlewarePriority = [
\App\Http\Middleware\MyCustomApiAuthMiddleware::class
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
The application logic resides in the controller's methods. So basically application lives in the controller's methods, not in the whole controller itself.
Middleware runs BEFORE the request enters the respective controller method. And thus, this is always OUTSIDE the real application. No controller method is executed unless all the Middlewares are passing the request.
The $this->middleware("My\Middleware"); statements that you put in the controller constructor, REGISTERS the My\Middleware for checking before the request enters the application.
If you see the code of a middleware and
if the request is passing, then we send it to the next middleware using the $next($request); statement. This allows multiple middlewares to be executed for a single request. Now, if Laravel run the middleware right at the $this->middleware(...); statement, Laravel would probably not be able to know which middleware should be next checked.
So, Laravel solves this by registering all the middlewares first, then passing the request through all the middlewares one by one.
They updated the order of execution between middlewares, controller and controller's construct.
Previously it was:
1. The global middleware pipeline
2. The route middleware pipeline
3. The controller middleware pipeline
Now its:
1. The global middleware pipeline
2. Controller's Construct
3. The route & controller middlewares
Read more here:
https://laracasts.com/discuss/channels/general-discussion/execution-order-in-controllers-constructor-whit-middleware
https://laravel-news.com/controller-construct-session-changes-in-laravel-5-3
Related
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.
I am building a Laravel 7.x. application that will combine both standard authentication (user logs on via form) and API authentication through Sanctum (token based). I want to generate a sanctum API token during successful user authentication. To do that, I need to hook into the login flow.
Standard authentication was scaffolded to my application by running php artisan ui vue --auth.
When I inspect routes/web.php, I can see only Auth::routes(); which under the hood allegedly generates the classic routes I was used to in previous Laravel versions. Taken from the answer I linked, /login route definition that is generated looks like this:
$this->post('login', 'Auth\LoginController#login');
However, when I inspect my LoginController that was scaffolded, I can not see any of the methods that should be generated by Auth::routes(). There is nothing in there and it looks like everything is handled transparently to me as a developer:
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
How do I hook into the login flow and add my own actions to it?
I can see, two questions here and I will try to answer them as good as I can.
1. Execute action on successful user authentication
I think the cleanest way to achieve this, it to utilize the Laravel event / listeners architecture.
In app/Providers/EventServiceProvider.php extend the $listen array
// include at the top
use Illuminate\Auth\Events\Login;
protected $listen = [
// other handlers [],
Login::class => [
CreateUserApiToken::class,
],
];
Then execute this artisan command, which will magically create your listener file
php artisan event:generate
Now you can open app/Providers/CreateUserApiToken.php and put whatever you like in the handle function.
public function handle(Login $event)
{
dd($event->user);
}
2. Where's the actual laravel code?
For lots of Laravel classes, you will only find a minimal amount of code projected directly to your app directory. Most of it is hidden in traits or by extending parent classes. Let's take the LoginController for example
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use AuthenticatesUsers;
This is the trait that controller is using. And especially the top line gives you a pretty good clue where that file is located. You could also use your code editors search function, to search in all files for trait AuthenticatesUsers.
In this case the corresponding file would be vendor/laravel/ui/auth-backend/AuthenticatesUsers.php. Here you will find most of the functions that you are looking for. Of course, it's not a good idea to overwrite that file directly, because all the changes would be lost, if the laravel framework get's updated.
But if you find a function in there, that you want to change, let's say showLoginForm() for example, you can simply include that function in your LoginController and change the code.
public function showLoginForm()
{
return view('example.login');
}
I am quite new to the laravel and want to understand about middlewares in deep. I want to know that what is the major difference between attaching middleware with the route or adding the middleware in the controller's constructor.For example it is a constructor of the controller named UserController
public function __construct() {
$this->middleware('age');
}
Let's assume that it is the route of the same controller i.e UserController
Route::get('user/profile', function () {
//
})->middleware('age');
My main question is that should I need to add middleware in both routes and controller's constructor or just in one of them?
Both will perform same task,it`s just you can write in two different ways.
However, it is more convenient to specify middleware within your controller's constructor. Using the middleware method from your controller's constructor, you may easily assign middleware to the controller's action. You may even restrict the middleware to only certain methods on the controller class.
https://laracasts.com/discuss/channels/laravel/middleware-in-controller-or-on-route
It depends on your situation if you have condition in the controller you must use first case or not it's better to use second case.
public function __construct() {
$this->middleware('age');
}
```````````````````````````````````````````````````````````````````````````````
Global middlewares are those that will be running during every HTTP request of your application. In the $middleware property of your app/Http/Kernel.php class, you can list all the global middleware for your project.
```````````````````````````````````````````````````````````````````````
Route::get('user/profile', function () {
//
})->middleware('age');
````````````````````````````````````````````````````````````````````````
When you want middlewares to specific routes, you have to add the middleware with a key for your app/Http/Kernel.php file and such middlewares are called route middleware. $routeMiddleware by default holds entries for the middleware that are already incorporated in Laravel. For adding your custom middleware, you need to append them to the list and add a key of your choice
[link]https://www.w3schools.in/laravel-tutorial/middleware/
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.
I'm using controller middlewares in my Laravel 5.2 app. According to the docs, to exclude specific actions from being handled by a middleware, I need to use the except array:
class UserController extends Controller
{
public function __construct() {
// Exclude foo- and barAction from auth middleware
$this->middleware('auth', ['except' => [
'fooAction',
'barAction',
]]);
}
}
Of course, the total number of methods in a controller will almost always be greater than the number of methods linked to specific routes in routes.php. So except for the route-actions there will be others, dealing strictly with the logic - public or private methods.
Do I need to exclude all those non-route actions from a middleware or excluding the route-actions is enough?
EDIT:
I would say that the other, non-route methods - as they are not accessible from outside - don't need to be excluded from a middeware. The question is rather: is the middleware ran for them every time they are accessed? I wouldn't say so but it's nice to make sure.
Your $this->middleware() method is defined in Illuminate\Routing\Controller: it merely saves its arguments in a protected property that is only accessed through a getter.
Other than in tests, that getter is only invoked in two places:
Illuminate\Foundation\Console\RouteListCommand (which handles the route:list Artisan command); and
Illuminate\Routing\ControllerDispatcher (which dispatches routing calls to a controller).