I am attempting to run middleware on specific routes as well as inside controller constructor.
However, it appears middleware that are defined inside the controller constructor are not being executed for the routes that include middleware.
Is this not possible? (all of these middleware are registered in kernel.php, all middleware in constructor working before adding middleware to route)
Route
Route::get('/{organization_slug}', function($organization_slug){
$organization = \App\Organization::where('slug', '=', $organization_slug)->first();
$app = app();
$controller = $app->make('\App\Http\Controllers\OrganizationController');
return $controller->callAction('details', $parameters = array($organization->id));
})->middleware('verifyorganizationslug');
Controller Constructor
public function __construct()
{
$this->middleware('auth', ['only' => ['create', 'update', 'store']]);
$this->middleware('accountactive', ['only' => ['create', 'update', 'store']]);
$this->middleware('ownsorganization', ['only' => ['update']]);
$this->middleware('verifyorganization', ['only' => ['details']]);
}
In gatherMiddleware, unique middleware are selected after merging route middleware with controller middleware.
public function gatherMiddleware()
{
if (! is_null($this->computedMiddleware)) {
return $this->computedMiddleware;
}
$this->computedMiddleware = [];
return $this->computedMiddleware = array_unique(array_merge(
$this->middleware(), $this->controllerMiddleware()
), SORT_REGULAR);
}
If you're viewing action mapped to details method, you should see verifyorganizationslug then verifyorganization applied.
Depending on which route you're viewing, the computedMiddleware will always have verifyorganizationslug middleware applied and other middleware specified for that route in the controller applied.
Route controller getMiddleware filters all middleware not belonging in that method.
public function getMiddleware($controller, $method)
{
if (! method_exists($controller, 'getMiddleware')) {
return [];
}
return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
return static::methodExcludedByOptions($method, $data['options']);
})->pluck('middleware')->all();
}
IMPORTANT
Now, your code runs around the request-response Pipeline which ensures that middleware are applied in order explained above. You lose the application of controller middleware as isControllerAction returns false because it's a Closure and not a string.
Fast forward, you want to use:
Route::get('/{organization_slug}', '\App\Http\Controllers\FooController#details')
->middleware('foo');
Then resolve organization_slug inside of your controller.
public function details(Request $request, $organisation_slug)
{
$organization = \App\Organization::where('slug', '=', $organization_slug)
->first();
...
}
Or consider using Route binding to bind organization_slug route parameter to an instance of the organization.✌️
Related
I'm setting up policies for my laravel application, and I'm stuck with a problem. I have to put the policy in the constructor of my controller this way:
public function __construct()
{
$this->middleware(['can:viewAny,App\Models\Photo'], ['only' => ['index']]);
$this->middleware(['can:view,photo'], ['only' => ['show']]);
}
Problem is, for the store action, I have to check one of the params sent in the request to check if the user is allowed to post on the related parent. According to the documentation, I could make my Policy this way:
public function store(User $user, int $parentId)
{
$parent = Parent::find($parentId);
return $user->id === $parent->user_id
}
And in the controller:
public function store(Request $request)
{
$this->authorize('store', [$request->parent]);
// The current user can store the photo...
}
But in the example, the authorization is put in the function, and there are no example with the usage of the request when treating the policy as a middleware. Is it even possible? I would have crafted something like:
$this->middleware(['can:store,App\Models\Photo,request->parent'], ['only' => ['store']]);
But that won't work. Thanks a lot if you can help me on this one!
I found how to do it, I forgot about the request() helper. Thus, I can access everything put in the request, and I can call the helper directly inside the policy.
So I can do this in the contructor:
$this->middleware(['can:store,App\Models\Photo'], ['only' => ['store']]);
And in the PhotoPolicy:
public function store(User $user)
{
$input = request()->input();
$parent = Parent::find($input['parent_id']);
return $user->id === $parent->user_id
}
I created a policy in laravel 5.3 with this two actions:
class ProjectPolicy {
...
public function index(User $user)
{
return true;
}
public function create(User $user)
{
return true;
}
...
}
and then I tried to make authorization via route group middleware:
Route::group(['middleware' => ['can:index,create,App\Project']], function () {
Route::resource('projects', 'ProjectController');
});
ofcourse I have created Project model and controller correctly, but calling index and create actions always returns 403 forbidden response. where is the problem?
Update:
removing one of actions from route middleware, results correct response. something like this:
Route::group(['middleware' => ['can:index,App\Project']], function () {
Route::resource('projects', 'ProjectController');
});
Looking through the docs the can middleware doesn't really lend itself to resources. You could use multiple middleware calls on the group but this would mean that your use would require all privileges to access the routes.
Your alternatives are:
Add $this->authorize(new App\Project) to your index and create methods in your controller. Laravel will use reflection to figure out what policy to use based on the method it is called from.
Or
In the __construct() method of your controller you could use:
$this->authorizeResource(App\Project::class);
This will require you to
create update, view and delete methods inside your Policy class. Each of these methods will be passed User $user, Project $project e.g.
public function view(User $user, Project $project)
{
return true;
}
FYI, if you leave off the method name with authorize() or you use authorizeResource() Laravel will map certain method names to different policy methods i.e. :
[
//ControllerMethod => PolicyMethod
'show' => 'view',
'create' => 'create',
'store' => 'create',
'edit' => 'update',
'update' => 'update',
'destroy' => 'delete',
];
You can override this by adding a resourceAbilityMap() method to your controller and returning a different array to the one above.
Hope this helps!
Spend hours on that ...
If your controller method does not take a Model as parameter, you also have to override the resourceMethodsWithoutModels().
protected function resourceAbilityMap()
{
Log::info("Inside resourceAbilityMap()");
return array_merge(parent::resourceAbilityMap(), [
//ControllerMethod => PolicyMethod
'index' => 'index',
'search' => 'search'
]);
}
protected function resourceMethodsWithoutModels(){
return array_merge(parent::resourceMethodsWithoutModels(), [
'search'
]);
}
I am creating a project where i have multiple user types, eg. superadmin, admin, managers etc. Once the user is authenticated, the system checks the user type and sends him to the respective controller. The middle ware for this is working fine.
So when manager goes to http://example.com/dashboard he will see the managers dashboard while when admin goes to the same link he can see the admin dashboard.
The below route groups work fine individually but when placed together only the last one works.
/***** Routes.php ****/
// SuperAdmin Routes
Route::group(['middleware' => 'App\Http\Middleware\SuperAdminMiddleware'], function () {
Route::get('dashboard', 'SuperAdmin\dashboard#index'); // SuperAdmin Dashboard
Route::get('users', 'SuperAdmin\manageUsers#index'); // SuperAdmin Users
});
// Admin Routes
Route::group(['middleware' => 'App\Http\Middleware\AdminMiddleware'], function () {
Route::get('dashboard', 'Admin\dashboard#index'); // Admin Dashboard
Route::get('users', 'Admin\manageUsers#index'); // Admin Users
});
I know we can rename the routes like superadmin/dashboard and admin/dashboard but i was wondering if there is any other way to achieve the clean route. Does anyone know of any anywork arounds ?
BTW i am using LARAVEL 5.1
Any help is appreciated :)
You can do this with a Before Middleware that overrides the route action's namespace, uses and controller attributes:
use Closure;
use Illuminate\Http\Request;
use Illuminate\Contracts\Container\Container;
use App\Http\Middleware\AdminMiddleware;
use App\Http\Middleware\SuperAdminMiddleware;
class AdminRoutingMiddleware
{
/**
* #var Container
*/
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
private static $ROLES = [
'admin' => [
'namespace' => 'Admin',
'middleware' => AdminMiddleware::class,
],
'super' => [
'namespace' => 'SuperAdmin',
'middleware' => SuperAdminMiddleware::class,
]
];
public function handle(Request $request, Closure $next)
{
$action = $request->route()->getAction();
$role = static::$ROLES[$request->user()->role];
$namespace = $action['namespace'] . '\\' . $role['namespace'];
$action['uses'] = str_replace($action['namespace'], $namespace, $action['uses']);
$action['controller'] = str_replace($action['namespace'], $namespace, $action['controller']);
$action['namespace'] = $namespace;
$request->route()->setAction($action);
return $this->container->make($role['middleware'])->handle($request, $next);
}
}
This way you have to register each route only once without the final namespace prefix:
Route::group(['middleware' => 'App\Http\Middleware\AdminRoutingMiddleware'], function () {
Route::get('dashboard', 'dashboard#index');
Route::get('users', 'manageUsers#index');
});
The middleware will convert 'dashboard#index' to 'Admin\dashboard#index' or 'SuperAdmin\dashboard#index' depending on current user's role attribute as well as apply the role specific middleware.
The best solution I can think is to create one controller that manages all the pages for the users.
example in routes.php file:
Route::get('dashboard', 'PagesController#dashboard');
Route::get('users', 'PagesController#manageUsers');
your PagesController.php file:
protected $user;
public function __construct()
{
$this->user = Auth::user();
}
public function dashboard(){
//you have to define 'isSuperAdmin' and 'isAdmin' functions inside your user model or somewhere else
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('dashboard');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('dashboard');
}
}
public function manageUsers(){
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('manageUsers');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('manageUsers');
}
}
So I have a route that looks like this:
Route::any('some/page', ['as' => 'some-page', 'uses' => 'SomePageController#index']);
However, I also have ajax calls at the same URL (using a request parameter called ajax like: some/page/?ajax=my_action) that I want to hit methods on my controller:
index already routes: 'SomePageController#index'
ajax = my_action needs to route: 'SomePageController#ajaxMyAction'
ajax = my_other_action needs to route: 'SomePageController#ajaxMyOtherAction'
ajax = blah_blah needs to route: 'SomePageController#ajaxBlahBlah
...
What's the elegant solution to setting this up in my routes.php file?
After inspection of Laravel's Http Request and Route classes, I found the route() and setAction() methods could be useful.
So I created a middleware to handle this:
<?php namespace App\Http\Middleware;
class Ajax {
public function handle($request, Closure $next)
{
// Looks for the value of request parameter called "ajax"
// to determine controller's method call
if ($request->ajax()) {
$routeAction = $request->route()->getAction();
$ajaxValue = studly_case($request->input("ajax"));
$routeAction['uses'] = str_replace("#index", "#ajax".$ajaxValue, $routeAction['uses']);
$routeAction['controller'] = str_replace("#index", "#ajax".$ajaxValue, $routeAction['controller']);
$request->route()->setAction($routeAction);
}
return $next($request);
}
}
Now my route looks like:
Route::any('some/page/', ['as' => 'some-page', 'middleware'=>'ajax', 'uses' => 'SomePageController#index']);
And correctly hits my controller methods (without disturbing Laravel's normal flow):
<?php namespace App\Http\Controllers;
class SomePageController extends Controller {
public function index()
{
return view('some.page.index');
}
public function ajaxMyAction(Requests\SomeFormRequest $request){
die('Do my action here!');
}
public function ajaxMyOtherAction(Requests\SomeFormRequest $request){
die('Do my other action here!');
}
...
I think this is a fairly clean solution.
You can't make this dispatch in the routing layer if you keep the same URL. You have two options :
Use different routes for your AJAX calls. For example, you can prefix all your ajax calls by /api. This is a common way :
Route::group(['prefix' => 'api'], function()
{
Route::get('items', function()
{
//
});
});
If the only different thing is your response format. You can use a condition in your controller. Laravel provides methods for that, for example :
public function index()
{
$items = ...;
if (Request::ajax()) {
return Response::json($items);
} else {
return View::make('items.index');
}
}
You can read this http://laravel.com/api/5.0/Illuminate/Http/Request.html#method_ajax and this http://laravel.com/docs/5.0/routing#route-groups if you want more details.
I have the following table: group_pages in mysql database with page name route name :
id name route
--------------------
0 About about
1 Contact contact
2 Blog blog
what I am trying to do is to create dynamic routes in my : routes.php ?
Where if I go to for example: /about it will go to AboutController.php ( which will be created dynamically) is that possible? is it possible to create a dynamic controller file?
I am trying to create dynamic pages routes that links to a controller
example i want to generate this dynamically in my routes.php
Route::controller('about', 'AboutController');
Route::controller('contact', 'ContactController');
Route::controller('blog', 'BlogController');
This is not the right way to create dynamic pages instead, you should use a database and keep all pages in the database. For example:
// Create pages table for dynamic pages
id | slug | title | page_content
Then create Page Eloquent model:
class Page extends Eloquent {
// ...
}
Then create Controller for CRUD, you may use a resource controller or a normal controller, for example, normally a PageController:
class PageController extends BaseController {
// Add methods to add, edit, delete and show pages
// create method to create new pages
// submit the form to this method
public function create()
{
$inputs = Input::all();
$page = Page::create(array(...));
}
// Show a page by slug
public function show($slug = 'home')
{
$page = page::whereSlug($slug)->first();
return View::make('pages.index')->with('page', $page);
}
}
The views/page/index.blade.php view file:
#extends('layouts.master')
{{-- Add other parts, i.e. menu --}}
#section('content')
{{ $page->page_content }}
#stop
To show pages create a route like this:
// could be page/{slug} or only slug
Route::get('/{slug}', array('as' => 'page.show', 'uses' => 'PageController#show'));
To access a page, you may require url/link like this:
http://example.com/home
http://example.com/about
This is a rough idea, try to implement something like this.
After spending 2 hours, digging through google and Laravel source, I came up with this solution, which I think works the best and looks the cleanest. No need for redirects and multiple inner requests.
You add this route at the very bottom of routes files.
If no other routes are matched, this is executed. In the closure, you decide which controller and action to execute.
The best part is - all route parameters are passed to action, and method injection still works. The ControllerDispatcer line is from Laravel Route(r?) class.
My example would handle 2 cases - first checks if user exists by that name, then checks if an article can be found by the slug.
Laravel 5.2 (5.3 below)
Route::get('{slug}/{slug2?}', function ($slug) {
$class = false;
$action = false;
$user = UserModel::where('slug', $slug)->first();
if ($user) {
$class = UserController::class;
$action = 'userProfile';
}
if (!$class) {
$article= ArticleModel::where('slug', $slug)->first();
if ($article) {
$class = ArticleController::class;
$action = 'index';
}
}
if ($class) {
$route = app(\Illuminate\Routing\Route::class);
$request = app(\Illuminate\Http\Request::class);
$router = app(\Illuminate\Routing\Router::class);
$container = app(\Illuminate\Container\Container::class);
return (new ControllerDispatcher($router, $container))->dispatch($route, $request, $class, $action);
}
// Some fallback to 404
throw new NotFoundHttpException;
});
5.3 has changed how the controller gets dispatched.
Heres my dynamic controller example for 5.3, 5.4
namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Routing\ControllerDispatcher;
use Illuminate\Routing\Route;
class DynamicRouteController extends Controller
{
/**
* This method handles dynamic routes when route can begin with a category or a user profile name.
* /women/t-shirts vs /user-slug/product/something
*
* #param $slug1
* #param null $slug2
* #return mixed
*/
public function handle($slug1, $slug2 = null)
{
$controller = DefaultController::class;
$action = 'index';
if ($slug1 == 'something') {
$controller = SomeController::class;
$action = 'myAction';
}
$container = app();
$route = $container->make(Route::class);
$controllerInstance = $container->make($controller);
return (new ControllerDispatcher($container))->dispatch($route, $controllerInstance, $action);
}
}
Hope this helps!
try
Route::get('/', ['as' => 'home', 'uses' => 'HomeController#index']);
$pages =
Cache::remember('pages', 5, function() {
return DB::table('pages')
->where('status', 1)
->lists('slug');
});
if(!empty($pages))
{
foreach ($pages as $page)
{
Route::get('/{'.$page.'}', ['as' => $page, 'uses' => 'PagesController#show']);
}
}
There is a component available, which you can use to store routes in a database. As an extra advantage, this component only loads the current active route, so it improves performance, since not all routes are loaded into the memory.
https://github.com/douma/laravel-database-routes
Follow the installation instructions provided in the readme.
Storing routes in the database
The only thing needed here is injecting the RouteManager into for example a cli command. With addRoute can tell the RouteManager to store the route into the database. You can easily change this code and use your own repository of pages or other data to construct the routes.
use Douma\Routes\Contracts\RouteManager;
class RoutesGenerateCommand extends Command
{
protected $signature = 'routes:generate';
private $routeManager;
public function __construct(RouteManager $routeManager)
{
$this->routeManager = $routeManager;
}
public function handle()
{
$this->routeManager->addRoute(
new Route('/my-route', false, 'myroute', MyController::class, 'index')
);
}
}
Run this cli command every 2-5 minutes or after a change in your data to make sure the routes are recent.
Register the RouteMiddleware in App\Http\Kernel.php
\Douma\Routes\Middleware\RouteMiddleware::class
Empty your web.php
If you have defined any Laravel route, make sure to empty this file.
Using routes from the database
You can use the route in blade:
{{ RouteManager::routeByName('myroute')->url() }}
Or you can inject the RouteManager-interface anywhere you like to obtain the route:
use Douma\Routes\Contracts\RouteManager;
class MyClass
{
public function __construct(RouteManager $routeManager)
{
$this->routeManager = $routeManager;
}
public function index()
{
echo $this->routeManager->routeByName('myroute')->url();
}
}
For more information see the readme.
We can make dynamic route by this way
// Instanciate a router class.
$router = app()->make('router');
Get Route value from Database
// For route path this can come from your database.
$paths = ['path_one','path_two','path_three'];
Then iterate the value to make dynamic route
// Then iterate the router "get" method.
foreach($paths as $path){
$router->resource($path, 'YourController');
}
You can use GET|POST|PUT|PATCH|DELETE methods also
// Then iterate the router "get" method.
foreach($paths as $path){
$router->get($path, 'YourController#index')->name('yours.index');
}
You can do something like this. It works perfect for me
use Illuminate\Container\Container;
use Illuminate\Routing\ControllerDispatcher;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Illuminate\Routing\Route as MainRoute;
use Illuminate\Routing\Router;
Route::group([
'prefix' => config('app.payment.route_prefix'),
'namespace' => config('app.payment.route_namespace'),
'middleware' => config('app.payment.route_middleware')
], function (Router $router) {
/**
* Resolve target payment method controller
*
* #param $key Example key [paypal]
* #param $action
* #return mixed
*/
$resolver = function ($key, $action) {
$route = app(MainRoute::class);
$container = app(Container::class);
// Generate App\\Http\Controllers\PaymentMethods\PaypalController
$controller = app(sprintf('%s\\%sController', config('app.payment.route_namespace'), ucfirst($key)));
return (new ControllerDispatcher($container))->dispatch($route, $controller, $action);
};
$router->post('{key}/error', function ($key) use ($resolver) {
return $resolver($key, 'error');
});
$router->post('{key}/success', function ($key) use ($resolver) {
return $resolver($key, 'success');
});
$router->get('{key}', function ($key) use ($resolver) {
return $resolver($key, 'index');
});
});