Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
LocaleContextBuilder
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
3 / 3
13
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%
21 / 21
100.00% covered (success)
100.00%
1 / 1
8
 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        return $context;
54    }
55
56    /**
57     * @param array<int, string> $locales
58     */
59    private function pickLocale(array $locales, string $headerName): mixed
60    {
61        if ($this->languageTypehint === null) {
62            return $locales[0];
63        }
64        $last = null;
65        foreach ($locales as $locale) {
66            try {
67                return Utils::toTypehint(
68                    // @phpstan-ignore-next-line argument.type
69                    ReflectionTypeFactory::createReflectionType($this->languageTypehint),
70                    $locale
71                );
72            } catch (\Throwable $err) {
73                $last = $err;
74            }
75        }
76        throw new NotAcceptedException($headerName, $last);
77    }
78}