I'm wondering how I can set up middleware using a controller constructor and also reference middleware parameters as I've done successfully in my routes file.
I have this working fine in routes.php:
Route::group(['middleware' => 'user-type:user'], function () {
// routes
});
Now I want to do this within a controller constructor, but I'm getting some issues...
public function __construct()
{
$this->middleware = 'event-is-active:voting';
}
And when I visit a link with the above applied I get the following error:
ErrorException in ControllerDispatcher.php line 127:
Invalid argument supplied for foreach()
Of course I'm doing this wrong - I couldn't see how to do it in the docs and reading the source didn't help, but perhaps I overlooked something. So I'm wondering what is the right way and is it even possible? Any help would be most appreciated, thanks!
You are using wrong syntax for setting middleware from controller constructor.
First of all you have to use laravel 5.1 to use middleware parameter.
Now you can set middleware in controller in constructor only.
Like
function __construct()
{
$this->middleware('event-is-active:voting');//this will applies to all methods of your controller
$this->middleware('event-is-active:voting', ['only' => ['show', 'update']]);//this will applies only show,update methods of your controller
}
Please note that in above code show and update are the example name. you have to write actual name which you are using in your controller.
Let say you are using
1. getShowUser($userId)
2. postUpdateUser($userId)
than you have to apply middleware in these methods as mentioned below:
function __construct()
{
$this->middleware('event-is-active:voting', ['only' => ['getShowUser', 'postUpdateUser']]);
}
Try this
function __construct()
{
$this->middleware('user-type:param1,param2', ['only' => ['show', 'update']]);
}
Related
I have been declaring all the routes for my application inside web.php , but it is now getting quite large. I find that I am losing a lot of time shifting between web.php and each controller and this is hurting productivity.
I feel like it would be better to define routes inside of the controller, perhaps ideally delegating some URL to a controller and then allowing the controller to handle the "sub routes" since this would allow me to use inheritance when I have two similar controllers with similar routes.
It is not possible given how laravel works. Every request is passed onto router to find its designated spot viz. the controller with the method. If it fails to find the route within the router, it just throws the exception. So the request never reaches any controller if the route is not found. It was possible in earlier versions on Symphony where you would configure the route in the comment of a particular controller method.
Sadly with laravel it works how it works.
But for me, I just like to have the routes in a separate file.
Alternate solution, easier way to sort all the routes.
You can move your route registration into controllers if you use static methods for this. The code below is checked in Laravel 7
In web.php
use App\Http\Controllers\MyController;
.....
MyController::registerRoutes('myprefix');
In MyController.php
(I use here additional static methods from the ancestor controller also posted below)
use Illuminate\Support\Facades\Route;
.....
class MyController extends Controller {
......
static public function registerRoutes($prefix)
{
Route::group(['prefix' => $prefix], function () {
Route::any("/foo/{$id}", self::selfRouteName("fooAction"));
Route::resource($prefix, self::selfQualifiedPath());
}
public function fooAction($id)
{
........
}
In Controller.php
class Controller extends BaseController {
....
protected static function selfAction($actionName, $parameters = [], $absolute = false)
{
return action([static::class, $actionName], $parameters, $absolute);
}
protected static function selfQualifiedPath()
{
return "\\".static::class;
}
protected static function selfRouteName($actionName)
{
//classic string syntax return "\\".static::class."#".$actionName;
// using tuple syntax for clarity
return [static::class, $actionName];
}
}
selfAction mentioned here is not related to your question, but mentioned just because it allows making correct urls for actions either by controller itself or any class using it. This approach helps making action-related activity closer to the controller and avoiding manual url-making. I even prefer making specific functions per action, so for example for fooAction
static public function fooActionUrl($id)
{
return self::selfAction('foo', ['id' => $id]);
}
Passing prefix into registerRoutes makes controller even portable in a sense, so allows inserting it into another site with a different prefix in case of conflict
I am using api throttle in my routes/api.php (as you can see in the code) but I am wondering if I can use it in the controller on methods.
Route::resource('/user/{user}/post', 'UserPostController')->middleware(['auth:api', 'throttle:5,1']);
Better to use the routes to specify the middleware for the routes. Still you think to use / specify inside your controller you can define __construct() menthod in your controller like:
public function __construct()
{
$this->middleware('throttle:5,1')->only('index');
}
This will work on the index action of your controller only.
For more details check the documentation Controller Middlewares
you can override route for example
Route::resource('/user/{user}/post', 'UserPostController')->middleware(['auth:api', 'throttle:5,1']);
//add route after resource
Route::get('/user/create', 'UserPostController#create')->middleware(['auth:api', 'throttle:5,1']);
second way add condition in controller
public function __construct()
{
$this->middleware('auth:api');
$this->middleware('throttle:10,1')->only('create');
}
as the title says I want to use a middleware inside a controller function. I have resource controllers, which their functions inside will have different access rights so I can't use a middleware in the web.php file, I have to use or apply it separately in each function to limit access, my googling hasn't been successful in getting a solution to that so far. Any help please and thanks in advance.
P.S. I believe no code is necessary here.
Middleware could also be applied to just one function, just add the method name in your controller constructor
public function __construct()
{
// Middleware only applied to these methods
$this->middleware('loggedIn', [
'only' => [
'update' // Could add bunch of more methods too
]
]);
}
OR
public function __construct()
{
// Middleware only applied to these methods
$this->middleware('loggedIn')->only([
'update' // Could add bunch of more methods too
]);
}
Here's the documentation
There are 3 ways to use a middleware inside a controller:
1) Protect all functions:
public function __construct()
{
$this->middleware('auth');
}
2) Protect only some functions:
public function __construct()
{
$this->middleware('auth')->only(['functionName1', 'functionName2']);
}
3) Protect all functions except some:
public function __construct()
{
$this->middleware('auth')->except(['functionName1', 'functionName2']);
}
Here you can find all the documentation about this topic: Controllers
I hope this can be helpful, regards!
Use the following code inside your controller constructor. The following code will use the auth middleware:
public function __construct() {
$this->middleware('auth');
}
Also you can simply add middleware at your routes. For example I need to add middleware for my method "registration_fee()" inside "RegisterController", so it will looks like this:
Route::get('/pay_register_fee', 'Auth\RegisterController#registration_fee')
->name('pay_register_fee')->middleware(['guest', Register::class, RegistrationFee::class]);
"RegistrationFee" is middleware that I want to add.
P.S. Not forget import class or write full path to middleware.
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();
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();
}
}