Passed
Pull Request — master (#356)
by Rustam
05:15
created

LocaleMiddleware::getLocaleFromRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.8449

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 3
eloc 10
c 1
b 1
f 0
nc 3
nop 1
dl 0
loc 15
ccs 6
cts 11
cp 0.5455
crap 3.8449
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Middleware;
6
7
use DateInterval;
8
use Psr\Http\Message\ResponseFactoryInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Psr\Log\LoggerInterface;
14
use Yiisoft\Cookies\Cookie;
15
use Yiisoft\Http\Header;
16
use Yiisoft\Http\Status;
17
use Yiisoft\Router\UrlGeneratorInterface;
18
use Yiisoft\Session\SessionInterface;
19
use Yiisoft\Translator\TranslatorInterface;
20
21
final class LocaleMiddleware implements MiddlewareInterface
22
{
23
    private const DEFAULT_LOCALE = 'en';
24
    private const DEFAULT_LOCALE_NAME = 'language';
25
26
    private TranslatorInterface $translator;
27
    private UrlGeneratorInterface $urlGenerator;
28
    private SessionInterface $session;
29
    private ResponseFactoryInterface $responseFactory;
30
    private LoggerInterface $logger;
31
    private array $locales;
32
    private bool $enableSaveLocale = true;
33
    private bool $enableDetectLocale = true;
34
    private string $defaultLocale = self::DEFAULT_LOCALE;
35
    private string $queryParameterName = self::DEFAULT_LOCALE_NAME;
36
    private string $sessionName = self::DEFAULT_LOCALE_NAME;
37
    private ?DateInterval $cookieDuration;
38
39 10
    public function __construct(
40
        TranslatorInterface $translator,
41
        UrlGeneratorInterface $urlGenerator,
42
        SessionInterface $session,
43
        LoggerInterface $logger,
44
        ResponseFactoryInterface $responseFactory,
45
        array $locales = []
46
    ) {
47 10
        $this->translator = $translator;
48 10
        $this->urlGenerator = $urlGenerator;
49 10
        $this->session = $session;
50 10
        $this->logger = $logger;
51 10
        $this->responseFactory = $responseFactory;
52 10
        $this->locales = $locales;
53 10
        $this->cookieDuration = new DateInterval('P30D');
54 10
    }
55
56 10
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
57
    {
58 10
        if ($this->locales === []) {
59
            return $handler->handle($request);
60
        }
61
62 10
        $uri = $request->getUri();
63 10
        $path = $uri->getPath();
64 10
        [$locale, $country] = $this->getLocaleFromPath($path);
65
66 10
        if ($locale !== null) {
67
            $length = strlen($locale);
68
            $newPath = substr($path, $length + 1);
69
            if ($newPath === '') {
70
                $newPath = '/';
71
            }
72
            $request = $request->withUri($uri->withPath($newPath));
73
            $this->translator->setLocale($locale);
74
            $this->urlGenerator->setUriPrefix('/' . $locale);
75
76
            $response = $handler->handle($request);
77
            if ($this->isDefaultLocale($locale, $country)) {
78
                $response = $this->responseFactory->createResponse(Status::FOUND)
79
                    ->withHeader(Header::LOCATION, $newPath);
80
            }
81
            if ($this->enableSaveLocale) {
82
                $response = $this->saveLocale($locale, $response);
83
            }
84
            return $response;
85
        }
86 10
        if ($this->enableSaveLocale) {
87 10
            $locale = $this->getLocaleFromRequest($request);
88
        }
89 10
        if ($locale === null && $this->enableDetectLocale) {
90
            // TODO: detect locale from headers
91
        }
92 10
        if ($locale === null || $this->isDefaultLocale($locale, $country)) {
93 10
            return $handler->handle($request);
94
        }
95
        return $this->responseFactory->createResponse(Status::FOUND)
96
            ->withHeader(Header::LOCATION, '/' . $locale . rtrim($path, '/'));
97
    }
98
99 10
    private function getLocaleFromPath(string $path): array
100
    {
101 10
        $parts = [];
102 10
        foreach ($this->locales as $code => $locale) {
103 10
            $lang = is_string($code) ? $code : $locale;
104 10
            $parts[] = $lang;
105
        }
106
107 10
        $pattern = implode('|', $parts);
108 10
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
109
            $locale = $matches[1];
110
            $country = null;
111
            if (strpos($locale, '-') !== false) {
112
                [$locale, $country] = explode('-', $locale, 2);
113
            }
114
            if (isset($this->locales[$locale])) {
115
                $this->logger->info(sprintf("Locale '%s' found in URL", $locale));
116
                return [$locale, $country];
117
            }
118
        }
119 10
        return [null, null];
120
    }
121
122 10
    private function getLocaleFromRequest(ServerRequestInterface $request)
123
    {
124 10
        $cookies = $request->getCookieParams();
125 10
        $queryParameters = $request->getQueryParams();
126 10
        if (isset($cookies[$this->sessionName])) {
127
            $this->logger->info(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
128
            return $cookies[$this->sessionName];
129
        }
130 10
        if (isset($queryParameters[$this->queryParameterName])) {
131
            $this->logger->info(
132
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
133
            );
134
            return $queryParameters[$this->queryParameterName];
135
        }
136 10
        return null;
137
    }
138
139
    private function isDefaultLocale(string $locale, ?string $country): bool
140
    {
141
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
142
    }
143
144
    private function detectLocale(ServerRequestInterface $request)
0 ignored issues
show
Unused Code introduced by
The method detectLocale() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

144
    private function detectLocale(/** @scrutinizer ignore-unused */ ServerRequestInterface $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
    }
147
148
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
149
    {
150
        $this->logger->info('Saving found locale to cookies');
151
        $this->session->set($this->sessionName, $locale);
152
        $cookie = (new Cookie($this->sessionName, $locale));
153
        if ($this->cookieDuration !== null) {
154
            $cookie = $cookie->withMaxAge($this->cookieDuration);
155
        }
156
        return $cookie->addToResponse($response);
157
    }
158
}
159