Passed
Pull Request — master (#383)
by Wilmer
03:15
created

LocaleMiddleware::process()   B

Complexity

Conditions 11
Paths 17

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 34.6818

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 30
c 1
b 0
f 0
nc 17
nop 2
dl 0
loc 43
ccs 13
cts 31
cp 0.4194
crap 34.6818
rs 7.3166

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