Test Failed
Pull Request — master (#356)
by Rustam
03:37
created

LocaleMiddleware::withDefaultLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\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\Cookies\Cookie;
15
use Yiisoft\Http\Header;
16
use Yiisoft\Http\Status;
17 10
use Yiisoft\Router\UrlGeneratorInterface;
18
use Yiisoft\Session\SessionInterface;
19 10
use Yiisoft\Translator\TranslatorInterface;
20 10
21
final class LocaleMiddleware implements MiddlewareInterface
22 10
{
23
    private const DEFAULT_LOCALE = 'en';
24 10
    private const DEFAULT_LOCALE_NAME = 'language';
25 10
26
    private TranslatorInterface $translator;
27
    private UrlGeneratorInterface $urlGenerator;
28 10
    private SessionInterface $session;
29
    private ResponseFactoryInterface $responseFactory;
30
    private LoggerInterface $logger;
31
    private array $locales;
32
    private bool $enableSaveLocale = false;
33
    private bool $enableDetectLocale = false;
34
    private string $defaultLocale = self::DEFAULT_LOCALE;
35
    private string $queryParameterName = self::DEFAULT_LOCALE_NAME;
36
    private string $sessionName = self::DEFAULT_LOCALE_NAME;
37
    private ?DateInterval $cookieDuration;
38
39
    public function __construct(
40
        TranslatorInterface $translator,
41
        UrlGeneratorInterface $urlGenerator,
42
        SessionInterface $session,
43
        LoggerInterface $logger,
44
        ResponseFactoryInterface $responseFactory,
45
        array $locales = []
46
    ) {
47
        $this->translator = $translator;
48
        $this->urlGenerator = $urlGenerator;
49
        $this->session = $session;
50
        $this->logger = $logger;
51
        $this->responseFactory = $responseFactory;
52
        $this->locales = $locales;
53
        $this->cookieDuration = new DateInterval('P30D');
54
    }
55
56
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
57
    {
58
        if ($this->locales === []) {
59
            return $handler->handle($request);
60
        }
61
        $this->urlGenerator->setLocales($this->locales);
0 ignored issues
show
Bug introduced by
The method setLocales() does not exist on Yiisoft\Router\UrlGeneratorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

61
        $this->urlGenerator->/** @scrutinizer ignore-call */ 
62
                             setLocales($this->locales);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
62
63
        $uri = $request->getUri();
64
        $path = $uri->getPath();
65
        [$locale, $country] = $this->getLocaleFromPath($path);
66
67
        if ($locale !== null) {
68
            $length = strlen($locale);
69
            $newPath = substr($path, $length + 1);
70
            if ($newPath === '') {
71
                $newPath = '/';
72
            }
73
            $request = $request->withUri($uri->withPath($newPath));
74
            $this->translator->setLocale($locale);
75
            $this->urlGenerator->setUriPrefix('/' . $locale);
76
77
            $response = $handler->handle($request);
78
            if ($this->isDefaultLocale($locale, $country)) {
79
                $response = $this->responseFactory->createResponse(Status::FOUND)
80
                    ->withHeader(Header::LOCATION, $newPath);
81
            }
82
            if ($this->enableSaveLocale) {
83
                $response = $this->saveLocale($locale, $response);
84
            }
85
            return $response;
86
        }
87
        if ($this->enableSaveLocale) {
88
            [$locale, $country] = $this->getLocaleFromRequest($request);
89
        }
90
        if ($locale === null && $this->enableDetectLocale) {
91
            [$locale, $country] = $this->detectLocale($request);
92
        }
93
        if ($locale === null || $this->isDefaultLocale($locale, $country)) {
94
            return $handler->handle($request);
95
        }
96
        return $this->responseFactory->createResponse(Status::FOUND)
97
            ->withHeader(Header::LOCATION, '/' . $locale . rtrim($path, '/'));
98
    }
99
100
    private function getLocaleFromPath(string $path): array
101
    {
102
        $parts = [];
103
        foreach ($this->locales as $code => $locale) {
104
            $lang = is_string($code) ? $code : $locale;
105
            $parts[] = $lang;
106
        }
107
108
        $pattern = implode('|', $parts);
109
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
110
            $locale = $matches[1];
111
            [$locale, $country] = $this->parseLocale($locale);
112
            if (isset($this->locales[$locale])) {
113
                $this->logger->info(sprintf("Locale '%s' found in URL", $locale));
114
                return [$locale, $country];
115
            }
116
        }
117
        return [null, null];
118
    }
119
120
    private function getLocaleFromRequest(ServerRequestInterface $request): array
121
    {
122
        $cookies = $request->getCookieParams();
123
        $queryParameters = $request->getQueryParams();
124
        if (isset($cookies[$this->sessionName])) {
125
            $this->logger->info(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
126
            return $this->parseLocale($cookies[$this->sessionName]);
127
        }
128
        if (isset($queryParameters[$this->queryParameterName])) {
129
            $this->logger->info(
130
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
131
            );
132
            return $this->parseLocale($queryParameters[$this->queryParameterName]);
133
        }
134
        return [null, null];
135
    }
136
137
    private function isDefaultLocale(string $locale, ?string $country): bool
138
    {
139
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
140
    }
141
142
    private function detectLocale(ServerRequestInterface $request): array
143
    {
144
        foreach ($request->getHeader(Header::ACCEPT_LANGUAGE) as $language) {
145
            return $this->parseLocale($language);
146
        }
147
        return [null, null];
148
    }
149
150
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
151
    {
152
        $this->logger->info('Saving found locale to cookies');
153
        $this->session->set($this->sessionName, $locale);
154
        $cookie = (new Cookie($this->sessionName, $locale));
155
        if ($this->cookieDuration !== null) {
156
            $cookie = $cookie->withMaxAge($this->cookieDuration);
157
        }
158
        return $cookie->addToResponse($response);
159
    }
160
161
    private function parseLocale(string $locale): array
162
    {
163
        if (strpos($locale, '-') !== false) {
164
            return explode('-', $locale, 2);
165
        } elseif (isset($this->locales[$locale]) && strpos($this->locales[$locale], '-') !== false) {
166
            return explode('-', $this->locales[$locale], 2);
167
        }
168
        return [$locale, null];
169
    }
170
171
    public function withLocales(array $locales): LocaleMiddleware
172
    {
173
        $new = clone $this;
174
        $new->locales = $locales;
175
        return $new;
176
    }
177
178
    public function withDefaultLocale(string $defaultLocale): LocaleMiddleware
179
    {
180
        $new = clone $this;
181
        $new->defaultLocale = $defaultLocale;
182
        return $new;
183
    }
184
185
    public function withQueryParameterName(string $queryParameterName): LocaleMiddleware
186
    {
187
        $new = clone $this;
188
        $new->queryParameterName = $queryParameterName;
189
        return $new;
190
    }
191
192
    public function withSessionName(string $sessionName): LocaleMiddleware
193
    {
194
        $new = clone $this;
195
        $new->sessionName = $sessionName;
196
        return $new;
197
    }
198
199
    public function withEnableSaveLocale(bool $enableSaveLocale): LocaleMiddleware
200
    {
201
        $new = clone $this;
202
        $new->enableDetectLocale = $enableSaveLocale;
203
        return $new;
204
    }
205
206
    public function withEnableDetectLocale(bool $enableDetectLocale): LocaleMiddleware
207
    {
208
        $new = clone $this;
209
        $new->enableDetectLocale = $enableDetectLocale;
210
        return $new;
211
    }
212
}
213