Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
LocaleContextBuilder
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
3 / 3
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
11
 pickLocale
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2namespace Apie\Common\ContextBuilders;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\ContextBuilders\ContextBuilderInterface;
6use Apie\Core\ContextConstants;
7use Apie\Core\Utils\LanguageParser;
8use Apie\Core\ValueObjects\Utils;
9use Apie\Serializer\Exceptions\NotAcceptedException;
10use Apie\TypeConverter\ReflectionTypeFactory;
11use Psr\Http\Message\ServerRequestInterface;
12
13/**
14 * Context builder that reads the Accept-Language and Content-Language headers and adds them to the context.
15 */
16class LocaleContextBuilder implements ContextBuilderInterface
17{
18    public function __construct(
19        private readonly ?string $languageTypehint
20    ) {
21    }
22
23    public function process(ApieContext $context): ApieContext
24    {
25        $request = $context->getContext(ServerRequestInterface::class, false);
26        if ($request instanceof ServerRequestInterface) {
27            $acceptLanguage = $request->getHeaderLine('Accept-Language');
28            if ($acceptLanguage) {
29                $locales = LanguageParser::parseLanguageHeader($acceptLanguage);
30                if (!empty($locales)) {
31                    $locale = $this->pickLocale($locales, 'Accept-Language');
32                    if (is_object($locale)) {
33                        $context = $context->registerInstance($locale);
34                    }
35                    $context = $context->withContext(ContextConstants::ACCEPT_LOCALE, Utils::toString($locale));
36                    $context = $context->withContext(ContextConstants::ACCEPTED_LOCALES, $locales);
37                }
38            }
39
40            $contentLanguage = $request->getHeaderLine('Content-Language');
41            if ($contentLanguage) {
42                $locales = LanguageParser::parseLanguageHeader($contentLanguage);
43                if (!empty($locales)) {
44                    $locale = $this->pickLocale($locales, 'Content-Language');
45                    if (is_object($locale)) {
46                        $context = $context->registerInstance($locale);
47                    }
48                    $context = $context->withContext(ContextConstants::LOCALE, Utils::toString($locale));
49                    $context = $context->withContext(ContextConstants::DATA_LOCALE, Utils::toString($locale));
50                }
51            }
52        }
53
54        if (!$context->getContext(ContextConstants::DATA_LOCALE, false)) {
55            $acceptLanguage = $context->getContext(ContextConstants::ACCEPT_LOCALE, false);
56            $contentLanguage = $context->getContext(ContextConstants::DATA_LOCALE, false);
57            if ($acceptLanguage) {
58                $localeObject = $this->pickLocale(
59                    [$acceptLanguage],
60                    'framework locale'
61                );
62                if (is_object($localeObject)) {
63                    $context = $context->registerInstance($localeObject);
64                }
65            }
66        }
67
68
69        return $context;
70    }
71
72    /**
73     * @param array<int, string> $locales
74     */
75    private function pickLocale(array $locales, string $headerName): mixed
76    {
77        if ($this->languageTypehint === null) {
78            return $locales[0];
79        }
80        $last = null;
81        foreach ($locales as $locale) {
82            try {
83                return Utils::toTypehint(
84                    // @phpstan-ignore-next-line argument.type
85                    ReflectionTypeFactory::createReflectionType($this->languageTypehint),
86                    $locale
87                );
88            } catch (\Throwable $err) {
89                $last = $err;
90            }
91        }
92        throw new NotAcceptedException($headerName, $last);
93    }
94}