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