Passed
Pull Request — master (#421)
by Aleksei
05:39
created

LocaleMiddleware::process()   C

Complexity

Conditions 13
Paths 21

Size

Total Lines 48
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 15.407

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 13
eloc 32
nc 21
nop 2
dl 0
loc 48
ccs 25
cts 33
cp 0.7576
crap 15.407
rs 6.6166
c 2
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 = false;
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 20
    public function __construct(
40
        TranslatorInterface $translator,
41
        UrlGeneratorInterface $urlGenerator,
42
        SessionInterface $session,
43
        LoggerInterface $logger,
44
        ResponseFactoryInterface $responseFactory,
45
        array $locales = []
46
    ) {
47 20
        $this->translator = $translator;
48 20
        $this->urlGenerator = $urlGenerator;
49 20
        $this->session = $session;
50 20
        $this->logger = $logger;
51 20
        $this->responseFactory = $responseFactory;
52 20
        $this->locales = $locales;
53 20
        $this->cookieDuration = new DateInterval('P30D');
54 20
    }
55
56 20
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
57
    {
58 20
        if ($this->locales === []) {
59
            return $handler->handle($request);
60
        }
61
62 20
        $uri = $request->getUri();
63 20
        $path = $uri->getPath();
64 20
        [$locale, $country] = $this->getLocaleFromPath($path);
65
66 20
        if ($locale !== null) {
67 12
            $length = strlen($locale);
68 12
            $newPath = substr($path, $length + 1);
69 12
            if ($newPath === '') {
70
                $newPath = '/';
71
            }
72 12
            $this->translator->setLocale($locale);
73 12
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
74
75 12
            $response = $handler->handle($request);
76 12
            if ($this->isDefaultLocale($locale, $country) && $request->getMethod() === 'GET') {
77 4
                $response = $this->responseFactory->createResponse(Status::FOUND)
78 4
                    ->withHeader(Header::LOCATION, $newPath);
79
            }
80 12
            if ($this->enableSaveLocale) {
81 12
                $response = $this->saveLocale($locale, $response);
82
            }
83 12
            return $response;
84
        }
85 20
        if ($this->enableSaveLocale) {
86 20
            [$locale, $country] = $this->getLocaleFromRequest($request);
87
        }
88 20
        if ($locale === null && $this->enableDetectLocale) {
89
            [$locale, $country] = $this->detectLocale($request);
90
        }
91 20
        if ($locale === null || $this->isDefaultLocale($locale, $country)) {
92 20
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, $this->defaultLocale);
93 20
            $request = $request->withUri($uri->withPath('/' . $this->defaultLocale . $path));
94 20
            return $handler->handle($request);
95
        }
96
        $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
97
98
        if ($request->getMethod() === 'GET') {
99
            return $this->responseFactory->createResponse(Status::FOUND)
100
                ->withHeader(Header::LOCATION, '/' . $locale . $path);
101
        }
102
103
        return $handler->handle($request);
104
    }
105
106 20
    private function getLocaleFromPath(string $path): array
107
    {
108 20
        $parts = [];
109 20
        foreach ($this->locales as $code => $locale) {
110 20
            $lang = is_string($code) ? $code : $locale;
111 20
            $parts[] = $lang;
112
        }
113
114 20
        $pattern = implode('|', $parts);
115 20
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
116 12
            $locale = $matches[1];
117 12
            [$locale, $country] = $this->parseLocale($locale);
118 12
            if (isset($this->locales[$locale])) {
119 12
                $this->logger->info(sprintf("Locale '%s' found in URL", $locale));
120 12
                return [$locale, $country];
121
            }
122
        }
123 20
        return [null, null];
124
    }
125
126 20
    private function getLocaleFromRequest(ServerRequestInterface $request): array
127
    {
128 20
        $cookies = $request->getCookieParams();
129 20
        $queryParameters = $request->getQueryParams();
130 20
        if (isset($cookies[$this->sessionName])) {
131 4
            $this->logger->info(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
132 4
            return $this->parseLocale($cookies[$this->sessionName]);
133
        }
134 20
        if (isset($queryParameters[$this->queryParameterName])) {
135
            $this->logger->info(
136
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
137
            );
138
            return $this->parseLocale($queryParameters[$this->queryParameterName]);
139
        }
140 20
        return [null, null];
141
    }
142
143 12
    private function isDefaultLocale(string $locale, ?string $country): bool
144
    {
145 12
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
146
    }
147
148
    private function detectLocale(ServerRequestInterface $request): array
149
    {
150
        foreach ($request->getHeader(Header::ACCEPT_LANGUAGE) as $language) {
151
            return $this->parseLocale($language);
152
        }
153
        return [null, null];
154
    }
155
156 12
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
157
    {
158 12
        $this->logger->info('Saving found locale to cookies');
159 12
        $this->session->set($this->sessionName, $locale);
160 12
        $cookie = (new Cookie($this->sessionName, $locale));
161 12
        if ($this->cookieDuration !== null) {
162 12
            $cookie = $cookie->withMaxAge($this->cookieDuration);
163
        }
164 12
        return $cookie->addToResponse($response);
165
    }
166
167 12
    private function parseLocale(string $locale): array
168
    {
169 12
        if (strpos($locale, '-') !== false) {
170
            return explode('-', $locale, 2);
171
        }
172 12
        if (isset($this->locales[$locale]) && strpos($this->locales[$locale], '-') !== false) {
173 12
            return explode('-', $this->locales[$locale], 2);
174
        }
175
        return [$locale, null];
176
    }
177
178
    public function withLocales(array $locales): self
179
    {
180
        $new = clone $this;
181
        $new->locales = $locales;
182
        return $new;
183
    }
184
185
    public function withDefaultLocale(string $defaultLocale): self
186
    {
187
        $new = clone $this;
188
        $new->defaultLocale = $defaultLocale;
189
        return $new;
190
    }
191
192
    public function withQueryParameterName(string $queryParameterName): self
193
    {
194
        $new = clone $this;
195
        $new->queryParameterName = $queryParameterName;
196
        return $new;
197
    }
198
199
    public function withSessionName(string $sessionName): self
200
    {
201
        $new = clone $this;
202
        $new->sessionName = $sessionName;
203
        return $new;
204
    }
205
206
    public function withEnableSaveLocale(bool $enableSaveLocale): self
207
    {
208
        $new = clone $this;
209
        $new->enableDetectLocale = $enableSaveLocale;
210
        return $new;
211
    }
212
213
    public function withEnableDetectLocale(bool $enableDetectLocale): self
214
    {
215
        $new = clone $this;
216
        $new->enableDetectLocale = $enableDetectLocale;
217
        return $new;
218
    }
219
}
220