Test Failed
Push — master ( 0fa9b8...148ab9 )
by Alexander
04:58 queued 01:58
created

LocaleMiddleware::process()   C

Complexity

Conditions 13
Paths 21

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 50.6287

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