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