Passed
Pull Request — master (#356)
by Rustam
05:15
created

LocaleMiddleware::process()   B

Complexity

Conditions 11
Paths 17

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 38.0748

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 27
c 1
b 0
f 0
nc 17
nop 2
dl 0
loc 41
ccs 11
cts 28
cp 0.3929
crap 38.0748
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use Yiisoft\Router\UrlGeneratorInterface;
18
use Yiisoft\Session\SessionInterface;
19
use Yiisoft\Translator\TranslatorInterface;
20
21
final class LocaleMiddleware implements MiddlewareInterface
22
{
23
    private const DEFAULT_LOCALE = 'en';
24
    private const DEFAULT_LOCALE_NAME = 'language';
25
26
    private TranslatorInterface $translator;
27
    private UrlGeneratorInterface $urlGenerator;
28
    private SessionInterface $session;
29
    private ResponseFactoryInterface $responseFactory;
30
    private LoggerInterface $logger;
31
    private array $locales;
32
    private bool $enableSaveLocale = true;
33
    private bool $enableDetectLocale = true;
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 10
    public function __construct(
40
        TranslatorInterface $translator,
41
        UrlGeneratorInterface $urlGenerator,
42
        SessionInterface $session,
43
        LoggerInterface $logger,
44
        ResponseFactoryInterface $responseFactory,
45
        array $locales = []
46
    ) {
47 10
        $this->translator = $translator;
48 10
        $this->urlGenerator = $urlGenerator;
49 10
        $this->session = $session;
50 10
        $this->logger = $logger;
51 10
        $this->responseFactory = $responseFactory;
52 10
        $this->locales = $locales;
53 10
        $this->cookieDuration = new DateInterval('P30D');
54 10
    }
55
56 10
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
57
    {
58 10
        if ($this->locales === []) {
59
            return $handler->handle($request);
60
        }
61
62 10
        $uri = $request->getUri();
63 10
        $path = $uri->getPath();
64 10
        [$locale, $country] = $this->getLocaleFromPath($path);
65
66 10
        if ($locale !== null) {
67
            $length = strlen($locale);
68
            $newPath = substr($path, $length + 1);
69
            if ($newPath === '') {
70
                $newPath = '/';
71
            }
72
            $request = $request->withUri($uri->withPath($newPath));
73
            $this->translator->setLocale($locale);
74
            $this->urlGenerator->setUriPrefix('/' . $locale);
75
76
            $response = $handler->handle($request);
77
            if ($this->isDefaultLocale($locale, $country)) {
78
                $response = $this->responseFactory->createResponse(Status::FOUND)
79
                    ->withHeader(Header::LOCATION, $newPath);
80
            }
81
            if ($this->enableSaveLocale) {
82
                $response = $this->saveLocale($locale, $response);
83
            }
84
            return $response;
85
        }
86 10
        if ($this->enableSaveLocale) {
87 10
            $locale = $this->getLocaleFromRequest($request);
88
        }
89 10
        if ($locale === null && $this->enableDetectLocale) {
90
            // TODO: detect locale from headers
91
        }
92 10
        if ($locale === null || $this->isDefaultLocale($locale, $country)) {
93 10
            return $handler->handle($request);
94
        }
95
        return $this->responseFactory->createResponse(Status::FOUND)
96
            ->withHeader(Header::LOCATION, '/' . $locale . rtrim($path, '/'));
97
    }
98
99 10
    private function getLocaleFromPath(string $path): array
100
    {
101 10
        $parts = [];
102 10
        foreach ($this->locales as $code => $locale) {
103 10
            $lang = is_string($code) ? $code : $locale;
104 10
            $parts[] = $lang;
105
        }
106
107 10
        $pattern = implode('|', $parts);
108 10
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
109
            $locale = $matches[1];
110
            $country = null;
111
            if (strpos($locale, '-') !== false) {
112
                [$locale, $country] = explode('-', $locale, 2);
113
            }
114
            if (isset($this->locales[$locale])) {
115
                $this->logger->info(sprintf("Locale '%s' found in URL", $locale));
116
                return [$locale, $country];
117
            }
118
        }
119 10
        return [null, null];
120
    }
121
122 10
    private function getLocaleFromRequest(ServerRequestInterface $request)
123
    {
124 10
        $cookies = $request->getCookieParams();
125 10
        $queryParameters = $request->getQueryParams();
126 10
        if (isset($cookies[$this->sessionName])) {
127
            $this->logger->info(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
128
            return $cookies[$this->sessionName];
129
        }
130 10
        if (isset($queryParameters[$this->queryParameterName])) {
131
            $this->logger->info(
132
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
133
            );
134
            return $queryParameters[$this->queryParameterName];
135
        }
136 10
        return null;
137
    }
138
139
    private function isDefaultLocale(string $locale, ?string $country): bool
140
    {
141
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
142
    }
143
144
    private function detectLocale(ServerRequestInterface $request)
0 ignored issues
show
Unused Code introduced by
The method detectLocale() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

144
    private function detectLocale(/** @scrutinizer ignore-unused */ ServerRequestInterface $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
    }
147
148
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
149
    {
150
        $this->logger->info('Saving found locale to cookies');
151
        $this->session->set($this->sessionName, $locale);
152
        $cookie = (new Cookie($this->sessionName, $locale));
153
        if ($this->cookieDuration !== null) {
154
            $cookie = $cookie->withMaxAge($this->cookieDuration);
155
        }
156
        return $cookie->addToResponse($response);
157
    }
158
}
159