Passed
Pull Request — master (#1115)
by Maxim
22:29
created

Indexer   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Test Coverage

Coverage 98.46%

Importance

Changes 0
Metric Value
wmc 27
eloc 51
dl 0
loc 156
ccs 64
cts 65
cp 0.9846
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A registerMessage() 0 14 3
A registerInvocations() 0 12 3
A indexClasses() 0 6 3
B fetchMessages() 0 27 8
A indexInvocations() 0 12 1
A prepareMessage() 0 7 2
A invocationDomain() 0 23 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Translator;
6
7
use Spiral\Logger\Traits\LoggerTrait;
8
use Spiral\Tokenizer\ScopedClassesInterface;
9
use Spiral\Tokenizer\InvocationsInterface;
10
use Spiral\Tokenizer\Reflection\ReflectionArgument;
11
use Spiral\Tokenizer\Reflection\ReflectionInvocation;
12
use Spiral\Translator\Config\TranslatorConfig;
13
use Spiral\Translator\Traits\TranslatorTrait;
14
15
/**
16
 * Index available classes and function calls to fetch every used string translation. Can
17
 * understand l, p and translate (trait) function.
18
 *
19
 * In addition Indexer will find every string specified in default value of model or class which
20
 * uses TranslatorTrait. String has to be embraced with [[ ]] in order to be indexed, you can
21
 * disable property indexation using @do-not-index doc comment. Translator can merge strings with
22
 * parent data, set class constant INHERIT_TRANSLATIONS to true.
23
 */
24
final class Indexer
25
{
26
    use LoggerTrait;
27
28
    /**
29
     * @param CatalogueInterface $catalogue Catalogue to aggregate messages into.
30
     */
31 5
    public function __construct(
32
        private readonly TranslatorConfig $config,
33
        private readonly CatalogueInterface $catalogue
34
    ) {
35 5
    }
36
37
    /**
38
     * Register string in active translator.
39
     */
40 5
    public function registerMessage(string $domain, string $string, bool $resolveDomain = true): void
41
    {
42 5
        if ($resolveDomain) {
43 3
            $domain = $this->config->resolveDomain($domain);
44
        }
45
46
        //Automatically registering
47 5
        if (!$this->catalogue->has($domain, $string)) {
48 5
            $this->catalogue->set($domain, $string, $string);
49
        }
50
51 5
        $this->getLogger()->debug(
52 5
            \sprintf('[%s]: `%s`', $domain, $string),
53 5
            ['domain' => $domain, 'string' => $string]
54 5
        );
55
    }
56
57
    /**
58
     * Index and register i18n string located in default properties which belongs to TranslatorTrait
59
     * classes.
60
     */
61 3
    public function indexClasses(ScopedClassesInterface $locator): void
62
    {
63 3
        foreach ($locator->getScopedClasses('translations', TranslatorTrait::class) as $class) {
64 3
            $strings = $this->fetchMessages($class, true);
65 3
            foreach ($strings as $string) {
66 3
                $this->registerMessage($class->getName(), $string);
67
            }
68
        }
69
    }
70
71
    /**
72
     * Index available methods and function invocations, target: l, p, $this->translate()
73
     * functions.
74
     */
75 4
    public function indexInvocations(InvocationsInterface $locator): void
76
    {
77 4
        $this->registerInvocations($locator->getInvocations(
78 4
            new \ReflectionFunction('l')
79 4
        ));
80
81 4
        $this->registerInvocations($locator->getInvocations(
82 4
            new \ReflectionFunction('p')
83 4
        ));
84
85 4
        $this->registerInvocations($locator->getInvocations(
86 4
            new \ReflectionMethod(TranslatorTrait::class, 'say')
87 4
        ));
88
    }
89
90
    /**
91
     * Register found invocations in translator bundles.
92
     *
93
     * @param ReflectionInvocation[] $invocations
94
     */
95 4
    private function registerInvocations(array $invocations): void
96
    {
97 4
        foreach ($invocations as $invocation) {
98 4
            if ($invocation->getArgument(0)->getType() != ReflectionArgument::STRING) {
99
                //We can only index invocations with constant string arguments
100 4
                continue;
101
            }
102
103 4
            $string = $invocation->getArgument(0)->stringValue();
104 4
            $string = $this->prepareMessage($string);
105
106 4
            $this->registerMessage($this->invocationDomain($invocation), $string, false);
107
        }
108
    }
109
110
    /**
111
     * Fetch default string values from class and merge it with parent strings if requested.
112
     */
113 3
    private function fetchMessages(\ReflectionClass $reflection, bool $inherit = false): array
114
    {
115 3
        $target = $reflection->getDefaultProperties() + $reflection->getConstants();
116
117 3
        foreach ($reflection->getProperties() as $property) {
118 3
            if (\is_string($property->getDocComment()) && \strpos($property->getDocComment(), '@do-not-index')) {
119 1
                unset($target[$property->getName()]);
120
            }
121
        }
122
123 3
        $strings = [];
124 3
        \array_walk_recursive($target, function ($value) use (&$strings): void {
125 3
            if (\is_string($value) && Translator::isMessage($value)) {
126 3
                $strings[] = $this->prepareMessage($value);
127
            }
128 3
        });
129
130 3
        if ($inherit && $reflection->getParentClass()) {
131
            //Joining strings data with parent class values (inheritance ON) - resolved into same
132
            //domain on export
133 3
            $strings = \array_merge(
134 3
                $strings,
135 3
                $this->fetchMessages($reflection->getParentClass(), true)
136 3
            );
137
        }
138
139 3
        return $strings;
140
    }
141
142
    /**
143
     * Get associated domain.
144
     */
145 4
    private function invocationDomain(ReflectionInvocation $invocation): string
146
    {
147
        //Translation using default bundle
148 4
        $domain = $this->config->getDefaultDomain();
149
150 4
        if ($invocation->getName() === 'say') {
151
            //Let's try to confirm domain
152 4
            $domain = $this->config->resolveDomain($invocation->getClass());
153
        }
154
155
        //`l` and `p`, `say` functions
156 4
        $argument = match (\strtolower($invocation->getName())) {
157 4
            'say', 'l' => $invocation->countArguments() >= 3 ? $invocation->getArgument(2) : null,
158 4
            'p' => $invocation->countArguments() >= 4 ? $invocation->getArgument(3) : null,
159
            default => null
160 4
        };
161
162 4
        if (!empty($argument) && $argument->getType() === ReflectionArgument::STRING) {
163
            //Domain specified in arguments
164 4
            $domain = $this->config->resolveDomain($argument->stringValue());
165
        }
166
167 4
        return $domain;
168
    }
169
170
    /**
171
     * Remove [[ and ]] braces from translated string.
172
     */
173 5
    private function prepareMessage(string $string): string
174
    {
175 5
        if (Translator::isMessage($string)) {
176 5
            $string = \substr($string, 2, -2);
177
        }
178
179 5
        return $string;
180
    }
181
}
182