Passed
Pull Request — master (#42)
by Rustam
02:40
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{0: string|null, 1: 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{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];
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{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];
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
     * @param string $locale
203
     *
204
     * @return list{0: string, 1: string|null}
205
     */
206 13
    private function parseLocale(string $locale): array
207
    {
208 13
        $localeParts = [];
209 13
        if (str_contains($locale, '-') || str_contains($locale, '_')) {
210 2
            $localeParts = preg_split('/[-_]/', $locale, 2);
211
        } elseif (
212 11
            isset($this->locales[$locale]) &&
213 11
            (str_contains($this->locales[$locale], '-') || str_contains($this->locales[$locale], '_'))
214
        ) {
215 10
            $localeParts = preg_split('/[-_]/', $this->locales[$locale], 2);
216
        }
217
218 13
        if (!empty($localeParts) && count($localeParts) === 2) {
219 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...
220
        }
221
222 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...
223
    }
224
225 5
    private function isRequestIgnored(ServerRequestInterface $request): bool
226
    {
227 5
        foreach ($this->ignoredRequests as $ignoredRequest) {
228 1
            if ((new WildcardPattern($ignoredRequest))->match($request->getUri()->getPath())) {
229 1
                return true;
230
            }
231
        }
232 4
        return false;
233
    }
234
235
    /**
236
     * @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...
237
     *
238
     * @return $this
239 1
     */public function withLocales(array $locales): self
240
    {
241 1
        $new = clone $this;
242 1
        $new->locales = $locales;
243 1
        return $new;
244
    }
245
246 3
    public function withDefaultLocale(string $defaultLocale): self
247
    {
248 3
        $new = clone $this;
249 3
        $new->defaultLocale = $defaultLocale;
250 3
        return $new;
251
    }
252
253 1
    public function withQueryParameterName(string $queryParameterName): self
254
    {
255 1
        $new = clone $this;
256 1
        $new->queryParameterName = $queryParameterName;
257 1
        return $new;
258
    }
259
260 1
    public function withSessionName(string $sessionName): self
261
    {
262 1
        $new = clone $this;
263 1
        $new->sessionName = $sessionName;
264 1
        return $new;
265
    }
266
267 1
    public function withEnableSaveLocale(bool $enableSaveLocale): self
268
    {
269 1
        $new = clone $this;
270 1
        $new->enableSaveLocale = $enableSaveLocale;
271 1
        return $new;
272
    }
273
274 3
    public function withEnableDetectLocale(bool $enableDetectLocale): self
275
    {
276 3
        $new = clone $this;
277 3
        $new->enableDetectLocale = $enableDetectLocale;
278 3
        return $new;
279
    }
280
281
    /**
282
     * @param string[] $ignoredRequests
283
     *
284
     * @return $this
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