The Wayback Machine - https://web.archive.org/web/20230128074654/https://github.com/symfony/symfony/issues/49017
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

[Messenger] Locale context in async middleware #49017

Open
veewee opened this issue Jan 18, 2023 · 0 comments
Open

[Messenger] Locale context in async middleware #49017

veewee opened this issue Jan 18, 2023 · 0 comments

Comments

@veewee
Copy link
Contributor

veewee commented Jan 18, 2023

Description

When having a multi-locale website, a common task is to make sure the application has the correct locale to use.
We are currently using router_context for passing down the context of the router during the async rendering and sending of emails. This has one big limitatation:

Flow:

  • Given the base structure of the application is /{_locale}.
  • A forget password at /{_locale}/forget-password will trigger an email that will be handled by e.g. rabbitMQ.
  • We are currently using the router_context to pass down the HTTP router context to the messenger consumer
  • However, the RouteContextStamp is missing the parameters that are available in RequestContext of the router.
  • So if the TWIG email uses {{ url('reset-password', {token: token} }} - which should go to /{_locale}/reset-password/{token}, the queue will fail that the _locale parameter can not be found. This is because the parameters are not being passed down. (Note: If you would be using the sync queue, this will work given the information is available in the RequestContext of the router.

An easy fix for this would be to extend the RouterContextMiddleware.

Yet, it might be a better idea to (also?) have a new LocaleContextMiddleware and LocaleContextStamp.
(We currently pass down the locale information manually)
This stamp could contain the currently selected locale in HTTP request.
When running the handler, it could first register the correct locale inside the application and finally revert this information after the handler's execution (just like the router middleware does).

This would imply:

  • Setting the translator's locale
  • Setting intl default locale
  • (I might be missing something here? - feel free to append)

So my questions here are:

What do you think about both suggestions above? Is this something you would like to have in messenger?
Maybe you have a better idea to beat this issue?

Example implementations / Considerations

RouterContextMiddleware change

The router's context parameters might contain closures. For example for the expression language, an additional "_functions" key is added which contains a service locator. We could e.g. only detect scalar types for passing down to the stamp:

array_filter(
    $context->getParameters(),
    static fn (mixed $value): bool => is_scalar($value)
),

(The rest of the code will be in line with what is there.)

LocaleContextMiddleware

This middleware would be responsible for passing down the locale from the HTTP request as a stamp.
Before handling the message, it can change the locale on all required services to the one of the HTTP request.
After handling the message, it will reset to previous settings.

LocaleContextStamp

class LocaleContextStamp implements StampInterface
{
    public function __construct(
        public readonly string $locale,
    ) {
    }
}

LocaleContextMiddleware

use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;

class LocaleContextMiddleware implements MiddlewareInterface
{
    /**
     * @var iterable<int, LocaleAwareInterface>
     */
    private iterable $localeAwareServices;

    /**
     * @param iterable<int, LocaleAwareInterface> $localeAwareServices
     */
    public function __construct(
        #[TaggedIterator('kernel.locale_aware')]
        iterable $localeAwareServices
    ){
        $this->localeAwareServices = $localeAwareServices;
    }

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        if (!$envelope->last(ConsumedByWorkerStamp::class) || !$contextStamp = $envelope->last(LocaleContextStamp::class)) {
            $envelope = $envelope->with(new LocaleContextStamp(
                \Locale::getDefault()
            ));

            return $stack->next()->handle($envelope, $stack);
        }

        $currentLocale = \Locale::getDefault();
        $this->changeLocale($contextStamp->locale);

        try {
            return $stack->next()->handle($envelope, $stack);
        } finally {
            $this->changeLocale($currentLocale);
        }
    }

    private function changeLocale(string $locale): void
    {
        \Locale::setDefault($locale);
        foreach ($this->localeAwareServices as $service) {
            $service->setLocale($locale);
        }

        // optionally: set the RouterContext _locale parameter if we decide to do it right here
    }
}

(!) Note: It currently uses intl for detecting current locale. We might as well select it from the first iterable instead - since intl might be disabled?

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

No branches or pull requests

2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.