Test Failed
Pull Request — master (#476)
by
unknown
03:40
created

LocaleMiddleware::process()   C

Complexity

Conditions 13
Paths 21

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 26.3058

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 13
eloc 34
c 2
b 0
f 0
nc 21
nop 2
dl 0
loc 50
ccs 20
cts 35
cp 0.5714
crap 26.3058
rs 6.6166

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