115

I am carrying out a permissions check on a user to determine whether they can view a page or not. This involves passing the request through some middleware first.

The problem I have is I am duplicating the same database query in the middleware and in the controller before returning the data to the view itself.

Here is an example of the setup;

-- routes.php

Route::get('pages/{id}', [
   'as' => 'pages',
   'middleware' => 'pageUser'
   'uses' => 'PagesController@view'
]);

-- PageUserMiddleware.php (class PageUserMiddleware)

public function handle($request, Closure $next)
    {
        //get the page
        $pageId = $request->route('id');
        //find the page with users
        $page = Page::with('users')->where('id', $pageId)->first();
        //check if the logged in user exists for the page
        if(!$page->users()->wherePivot('user_id', Auth::user()->id)->exists()) {
            //redirect them if they don't exist
            return redirect()->route('redirectRoute');
        }
        return $next($request);
    }

-- PagesController.php

public function view($id)
{
    $page = Page::with('users')->where('id', $id)->first();
    return view('pages.view', ['page' => $page]);
}

As you can see, the Page::with('users')->where('id', $id)->first() is repeated in both the middleware and controller. I need to pass the data through from one to the other so an not to duplicate.

1
  • I was going to ask sort of the same, took me a long time to find this answer. Here is my question. I'll add it here for SEO/findability reasons, hope that's ok: Laravel 5.0 - Load model in middleware AND controller. How do I load an instance of the Users model so that the same instance (only one database query) is available in both the Middleware and the Controller? Because in the middleware I want to check if the user is Authorized and in the Controller I might want to present information about the User or manipulate the User somehow. Commented Oct 27, 2015 at 5:41

13 Answers 13

172

I believe the correct way to do this (in Laravel 5.x) is to add your custom fields to the attributes property.

From the source code comments, we can see attributes is used for custom parameters:

 /**
 * Custom parameters.
 *
 * @var \Symfony\Component\HttpFoundation\ParameterBag
 *
 * @api
 */
public $attributes;

So you would implement this as follows;

$request->attributes->add(['myAttribute' => 'myValue']);

You can then retrieved the attribute by calling:

\Request::get('myAttribute');

Or from request object in laravel 5.5+

 $request->get('myAttribute');
Sign up to request clarification or add additional context in comments.

15 Comments

How do you then access the attributes within the receiving controller?
add request class to controller method arguments (IoC container) or call static class \Request
$myAttribute = \Request::get('myAttribute');
Wow, this solution looks very clean!
You can also use $request->request->add(['myAttribute' => 'myValue']); to be able to use the magic getter shorthand $request->myAttribute
|
42

Instead of custom request parameters, you can follow the inversion-of-control pattern and use dependency injection.

In your middleware, register your Page instance:

app()->instance(Page::class, $page);

Then declare that your controller needs a Page instance:

class PagesController 
{
    protected $page;

    function __construct(Page $page) 
    {
        $this->page = $page;
    }
}

Laravel will automatically resolve the dependency and instantiate your controller with the Page instance that you bound in your middleware.

6 Comments

This is a really good idea, I went ahead and created a Service Provider then registered a Service Container. That way when needed some attributes, I would just inject the dependencies. Much cleaner and transparent. Thanks!
@ArianAcosta Please, can you elaborate an answer with your way? I mean, how to use dependency injection and how it is associated with the middleware.
@JCarlos Sure! The idea would be to have a custom Service Container class that holds as internal properties the data that you need to pass around between the middleware and the controller. If you register that Service Container as a singleton with $this->app->singleton(...) then, it will always be the same instance every time you inject it. So, essentially you would first inject it in the middleware (by simply requiring it as an argument), then put the data inside of it, and finally require it in the controller where you can access the data. See laravel.com/docs/5.4/container good luck
This is a GREAT answer ... neat! :)
Remark: in __constructor it doesn't work, because middleware loaded after controller's constructor. But you could use DI in any action of controller.
|
30

In Laravel >= 5 you can use $request->merge in the middleware.

public function handle($request, Closure $next)
{
    $request->merge(["myVar" => "1234"]);
    
    return $next($request);
}

And in the controller

public function index(Request $request)
{
    $myVar = $request->myVar;
    ...
}

3 Comments

Why would you access Request::instance() statically, rather than using $request?
This is not a good solution, it can cause unexpected side effects, check my answer below for more details: stackoverflow.com/a/73131117/8485567
I agree with @BernardWiesner, this may contaminate your request payload.
25

Laravel 5.7

// in Middleware register instance
app()->instance('myObj', $myObj);

and

// to get in controller just use the resolve helper
$myObj = resolve('myObj');

Comments

11

As mentioned in one of the comments above for laravel 5.3.x

$request->attributes->add(['key => 'value'] ); 

Doesn't work. But setting the variable like this in the middleware works

$request->attributes->set('key', 'value');

I could fetch the data using this in my controller

$request->get('key');

Comments

10

It is very simple:

Here is middleware code:

public function handle($request, Closure $next)
{

    $request->merge(array("customVar" => "abcde"));

    return $next($request);
}

and here is controller code:

$request->customVar;

1 Comment

This will cause error Expected a scalar, or an array as a 2nd argument to \"Symfony\\Component\\HttpFoundation\\InputBag::set()\", \"stdClass\" given.
6

I am sure if it was possible to pass data from a middleware to a controller then it would be in the Laravel documentation.

Have a look at this and this, it might help.

In short, you can piggy back your data on the request object which is being passed to the middleware. The Laravel authentication facade does that too.

So, in your middleware, you can have:

$request->myAttribute = "myValue";

2 Comments

Thanks @norman - thats a good solution and i didn't know you could do it...! I was questioning if i should be using middleware at this point at all, but it seems i should. The documentation does not mention anything of the sort. Thanks again
@Alex Yes, I think if its a common piece of code that executes in every controller action, it is not a bad idea to implement it as a middleware.
6

The accepted answer is the best practice and I will expand as to why.

There are multiple ways to add parameters to the request, as some suggested you can do:

$request->merge(["foo" => "bar"]);

The problem with this, is it will add your parameter to the query of the request. If later on in your code you try to get the query parameters of the url and do:

$request->query();

You will unexpectedly obtain the added parameter above that is actually not in the query of the current url, this could introduce bugs in your code, as an example if you are building some link with the full path of the current url and query string.

To avoid such a side effect its best to store custom parameters in the attributes property of the request.

$request->attributes->add(["foo" => "bar"])

Note that if you do above you CANNOT obtain the parameter by using magic methods such as:

$request->foo // Does not work, returns null!

You need to use the get() method on the request:

$request->get('foo'); // works!

Comments

2

I was able to add values to the Request-object with:

$request->attributes->set('key', 'value');

and get them back at a later point with:

$request->attributes->get('key');

This is possible because laravels Request extends symfonys Request which has the attribute "$attributes" of type ParameterBag that is intended to hold custom parameters.

I think this should be Best Practice to pass data to subsequent Middleware, Controllers or any other place where it's possible to access the Request-object.

Tested with Laravel 5.6, but probably also working with other versions.

Comments

2

If your website has cms pages which are being fetched from database and want to show their titles in the header and footer block on all pages of laravel application then use middleware. Write below code in your middleware:

namespace App\Http\Middleware;

use Closure;

use Illuminate\Support\Facades\DB;

public function handle($request, Closure $next)
{    

$data = DB::table('pages')->select('pages.id','pages.title')->where('pages.status', '1')->get();

\Illuminate\Support\Facades\View::share('cms_pages', $data);

return $next($request);

}

Then goto your header.blade.php and footer.blade.php and write below code to add links of cms pages:

<a href="{{ url('/') }}">Home</a> | 

@foreach ($cms_pages as $page) 

<a href="{{ url('page/show/'.$page->id) }}">{{ $page->title }}</a> | 

@endforeach

<a href="{{ url('contactus') }}">Contact Us</a>

Thanks a lot to all and enjoy the code :)

Comments

2

$request is the array so that we can just add value and key to the array and get the $request with this key in the controller.

$request['id'] = $id;

Comments

1

i don't speak english, so... sorry for possible errors.

You can use the IoC binding for this. In your middleware you can do this for binding $page instance:

\App::instance('mi_page_var', $page);

After, in your controller you call that instance:

$page = \App::make('mi_page_var');

The App::instance not re-instance the class, instead return the instance previusly binding.

Comments

1

You can make a method on your controller, for example

public function setSomeVariableOnMyController(array $in): void

then call it in your middleware and pass any data there like this:

$request
        ->route()
        ->getController()
        ->setSomeVariableOnMyController($anyData);

tested on laravel 9

1 Comment

Fantastic answer and, at least for my use-case, way cleaner than setting attributes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.