Passed
Pull Request — master (#10)
by Rustam
02:11
created

Locale   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 213
Duplicated Lines 0 %

Test Coverage

Coverage 95.65%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 108
c 2
b 0
f 0
dl 0
loc 213
ccs 110
cts 115
cp 0.9565
rs 8.96
wmc 43

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A withCookieSecure() 0 5 1
A withEnableSaveLocale() 0 5 1
A saveLocale() 0 9 2
A getLocaleFromRequest() 0 15 3
A withQueryParameterName() 0 5 1
A withDefaultLocale() 0 5 1
A parseLocale() 0 13 5
A detectLocale() 0 6 2
B process() 0 42 11
A withSessionName() 0 5 1
A applyLocaleFromPath() 0 20 4
A withLocales() 0 5 1
A getLocaleFromPath() 0 18 5
A withEnableDetectLocale() 0 5 1
A isDefaultLocale() 0 3 3

How to fix   Complexity   

Complex Class

Complex classes like Locale often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Locale, and based on these observations, apply Extract Interface, too.

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