Passed
Pull Request — master (#42)
by Rustam
02:54
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 list{string|null, string|null}
0 ignored issues
show
Bug introduced by
The type Yiisoft\Yii\Middleware\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($locale, $country) returns the type array which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
147
            }
148
        }
149 6
        return [null, null];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(null, null) returns the type array<integer,null> which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
150
    }
151
152
    /**
153
     * @return list{string|null, 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];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(null, null) returns the type array<integer,null> which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
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 list{string|null, 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];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(null, null) returns the type array<integer,null> which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
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 list{string, 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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $localeParts returns the type array|string[] which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
218
        }
219
220 1
        return [$locale, null];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($locale, null) returns the type array<integer,null|string> which is incompatible with the documented return type Yiisoft\Yii\Middleware\list.
Loading history...
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