Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Laravel Octane #780

Open
ju5t opened this issue Apr 6, 2021 · 18 comments
Open

Support for Laravel Octane #780

ju5t opened this issue Apr 6, 2021 · 18 comments
Labels

Comments

@ju5t
Copy link

ju5t commented Apr 6, 2021

Laravel has recently released a beta version of Laravel Octane.

Because a lot of things are cached, the list of routes doesn't update between requests. This means all prefixed routes do not exist, as Octane didn't make them available. Although Octane is a beta, this caching is unlikely to change.

It would be awesome if Octane support can be added.

edit: See laravel/octane#113 for a workaround/solution. For me this still caused some translation issues, which I will try to demonstrate in more detail later, unless someone beats me to it :)

@pmochine
Copy link

@ju5t which kind of translation issues do you get? Just beginning to use laravel-localization for a side project

@ju5t
Copy link
Author

ju5t commented Jun 26, 2021

@pmochine most details are in laravel/octane#113.

We ended up writing our own simplified localisation support. This was much easier for us. It isn't something we can share at this point in time, as it's not packaged up nicely.

We haven't moved onto Octane though. We're using Forge and you can't switch; you have to migrate.

@abishekrsrikaanth
Copy link

can't seem to get this to work on octane, tried what is suggested here laravel/octane#113. No luck

@omarherri
Copy link

Did someone managed to resolve this guys ??

@mcolominas
Copy link

Hello, I have my own package very similar to this one, and I adapted it to laravel octane, therefore, adapting this package to octane has not been very difficult, here I tell you how to make it work in octane. Routes are always required to be cached.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Mcamara\LaravelLocalization\LaravelLocalization;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //Remove singleton
        $this->app->offsetUnset(LaravelLocalization::class);

        //Add bind (Necessary for each cycle to restart)
        $this->app->bind(LaravelLocalization::class, function () {
            return new LaravelLocalization();
        });
    }
}
namespace App\Listeners;

use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class ReloadRoutes
{
    private static $_last_locale = null;
    /**
     * Handle the event.
     *
     * @param  mixed  $event
     * @return void
     */
    public function handle($event): void
    {
        $locale = LaravelLocalization::setLocale();

        $path = $this->makeLocaleRoutesPath($event->sandbox, $locale);
        if (self::$_last_locale != $locale && file_exists($path) && is_file($path)) {
            self::$_last_locale = $locale;
            include $path;
        }
    }

    /**
     * @param Application $app
     * @param string $locale
     * @return string
     */
    protected function makeLocaleRoutesPath($app, $locale = '')
    {
        $path = $app->getCachedRoutesPath();

        if (!$locale) {
            return $path;
        }

        return substr($path, 0, -4) . '_' . $locale . '.php';
    }
}

In config/octane.php add:

...
'listeners' => [
        ...
        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            App\Listeners\ReloadRoutes::class,
        ],
        ...
],
...

With this it should work, but I have to do more tests.

@jangaraev
Copy link

Hi gents,

I've also managed to partially make it work with Octane. My way is as per described below:

Since the service provider registers its facade as a singleton we need to switch it to a way proposed by Octane's documentation:

What I did?

  1. disabled an auto-discover for the package in composer.json:
    "extra": {
        "laravel": {
            "dont-discover": [
                "mcamara/laravel-localization"
            ]
        }
    },

  1. created my own service provider for that (to have a better control over this as I'm still didn't make it work 100%)
namespace App\Providers;

use Mcamara\LaravelLocalization\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;

class LocalizationServiceProvider extends LaravelLocalizationServiceProvider
{
    protected function registerBindings()
    {
        $this->app->bind(LaravelLocalization::class, fn () => new LaravelLocalization());
        $this->app->alias(LaravelLocalization::class, 'laravellocalization');
    }
}
  1. registered it in the appropriate config in app.php
        /*
         * Application Service Providers...
         */
        App\Providers\LocalizationServiceProvider::class,
        ...
        App\Providers\AppServiceProvider::class,
  1. manually registered there as well the facade as its often referenced in blade files
    'aliases' => [
        ...
        'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class,
    ]

@jangaraev
Copy link

I'm still struggling with routes and the homepage without locale chunk doesn't work even with the solution of foreach (LaravelLocalization::getSupportedLocales() ...

Will keep you guys updated if I achieve any meaningful results.

@jangaraev
Copy link

jangaraev commented Mar 18, 2022

Yeah guys, I've managed to make it work completely!

Once setup Laravel Octane via Roadrunner, I discovered several issues with this package:

  • locale from URL isn't recognized
  • homepage doesn't open when there is no locale chunk
  • links get generated using another locale
  • translatable routes are not recognized and built improperly

Here is my solution, it fixes all the issues above:

  1. Add a listener to hook into Octane's RequestReceived event (thanks to @mcolominas):
namespace App\Listeners;

use Illuminate\Foundation\Application;
use Laravel\Octane\Events\RequestReceived;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class LoadLocalizedRoutesCache
{
    private static $lastLocale;


    public function handle(RequestReceived $event): void
    {
        // passing request segment is crucial because the package doesn't
        // know the current locale as it was instantiated in service provider

        // there is also an option to don't pass the request segment in case
        // you don't use translatable routes (transRoute() in web.php) in your project
        // in this case the package will correctly resolve the locale and you
        // don't need to pass the 3rd param when binding in service provider
        $locale = LaravelLocalization::setLocale($event->request->segment(1));

        $path = $this->makeLocaleRoutesPath($event->sandbox, $locale);

        if (self::$lastLocale != $locale && is_file($path)) {
            self::$lastLocale = $locale;
            include $path;
        }
    }

    protected function makeLocaleRoutesPath(Application $app, $locale = ''): string
    {
        $path = $app->getCachedRoutesPath();

        if (!$locale) {
            return $path;
        }

        return substr($path, 0, -4) . '_' . $locale . '.php';
    }
}
  1. Reference the listener in Octane's config file:
    'listeners' => [
        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            \App\Listeners\LoadLocalizedRoutesCache::class
        ],
  1. disabled an auto-discover for the package in composer.json:
    "extra": {
        "laravel": {
            "dont-discover": [
                "mcamara/laravel-localization"
            ]
        }
    },
  1. created my own service provider instead of disabled native one in composer.json:
namespace App\Providers;

use Mcamara\LaravelLocalization\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;

class LocalizationServiceProvider extends LaravelLocalizationServiceProvider
{
    protected function registerBindings()
    {
        $fn = fn () => new LaravelLocalization();

        // the conditional check below is important
        // when you do caching routes via `php artisan route:trans:cache` if binding
        // via `bind` used you will get incorrect serialized translated routes in cache
        // files and that's why you'll get broken translatable route URLs in UI

        // again, if you don't use translatable routes, you may get rid of this check
        // and leave only 'bind()' here

        // the 3rd parameter is important to be passed to 'bind'
        // otherwise the package's instance will be instantiated every time
        // you reference it and it won't get proper data for 'serialized translatable routes'
        // class variable, this will make impossible to use translatable routes properly
        // but oveall the package will still work stable except generating the same URLs
        // for translatable routes independently of locale

        if ($this->runningInOctane()) {
            $this->app->bind(LaravelLocalization::class, $fn, true);
        } else {
            $this->app->singleton(LaravelLocalization::class, $fn);
        }

        $this->app->alias(LaravelLocalization::class, 'laravellocalization');
    }

    private function runningInOctane(): bool
    {
        return !$this->app->runningInConsole() && env('LARAVEL_OCTANE');
    }
}
  1. registered it in the appropriate config in app.php
        /*
         * Application Service Providers...
         */
        App\Providers\LocalizationServiceProvider::class,
        ...
        App\Providers\AppServiceProvider::class,
  1. manually registered there as well the facade as its often referenced in blade files:
    'aliases' => [
        ...
        'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class,
    ]

My project is a typical Laravel project:

  • Laravel 8.83
  • Laravel Nova 3
  • LaravelLocalization 1.7
  • Blade + a bit of VueJS on frontend

We use several translatable routes in the project. Most of problems get from there. Basically adapting the package appeared not so hard, but making translatable routes work made me a bit nervous :-)

Notes:

  1. You don't need to touch your routes at all, no need to foreach (...) over locales as described in posts above.
  2. It's important to use routes cache there (php artisan route:trans:cache)

@jangaraev
Copy link

@mcamara , can I prepare a PR with those changes?

@mcamara
Copy link
Owner

mcamara commented Mar 18, 2022

@jangaraev please go ahead, we will really appreciate it

@taai
Copy link

taai commented Jun 2, 2022

can I prepare a PR with those changes?

@jangaraev Are you still planning to make a PR?

@ilyasozkurt
Copy link

@jangaraev you've saved my day dude. Thank you so much for the great solution :)

@jangaraev
Copy link

@jangaraev Are you still planning to make a PR?

sorry, was sick for a long time. yep, in a week will try to propose something there.

jangaraev added a commit to jangaraev/laravel-localization that referenced this issue Jun 21, 2022
1. binding in service provider changed to a way proposed by Laravel Octane in case if we detect it
2. introduced a listener to hook into Octane's RequestReceived event

Refs: mcamara#780
@parallels999
Copy link

@jangaraev please use markdown correctly for highlight ```php, thanks

@korridor
Copy link

Is anybody using one of the workarounds described in this issue and everything still works? I just tested both and I had problems with all of them. Just wondering if I did something wrong or if this workaround just don't work anymore.

@vic-pic
Copy link

vic-pic commented May 28, 2024

Hi @jangaraev,
I am trying your workaround, but it doesn't work. The octane route cache file for the selected locale was not created, so the listener doesn't include it and octane returns 404.

I'm using Laravel 10 and PHP 8.2

How can I fix this issue?

Thanks, Vincenzo

@korridor
Copy link

@vic-pic I already wrote in the PR #833, but not in here maybe this helps you as well:

I tried the solution and it didn't solve the problem for me. I also looked into the code of the solution, and it seems a bit hacky, but I'm not that deep into this plugin.

Since this broke my production environment and I needed a multi-language solution fast, I uninstalled this plugin and used this instead: https://github.com/codezero-be/laravel-localized-routes

Worked with Octane out of the box and the migration process was surprisingly easy. I think it is also nice that route:cache works normally there.

@ilyasozkurt
Copy link

ilyasozkurt commented Jun 6, 2024

Hello guys,

Here's my workaround. First of all you need to understand sandbox is an isolated instance of your app. Whenever you have any request one of these instances are getting handle the request.

So, it means that you need to load localed routes whenever you have request. Because you do not know which sandboxed instance of your app is going to handle "the" request and what language is currently adjusted for that isolated instance.

Here's my LoadLocalizedRoutesCache

namespace App\Listeners;

use Barryvdh\Debugbar\LaravelDebugbar;
use Illuminate\Foundation\Application;
use Laravel\Octane\Events\RequestReceived;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class LoadLocalizedRoutesCache
{

    /**
     * Handle request received event
     *
     * @param RequestReceived $event
     * @return void
     */
    public function handle(RequestReceived $event): void
    {

        // Fix routes
        $this->fixRoutes($event);

        // Fix debugbar
        $this->fixDebugbar($event->sandbox);

    }

    /**
     * Create the path to the locale routes file
     *
     * @param Application $sandbox
     * @param string $locale
     * @return string
     */
    protected function makeLocaleRoutesPath(Application $sandbox, string $locale = 'en'): string
    {

        // Get the path to the cached routes file
        $path = $sandbox->getCachedRoutesPath();

        // Return the path to the locale routes file
        return substr($path, 0, -4) . '_' . $locale . '.php';

    }

    /**
     * @param Application $sandbox
     * @return void
     */
    protected function fixDebugbar(Application $sandbox): void
    {

        $sandbox->singleton(LaravelDebugbar::class, function ($sandbox) {
            return new LaravelDebugbar($sandbox);
        });

    }

    /**
     * @param RequestReceived $event
     * @return void
     */
    protected function fixRoutes(RequestReceived $event): void
    {

        // Get the sandbox
        $sandbox = $event->sandbox;

        // Get default locale
        $defaultLocale = LaravelLocalization::getDefaultLocale();

        // Get the locale code from the request
        $currentCode = $event->request->segment(1, $defaultLocale);

        // Set the locale
        $sandbox->setLocale($currentCode);
        LaravelLocalization::setLocale($currentCode);

        // Get the path to the locale routes file
        $path = $this->makeLocaleRoutesPath($sandbox, $currentCode);

        // If the locale the file exists, include it
        if (is_file($path)) {
            include $path;
        }

        // Fix home route not found, this is required for auto redirection
        $sandbox['router']->addRoute('GET', '/', function () use ($currentCode) {
            return redirect(LaravelLocalization::localizeUrl('/', $currentCode));
        });

    }

}

You need to add this event handle to octane.php like below.

image

I hope this helps to you too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests