Passed
Pull Request — master (#42)
by Rustam
02:45
created

Locale::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 10
dl 0
loc 13
ccs 2
cts 2
cp 1
crap 1
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Aliases\Aliases;
15
use Yiisoft\Cookies\Cookie;
16
use Yiisoft\Http\Header;
17
use Yiisoft\Http\Method;
18
use Yiisoft\Http\Status;
19
use Yiisoft\Router\UrlGeneratorInterface;
20
use Yiisoft\Session\SessionInterface;
21
use Yiisoft\Strings\WildcardPattern;
22
use Yiisoft\Translator\TranslatorInterface;
23
24
final class Locale implements MiddlewareInterface
25
{
26
    private const DEFAULT_LOCALE = 'en';
27
    private const DEFAULT_LOCALE_NAME = '_language';
28
29
    private bool $enableSaveLocale = true;
30
    private bool $enableDetectLocale = false;
31
    private string $defaultLocale = self::DEFAULT_LOCALE;
32
    private string $queryParameterName = self::DEFAULT_LOCALE_NAME;
33
    private string $sessionName = self::DEFAULT_LOCALE_NAME;
34
    private ?DateInterval $cookieDuration;
35
36
    /**
37
     * @param array<array-key, string> $locales
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, string>.
Loading history...
38
     * @param string[] $ignoredRequests
39
     */
40 16
    public function __construct(
41
        private TranslatorInterface $translator,
42
        private UrlGeneratorInterface $urlGenerator,
43
        private SessionInterface $session,
44
        private Aliases $aliases,
45
        private LoggerInterface $logger,
46
        private ResponseFactoryInterface $responseFactory,
47
        private array $locales = [],
48
        private array $ignoredRequests = [],
49
        private bool $cookieSecure = false,
50
        private string $baseUrlAlias = '@baseUrl',
51
    ) {
52 16
        $this->cookieDuration = new DateInterval('P30D');
53
    }
54
55 15
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
56
    {
57 15
        if ($this->locales === []) {
58 1
            return $handler->handle($request);
59
        }
60
61 14
        $uri = $request->getUri();
62 14
        $path = $uri->getPath();
63 14
        $query = $uri->getQuery();
64
65 14
        [$locale, $country] = $this->getLocaleFromPath($path);
66
67 14
        if ($locale !== null) {
68 8
            $this->translator->setLocale($locale);
69 8
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
70
71 8
            $response = $handler->handle($request);
72 8
            $newPath = null;
73 8
            if ($this->isDefaultLocale($locale, $country) && $request->getMethod() === Method::GET) {
74 3
                $length = strlen($locale);
75 3
                $newPath = substr($path, $length + 1);
76
            }
77 8
            return $this->applyLocaleFromPath($locale, $response, $query, $newPath);
78
        }
79 6
        if ($this->enableSaveLocale) {
80 6
            [$locale, $country] = $this->getLocaleFromRequest($request);
81
        }
82 6
        if ($locale === null && $this->enableDetectLocale) {
83 2
            [$locale, $country] = $this->detectLocale($request);
84
        }
85 6
        if ($locale === null || $this->isDefaultLocale($locale, $country) || $this->isRequestIgnored($request)) {
86 2
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, null);
87 2
            $request = $request->withUri($uri->withPath('/' . $this->defaultLocale . $path));
88 2
            return $handler->handle($request);
89
        }
90
91 4
        $this->translator->setLocale($locale);
92 4
        $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
93
94 4
        if ($request->getMethod() === Method::GET) {
95 3
            $location = rtrim($this->aliases->get($this->baseUrlAlias), '/') . '/'
96 3
                . $locale . $path . ($query !== '' ? '?' . $query : '');
97 3
            return $this->responseFactory
98 3
                ->createResponse(Status::FOUND)
99 3
                ->withHeader(Header::LOCATION, $location);
100
        }
101
102
103 1
        return $handler->handle($request);
104
    }
105
106 8
    private function applyLocaleFromPath(
107
        string $locale,
108
        ResponseInterface $response,
109
        string $query,
110
        ?string $newPath = null,
111
    ): ResponseInterface {
112 8
        if ($newPath === '') {
113 1
            $newPath = '/';
114
        }
115
116 8
        if ($newPath !== null) {
117 3
            $location = rtrim($this->aliases->get($this->baseUrlAlias), '/')
118 3
                . $newPath . ($query !== '' ? '?' . $query : '');
119 3
            $response = $this->responseFactory
120 3
                ->createResponse(Status::FOUND)
121 3
                ->withHeader(Header::LOCATION, $location);
122
        }
123 8
        if ($this->enableSaveLocale) {
124 8
            $response = $this->saveLocale($locale, $response);
125
        }
126 8
        return $response;
127
    }
128
129
    /**
130
     * @return array{0:string|null, 1:string|null}
131
     */
132 14
    private function getLocaleFromPath(string $path): array
133
    {
134 14
        $parts = [];
135 14
        foreach ($this->locales as $code => $locale) {
136 14
            $lang = is_string($code) ? $code : $locale;
137 14
            $parts[] = $lang;
138
        }
139
140 14
        $pattern = implode('|', $parts);
141 14
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
142 8
            $matchedLocale = $matches[1];
143 8
            [$locale, $country] = $this->parseLocale($matchedLocale);
144 8
            if (isset($this->locales[$locale]) || in_array($matchedLocale, $this->locales, true)) {
145 8
                $this->logger->debug(sprintf("Locale '%s' found in URL", $locale));
146 8
                return [$locale, $country];
147
            }
148
        }
149 6
        return [null, null];
150
    }
151
152
    /**
153
     * @return array{0:string|null, 1:string|null}
154
     */
155 6
    private function getLocaleFromRequest(ServerRequestInterface $request): array
156
    {
157
        /** @var array<string, string> $cookies */
158 6
        $cookies = $request->getCookieParams();
159 6
        if (isset($cookies[$this->sessionName])) {
160 1
            $this->logger->debug(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
161 1
            return $this->parseLocale($cookies[$this->sessionName]);
162
        }
163
        /** @var array<string, string> $queryParameters */
164 5
        $queryParameters = $request->getQueryParams();
165 5
        if (isset($queryParameters[$this->queryParameterName])) {
166 3
            $this->logger->debug(
167 3
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
168 3
            );
169 3
            return $this->parseLocale($queryParameters[$this->queryParameterName]);
170
        }
171 2
        return [null, null];
172
    }
173
174 13
    private function isDefaultLocale(string $locale, ?string $country): bool
175
    {
176 13
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
177
    }
178
179
    /**
180
     * @return array{0:string|null, 1:string|null}
181
     */
182 2
    private function detectLocale(ServerRequestInterface $request): array
183
    {
184 2
        foreach ($request->getHeader(Header::ACCEPT_LANGUAGE) as $language) {
185 1
            return $this->parseLocale($language);
186
        }
187 1
        return [null, null];
188
    }
189
190 8
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
191
    {
192 8
        $this->logger->debug('Saving found locale to cookies');
193 8
        $this->session->set($this->sessionName, $locale);
194 8
        $cookie = new Cookie(name: $this->sessionName, value: $locale, secure: $this->cookieSecure);
195 8
        if ($this->cookieDuration !== null) {
196 8
            $cookie = $cookie->withMaxAge($this->cookieDuration);
197
        }
198 8
        return $cookie->addToResponse($response);
199
    }
200
201
    /**
202
     * @return array{0:string, 1:string|null}
203
     */
204 13
    private function parseLocale(string $locale): array
205
    {
206 13
        $localeParts = [];
207 13
        if (str_contains($locale, '-') || str_contains($locale, '_')) {
208 2
            $localeParts = preg_split('/[-_]/', $locale, 2);
209
        } elseif (
210 11
            isset($this->locales[$locale]) &&
211 11
            (str_contains($this->locales[$locale], '-') || str_contains($this->locales[$locale], '_'))
212
        ) {
213 10
            $localeParts = preg_split('/[-_]/', $this->locales[$locale], 2);
214
        }
215
216 13
        if (!empty($localeParts) && count($localeParts) === 2) {
217 12
            return $localeParts;
218
        }
219
220 1
        return [$locale, null];
221
    }
222
223 5
    private function isRequestIgnored(ServerRequestInterface $request): bool
224
    {
225 5
        foreach ($this->ignoredRequests as $ignoredRequest) {
226 1
            if ((new WildcardPattern($ignoredRequest))->match($request->getUri()->getPath())) {
227 1
                return true;
228
            }
229
        }
230 4
        return false;
231
    }
232
233
    /**
234
     * @param array<array-key, string> $locales
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, string>.
Loading history...
235
     *
236
     * @return $this
237
     */
238 1
    public function withLocales(array $locales): self
239
    {
240 1
        $new = clone $this;
241 1
        $new->locales = $locales;
242 1
        return $new;
243
    }
244
245 3
    public function withDefaultLocale(string $defaultLocale): self
246
    {
247 3
        $new = clone $this;
248 3
        $new->defaultLocale = $defaultLocale;
249 3
        return $new;
250
    }
251
252 1
    public function withQueryParameterName(string $queryParameterName): self
253
    {
254 1
        $new = clone $this;
255 1
        $new->queryParameterName = $queryParameterName;
256 1
        return $new;
257
    }
258
259 1
    public function withSessionName(string $sessionName): self
260
    {
261 1
        $new = clone $this;
262 1
        $new->sessionName = $sessionName;
263 1
        return $new;
264
    }
265
266 1
    public function withEnableSaveLocale(bool $enableSaveLocale): self
267
    {
268 1
        $new = clone $this;
269 1
        $new->enableSaveLocale = $enableSaveLocale;
270 1
        return $new;
271
    }
272
273 3
    public function withEnableDetectLocale(bool $enableDetectLocale): self
274
    {
275 3
        $new = clone $this;
276 3
        $new->enableDetectLocale = $enableDetectLocale;
277 3
        return $new;
278
    }
279
280
    /**
281
     * @param string[] $ignoredRequests
282
     *
283
     * @return $this
284
     */
285 2
    public function withIgnoredRequests(array $ignoredRequests): self
286
    {
287 2
        $new = clone $this;
288 2
        $new->ignoredRequests = $ignoredRequests;
289 2
        return $new;
290
    }
291
292 1
    public function withCookieSecure(bool $secure): self
293
    {
294 1
        $new = clone $this;
295 1
        $new->cookieSecure = $secure;
296 1
        return $new;
297
    }
298
299 1
    public function withBaseUrlAlias(string $baseUrlAlias): self
300
    {
301 1
        $new = clone $this;
302 1
        $new->baseUrlAlias = $baseUrlAlias;
303 1
        return $new;
304
    }
305
}
306