Passed
Pull Request — master (#429)
by Evgeniy
18:30 queued 14:18
created

LocaleMiddleware::process()   C

Complexity

Conditions 13
Paths 21

Size

Total Lines 48
Code Lines 32

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 32
c 2
b 0
f 0
nc 21
nop 2
dl 0
loc 48
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
    ) {
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
            $length = strlen($locale);
68
            $newPath = substr($path, $length + 1);
69
            if ($newPath === '') {
70
                $newPath = '/';
71
            }
72
            $this->translator->setLocale($locale);
73
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
74
75
            $response = $handler->handle($request);
76
            if ($this->isDefaultLocale($locale, $country) && $request->getMethod() === 'GET') {
77
                $response = $this->responseFactory->createResponse(Status::FOUND)
78
                    ->withHeader(Header::LOCATION, $newPath);
79
            }
80
            if ($this->enableSaveLocale) {
81
                $response = $this->saveLocale($locale, $response);
82
            }
83
            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, null);
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
            $locale = $matches[1];
117
            [$locale, $country] = $this->parseLocale($locale);
118
            if (isset($this->locales[$locale])) {
119
                $this->logger->info(sprintf("Locale '%s' found in URL", $locale));
120
                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
            $this->logger->info(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
132
            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
    private function isDefaultLocale(string $locale, ?string $country): bool
144
    {
145
        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
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
157
    {
158
        $this->logger->info('Saving found locale to cookies');
159
        $this->session->set($this->sessionName, $locale);
160
        $cookie = (new Cookie($this->sessionName, $locale));
161
        if ($this->cookieDuration !== null) {
162
            $cookie = $cookie->withMaxAge($this->cookieDuration);
163
        }
164
        return $cookie->addToResponse($response);
165
    }
166
167
    private function parseLocale(string $locale): array
168
    {
169
        if (strpos($locale, '-') !== false) {
170
            return explode('-', $locale, 2);
171
        }
172
        if (isset($this->locales[$locale]) && strpos($this->locales[$locale], '-') !== false) {
173
            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