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.
Related
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.
With Slim I group my controllers and generally have an abstract BaseController I extend for each group. I use class based routing:
/* SLIM 2.0 */
// Users API - extends BaseApiController
$app->post('/users/insert/' , 'Controller\Api\UserApiController:insert');
.
.
// Campaigns - extends BaseAdminController
$app->get('/campaigns/', 'Controller\CampaignController:index')->name('campaigns');
and needed to password protect some routes, at other times I needed to have a slightly different configuration. BaseApiController, BaseAdminController... etc. There were times I needed to know which route I was in so I could execute a certain behavior for just that route. In those cases I would have a helper function like so:
/* SLIM 2.0 */
// returns the current route's name
function getRouteName()
{
return Slim\Slim::getInstance()->router()->getCurrentRoute()->getName();
}
This would give me the route name that is currently being used. So I could do something like...
namespace Controller;
abstract class BaseController
{
public function __construct()
{
/* SLIM 2.0 */
// Do not force to login page if in the following routes
if(!in_array(getRouteName(), ['login', 'register', 'sign-out']))
{
header('Location: ' . urlFor('login'));
}
}
}
I cannot find a way to access the route name being executed. I found this link
Slim 3 get current route in middleware
but I get NULL when I try
$request->getAttribute('routeInfo');
I have also tried the suggested:
'determineRouteBeforeAppMiddleware' => true
I've inspected every Slim3 object for properties and methods, I can't seem to find the equivalent for Slim3, or get access to the named route. It doesn't appear that Slim3 even keeps track of what route it executed, it just... executes it.
These are the following methods the router class has and where I suspect this value would be:
//get_class_methods($container->get('router'));
setBasePath
map
dispatch
setDispatcher
getRoutes
getNamedRoute
pushGroup
popGroup
lookupRoute
relativePathFor
pathFor
urlFor
I was hoping someone has done something similar. Sure, there are other hacky ways I could do this ( some I'm already contemplating now ) but I'd prefer using Slim to give me this data. Any Ideas?
NOTE: I'm aware you can do this with middleware, however I'm looking for a solution that will not require middleware. Something that I can use inside the class thats being instantiated by the triggered route. It was possible with Slim2, was hoping that Slim3 had a similar feature.
It's available via the request object, like this:
$request->getAttribute('route')->getName();
Some more details available here
The methods in your controller will all accept request and response as parameters - slim will pass them through for you, so for example in your insert() method:
use \Psr\Http\Message\ServerRequestInterface as request;
class UserApiController {
public function insert( request $request ) {
// handle request here, or pass it on to a getRouteName() method
}
}
After playing around I found a way to do it. It may not be the most efficient way but it works, and although it uses Middleware to accomplish this I think there are other applications for sharing data in the Middleware with controller classes.
First you create a middleware but you use a "Class:Method" string just like you would in a route. Name it whatever you like.
//Middleware to get route name
$app->add('\Middleware\RouteMiddleware:getName');
Then your middleware:
// RouteMiddleware.php
namespace Middleware;
class RouteMiddleware
{
protected $c; // container
public function __construct($c)
{
$this->c = $c; // store the instance as a property
}
public function getName($request, $response, $next)
{
// create a new property in the container to hold the route name
// for later use in ANY controller constructor being
// instantiated by the router
$this->c['currentRoute'] = $request->getAttribute('route')->getName();
return $next($request, $response);
}
}
Then in your routes you create a route with a route name, in this case I'll use "homePage" as the name
// routes.php
$app->get('/home/', 'Controller\HomeController:index')->setName('homePage');
And in your class controller
// HomeController.php
namespace Controller;
class HomeController
{
public function __construct($c)
{
$c->get('currentRoute'); // will give you "homePage"
}
}
This would allow you to do much more then just get a route name, you can also pass values from the middleware to your class constructors.
If anyone else has a better solution please share!
$app->getCurrentRoute()->getName();
$request->getAttribute('route')->getName();
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
I would like to extend Laravels Router class (Illuminate\Routing\Router) to add a method I need a lot in my application.
But sadly I can't get this to work. I already extended other classes successfully so I really have no idea where my wrong thinking comes from.
Anyway, right into the code:
<?php
namespace MyApp\Extensions;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function test()
{
$route = $this->getCurrentRoute();
return $route->getParameter('test');
}
}
So as you see I want to get the parameter set by {test} in routes.php with a simple call like:
Router::test();
Not sure how to go on now. Tried to bind it to the IOC-Container within my ServiceProvider in register() and boot() but I got no luck.
Whatever I try I get either a constructor error or something else.
All solutions I found are too old and the API has changed since then.
Please help me!
edit:
I already tried binding my own Router within register() and boot() (as said above) but it doesn't work.
Here is my code:
<?php
namespace MyApp;
use Illuminate\Support\ServiceProvider;
use MyApp\Extensions\Router;
class MyAppServiceProvider extends ServiceProvider {
public function register()
{
$this->app['router'] = $this->app->share(function($app)
{
return new Router(new Illuminate\Events\Dispatcher);
}
// Other bindings ...
}
}
When I try to use my Router now I have the problem that it needs an Dispatcher.
So I have to do:
$router = new Router(new Illuminate\Events\Dispatcher); // Else I get an exception :(
Also it simply does nothing, if I call:
$router->test();
:(
And if I call
dd($router->test());
I get NULL
Look at: app/config/app.php and in the aliases array. You will see Route is an alias for the illuminate router via a facade class.
If you look at the facade class in Support/Facades/Route.php of illuminate source, you will see that it uses $app['router'].
Unlike a lot of service providers in laravel, the router is hard coded and cannot be swapped out without a lot of work rewiring laravel or editing the vendor source (both are not a good idea). You can see its hardcoded by going to Illuminate / Foundation / Application.php and searching for RoutingServiceProvider.
However, there's no reason i can think of that would stop you overriding the router class in a service provider. So if you create a service provider for your custom router, which binds to $app['router'], that should replace the default router with your own router.
I wouldn't expect any issues to arise from this method, as the providers should be loaded before any routing is done. So overriding the router, should happen before laravel starts to use the router class, but i've not this before, so be prepared for a bit of debugging if it doesn't work straight away.
So I was asking in the official Laravel IRC and it seems like you simply can't extend Router in 4.1 anymore. At least that's all I got as a response in a pretty long dialogue.
It worked in Laravel 4.0, but now it doesn't. Oh well, maybe it will work in 4.2 again.
Other packages suffer from this as well: https://github.com/jasonlewis/enhanced-router/issues/16
Anyway, personally I'll stick with my extended Request then. It's not that much of a difference, just that Router would've been more dynamic and better fitting.
I'm using Laravel 4.2, and the router is really hard coded into the Application, but I extended it this way:
Edit bootstrap/start.php, change Illuminate\Foundation\Application for YourNamespace\Application.
Create a class named YourNamespace\Application and extend \Illuminate\Foundation\Application.
class Application extends \Illuminate\Foundation\Application {
/**
* Register the routing service provider.
*
* #return void
*/
protected function registerRoutingProvider()
{
$this->register(new RoutingServiceProvider($this));
}
}
Create a class named YourNamespace\RoutingServiceProvider and extend \Illuminate\Routing\RoutingServiceProvider.
class RoutingServiceProvider extends \Illuminate\Routing\RoutingServiceProvider {
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function($app)
{
$router = new Router($app['events'], $app);
// If the current application environment is "testing", we will disable the
// routing filters, since they can be tested independently of the routes
// and just get in the way of our typical controller testing concerns.
if ($app['env'] == 'testing')
{
$router->disableFilters();
}
return $router;
});
}
}
Finally, create YourNamespace\Router extending \Illuminate\Routing\Router and you're done.
NOTE: Although you're not changing the name of the class, like Router and RoutingServiceProvider, it will work because of the namespace resolution that will point it to YourNamespace\Router and so on.
In Slim is it possible to get the current route within middleware?
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->getRoute(); // Something like this?
}
}
I know you can call $app->router()->getCurrentRoute() after the slim.before.dispatch hook is called, but when you call this from middleware it returns a non-object. Any help would be greatly appreciated.
Yes and no. If you look at the source code for Slim, you will see that registered Middlewares are called in LIFO order when the Slim::run method is called, and then Slim runs it's own "call" method where the processing of the request begins. It is in this method that Slim parses and processes the route. In which case, you cannot access $app->router()->getCurrentRoute() in the Middleware::call method because it won't have been parsed and defined yet.
The only way to do this is to register a listener on slim.before.dispatch inside your Middleware, and implement whatever you want to do in that method.
From the name of your class I assume you are trying to create a basic authentication module? I've done something similar to this before, and it went something like this:
class AuthMiddleware extends \Slim\Middleware
{
public function call()
{
$this->app->hook('slim.before.dispatch', array($this, 'onBeforeDispatch'));
$this->next->call();
}
public function onBeforeDispatch()
{
$route = $this->app->router()->getCurrentRoute();
//Here I check if the route is "protected" some how, and if it is, check the
//user has permission, if not, throw either 404 or redirect.
if (is_route_protected() && !user_has_permission())
{
$this->app->redirect('/login?return=' . urlencode(filter_input(INPUT_SERVER, 'REQUEST_URI')));
}
}
}
In this example, the onBeforeDispatch method will be run before of the route handlers are invoked. If you look at the source code, you can see the events are fired inside a try/catch block that is listening for the exceptions thrown by $app->redirect() and $app->pass(), etc. This means we can implement our check/redirect logic here just as if this was a route handler function.
Above is_route_protected and user_has_permission are just pseudo-code to illustrate how my auth middleware worked. I structured the class so that you could specify a list of routes or regex for routes in the Middleware constructor that were protected, as well as passing a service object that implemented the user permission checking, etc. Hope this helps.
There is an alternative method of doing this, as I've been in the same situation. What I wanted to avoid was matching anything by route and wanted to use route names instead, so you could try the following:
public function call() {
$routeIWantToCheckAgainst = $this->slimApp->router()->urlFor('my.route.name');
$requestRoute = $this->slimApp->request()->getPathInfo();
if ($routeIWantToCheckAgainst !== $requestRoute) {
// Do stuff you need to in here
}
$this->next->call();
}
You could even have an array of routes you DON'T want the middleware to run on and then just check if it's in_array() etc and if not, do what you need to.
You should use app->request()->getPathInfo() instead of app->getRoute().
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->request()->getPathInfo();
}
}