request-model icon indicating copy to clipboard operation
request-model copied to clipboard

Runtime formatter with translator

Open razonyang opened this issue 3 years ago • 6 comments

Hi, I would like to format the error message with runtime Yii translator, a middleware that store the translator instance into request attributes, according to the Accept-Language header or URL query parameters.

I extend the RequestModel class like this:

<?php
// Custom RequestModel
abstract class RequestModel extends BaseModel implements RulesProviderInterface
{
    private ?TranslatorInterface $translator = null;

    public function __construct(
        private TranslatorServiceInterface $translatorService,
    )
    {
    }

    public function getTranslator(): TranslatorInterface
    {
        if (!$this->translator) {
            $this->translator = $this->translatorService->fromAttributes($this->getRequestData()['attributes']);
        }

        return $this->translator;
    }
}
<?php
// Custom Formatter
class Formatter implements FormatterInterface
{
    public function __construct(
        private TranslatorInterface $translator
    )
    {
    }

    public function format(string $message, array $parameters = []): string
    {
        return $this->translator->translate($message, $parameters);
    }
}

Is there any way to hack the runtime formatter into rules? The formatter is belongs to RuleHandler, not Rule, so I could not do it in the getRules function.

public function getRules(): array
{
    return [
        'body.login' => [
            new Required(),
        ],
        'body.password' => [
            new Required(),
            new HasLength(6, 16),
        ],
    ];
}

razonyang avatar Sep 10 '22 05:09 razonyang

Why wouldn't you use setLocale()?

xepozz avatar Sep 10 '22 15:09 xepozz

Why wouldn't you use setLocale()?

Sorry, I didn't notice that, is this method can be used in RoadRunner and Swoole safely?

razonyang avatar Sep 10 '22 17:09 razonyang

Why wouldn't you use setLocale()?

Sorry, I didn't notice that, is this method can be used in RoadRunner and Swoole safely?

I haven't checked it, but seems it may occur problems. @yiisoft/yii3 has anyone checked this case?

xepozz avatar Sep 10 '22 17:09 xepozz

In RR better use withLocale https://github.com/yiisoft/translator#get-a-new-translator-instance-with-a-locale-to-be-used-by-default-in-case-locale-isnt-specified-explicitly

darkdef avatar Sep 10 '22 18:09 darkdef

Hi @xepozz, I just tested it in RR and Swoole, the RR seems fine with the setLocale, but Swoole does not.

I guess requests are blocking in RR single worker, the next request won't be handled until the current request was done. And Swoole Coroutine won't blocking requests, if the current request is suspend, other requests will be handled. The DI resetter isn't useful in this case.

I wrote a simple test case as the following.

image

The response will be affected by other requests.

<?php
// Middleware
class TranslatorMiddleware implements MiddlewareInterface
{
    public function __construct(
        private TranslatorInterface $translator,
    ) {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $this->translator->setLocale($request->getQueryParams()['locale']);
        sleep(5);
        return $handler->handle($request);
    }
}
<?php
// Controller
class InfoController
{
    public const TITLE = 'New Tab API';

    public const VERSION = '1.0.0';

    public function index(DataResponseFactoryInterface $responseFactory, TranslatorInterface $translator): ResponseInterface
    {
        return $responseFactory->createResponse([
            'title' => self::TITLE,
            'version' => self::VERSION,
            't' => $translator->translate('home'),
        ]);
    }
}

As the image shown, the Application Runner does invoke the StateResetter::reset method to reset the locale per request.

<?php
    private function afterRespond(
        Application $application,
        ContainerInterface $container,
        ?ResponseInterface $response,
    ): void {
        $application->afterEmit($response);
        /** @psalm-suppress MixedMethodCall */
        $container
            ->get(StateResetter::class)
            ->reset(); // We should reset the state of such services every request.
        var_dump('reset service state');
        gc_collect_cycles();
    }

I'm not sure am I test it in a right way.

The HTTP server is running with Swoole 5 Coroutine

razonyang avatar Sep 11 '22 06:09 razonyang

Hi, I forked the yiisoft/app-api and created the swoole branch for checking this case.

Changes can be found at https://github.com/razonyang/yii-app-demo/commit/13300e05cafe016785e439c92ef81f36308c7e76 and https://github.com/razonyang/yii-app-demo/commit/d1e9ccb58e56b18dfef27cb4b7e1a53533429b7e Swoole runnner adapter https://github.com/razonyang/yii-runner-swoole

Steps for testing:

$ git clone -b swoole [email protected]:razonyang/yii-app-demo.git

$ cd yii-app-demo

$ docker-compose up --build -d php

$ docker-compose exec php bash
root@***:/app# composer install
root@***:/app# exit

$ docker-compose up --build -d swoole

And then make some concurrent requests.

$ curl "http://localhost:9501?locale=zh-CN"

$ curl "http://localhost:9501?locale=en-US"

I think it would be better if we are able to use withLocale instead of setLocale, and then inject the runtime translator into Rule Handlers.

razonyang avatar Sep 14 '22 18:09 razonyang