Passed
Push — master ( 893d1e...31005e )
by Kirill
03:35
created

Indexer::invocationDomain()   B

Complexity

Conditions 9
Paths 28

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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