Passed
Pull Request — master (#42)
by Alexander
05:33 queued 02:46
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 = preg_split('/[-_]/', $locale, 2, PREG_SPLIT_NO_EMPTY);
207
208 13
        if (isset($this->locales[$locale]) && (empty($localeParts) || count($localeParts) !== 2)) {
209 10
            $localeParts = preg_split('/[-_]/', $this->locales[$locale], 2, PREG_SPLIT_NO_EMPTY);
210
        }
211
212 13
        if (!empty($localeParts) && count($localeParts) === 2) {
213 12
            return $localeParts;
214
        }
215
216 1
        return [$locale, null];
217
    }
218
219 5
    private function isRequestIgnored(ServerRequestInterface $request): bool
220
    {
221 5
        foreach ($this->ignoredRequests as $ignoredRequest) {
222 1
            if ((new WildcardPattern($ignoredRequest))->match($request->getUri()->getPath())) {
223 1
                return true;
224
            }
225
        }
226 4
        return false;
227
    }
228
229
    /**
230
     * @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...
231
     *
232
     * @return $this
233
     */
234 1
    public function withLocales(array $locales): self
235
    {
236 1
        $new = clone $this;
237 1
        $new->locales = $locales;
238 1
        return $new;
239
    }
240
241 3
    public function withDefaultLocale(string $defaultLocale): self
242
    {
243 3
        $new = clone $this;
244 3
        $new->defaultLocale = $defaultLocale;
245 3
        return $new;
246
    }
247
248 1
    public function withQueryParameterName(string $queryParameterName): self
249
    {
250 1
        $new = clone $this;
251 1
        $new->queryParameterName = $queryParameterName;
252 1
        return $new;
253
    }
254
255 1
    public function withSessionName(string $sessionName): self
256
    {
257 1
        $new = clone $this;
258 1
        $new->sessionName = $sessionName;
259 1
        return $new;
260
    }
261
262 1
    public function withEnableSaveLocale(bool $enableSaveLocale): self
263
    {
264 1
        $new = clone $this;
265 1
        $new->enableSaveLocale = $enableSaveLocale;
266 1
        return $new;
267
    }
268
269 3
    public function withEnableDetectLocale(bool $enableDetectLocale): self
270
    {
271 3
        $new = clone $this;
272 3
        $new->enableDetectLocale = $enableDetectLocale;
273 3
        return $new;
274
    }
275
276
    /**
277
     * @param string[] $ignoredRequests
278
     *
279
     * @return $this
280
     */
281 2
    public function withIgnoredRequests(array $ignoredRequests): self
282
    {
283 2
        $new = clone $this;
284 2
        $new->ignoredRequests = $ignoredRequests;
285 2
        return $new;
286
    }
287
288 1
    public function withCookieSecure(bool $secure): self
289
    {
290 1
        $new = clone $this;
291 1
        $new->cookieSecure = $secure;
292 1
        return $new;
293
    }
294
295 1
    public function withBaseUrlAlias(string $baseUrlAlias): self
296
    {
297 1
        $new = clone $this;
298 1
        $new->baseUrlAlias = $baseUrlAlias;
299 1
        return $new;
300
    }
301
}
302