Search code examples
laravellaravel-11

Laravel dependency injection from middleware to form request


I have a multi language site and I'd like to inject the language list from the language middleware to the form request. As far as I can tell there is a service container which could be used for this purpose, but it is based on the class of the value, while here I just want to inject an array of Language models or stdClass if it comes from cache. Is there a way to inject an array from the middleware to a controller or form request?

namespace App\Http\Middleware;

use App\Models\Language;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Request as FacadesRequest;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Symfony\Component\HttpFoundation\Response;

class SetUrlLanguage
{
    public function handle(Request $request, Closure $next): Response
    {
        if (Cache::has('locales'))
            $locales = collect(json_decode(Cache::get('locales')));
        else {
            $locales = Language::all();
            Cache::add('locales', $locales->toJson());
        }
        $locales = $locales->keyBy('code');
        $codes = $locales->keys()->toArray();
        $code = FacadesRequest::segment(1);
        if (!in_array($code, $codes))
            abort(404);
        URL::defaults([
            'locale' => $code
        ]);
        View::share('locales', $locales);
        View::share('selectedLocale', $locales->get($code));
        if (App::getLocale() !== $code)
            App::setLocale($code);
        return $next($request);
    }
}

edit:

Solved it with the Config facade for now, but I am curious what is the best way to do this.


Solution

  • There are so many way through which you can do this. But 1st I want draw your attention to

    Why using Config::set(...) is not the best approach ?

    1. When you use Config::set('locales', $locales); & Config::set('selectedLocale', $locales->get($code)) , It only temporarily sets a configuration value in memory for the current request lifecycle.So basically during each request-lifecycle you set a configuration value, that may involve additional overhead.
    2. Laravel caches configuration files for performance in production environments using the php artisan config:cache command. Changes made to configuration values at runtime (e.g., using Config::set()) might not be reflected in the cached configuration.
    3. When you use this approach it is not well-scoped to the request passing through the SetUrlLanguage middleware though you have set the value of the Config in the middleware if other parts of the application are also trying to set or get configuration values, this can make your application harder to debug.

    So I think to implement the best approach and as well as to get rid of all the issue I mentioned above you need to use request-object to do the same like this.

    In your middleware do this..

    public function handle(Request $request, Closure $next): Response
    {
      ....//Do your other works//....
      // Injecting array into the request
      $languages = ['locales' => $locales, 'selectedLocale' => $locales->get($code)];
      $request->merge(['data' => $languages]);
    
      return $next($request);
    }
    

    You can access the injected array in the controller using the Request object.

    public function index(Request $request)
    {
        $data = $request->input('data');
        //$data is now the array injected by the middleware
    }
    

    If you're using a form request, you can access the array the same way.

    class CustomFormRequest extends FormRequest
    {
        public function rules()
        {
            //Accessing the array in form request
            $data = $this->input('data');
        }
    }
    

    N:B :-

    1. Injecting the array into the Request object using $request->merge() in the middleware is clean, and scoped to a single request.
    2. Other approches includes using session & Laravel’s Service Container Binding that you have also mentioned in your question. But in this particular scenario I think the above procedure is best suited as per the condition you mentioned.
    3. Please also add laravel tag to your question since this particular question focuses on laravel and it's core feature as a whole rather than it's specific version. Even before the release of laravel-11 all the features discussed here in the Q&A already existed in the other versions.