TranslationExtension   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 30
Bugs 1 Features 3
Metric Value
wmc 35
eloc 147
c 30
b 1
f 3
dl 0
loc 244
rs 9.6
ccs 126
cts 126
cp 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A afterCompile() 0 18 5
A getFolders() 0 14 4
A getConfigSchema() 0 18 1
A resolveLoaderClass() 0 12 4
B beforeCompile() 0 44 7
A resolveMessageSelector() 0 9 3
A loadConfiguration() 0 33 5
A resolveResolverClass() 0 19 6
1
<?php
2
declare(strict_types=1);
3
4
namespace Nexendrie\Translation\Bridges\NetteDI;
5
6
use Nette\DI\CompilerExtension;
7
use Nette\DI\Definitions\FactoryDefinition;
8
use Nette\PhpGenerator\ClassType;
9
use Nexendrie\Translation\ILocaleResolver;
10
use Nexendrie\Translation\Bridges\NetteApplication\IAppRequestAwareLocaleResolver;
11
use Nexendrie\Translation\Resolvers\EnvironmentLocaleResolver;
12
use Nexendrie\Translation\Resolvers\ManualLocaleResolver;
13
use Nexendrie\Translation\Resolvers\FallbackLocaleResolver;
14
use Nexendrie\Translation\Resolvers\ChainLocaleResolver;
15
use Nexendrie\Translation\Resolvers\SessionLocaleResolver;
16
use Nexendrie\Translation\Resolvers\HeaderLocaleResolver;
17
use Nexendrie\Translation\Bridges\NetteApplication\ParamLocaleResolver;
18
use Nexendrie\Translation\Translator;
19
use Nexendrie\Translation\ILoader;
20
use Nexendrie\Translation\IFileLoader;
21
use Nexendrie\Translation\Loaders\NeonLoader;
22
use Nexendrie\Translation\Loaders\IniLoader;
23
use Nexendrie\Translation\Loaders\JsonLoader;
24
use Nexendrie\Translation\Loaders\YamlLoader;
25
use Nexendrie\Translation\Loaders\PhpLoader;
26
use Nexendrie\Translation\Loaders\MessagesCatalogue;
27
use Nexendrie\Translation\InvalidLocaleResolverException;
28
use Nexendrie\Translation\InvalidFolderException;
29
use Nexendrie\Translation\InvalidLoaderException;
30
use Nexendrie\Translation\Bridges\Tracy\TranslationPanel;
31
use Nexendrie\Translation\CatalogueCompiler;
32
use Nette\Utils\Arrays;
33
use Nette\Application\Application;
34
use Nette\Bridges\ApplicationLatte\ILatteFactory;
35
use Nette\Utils\AssertionException;
36
use Nexendrie\Translation\ILoaderAwareLocaleResolver;
37
use Nexendrie\Translation\IMessageSelector;
38
use Nexendrie\Translation\MessageSelector;
39
use Nexendrie\Translation\InvalidMessageSelectorException;
40
use Nette\Schema\Expect;
41
use Nette\DI\Definitions\ServiceDefinition;
42
use Nette\DI\Helpers;
43
44
/**
45
 * TranslationExtension for Nette DI Container
46
 *
47
 * @author Jakub Konečný
48
 */
49 1
final class TranslationExtension extends CompilerExtension {
50
  /** @internal */
51
  public const SERVICE_TRANSLATOR = "translator";
52
  /** @internal */
53
  public const SERVICE_LOADER = "loader";
54
  /** @internal */
55
  public const SERVICE_LOCALE_RESOLVER = "localeResolver";
56
  /** @internal */
57
  public const SERVICE_PANEL = "panel";
58
  /** @internal */
59
  public const SERVICE_CATALOGUE_COMPILER = "catalogueCompiler";
60
  /** @internal */
61
  public const SERVICE_ORIGINAL_LOADER = "originalLoader";
62
  /** @internal */
63
  public const SERVICE_MESSAGE_SELECTOR = "messageSelector";
64
  
65
  /** @var string[] */
66
  protected $resolvers = [
67
    "environment" => EnvironmentLocaleResolver::class,
68
    "manual" => ManualLocaleResolver::class,
69
    "fallback" => FallbackLocaleResolver::class,
70
    "session" => SessionLocaleResolver::class,
71
    "header" => HeaderLocaleResolver::class,
72
    "param" => ParamLocaleResolver::class
73
  ];
74
  
75
  /** @var string[] */
76
  protected $loaders = [
77
    "neon" => NeonLoader::class,
78
    "ini" => IniLoader::class,
79
    "json" => JsonLoader::class,
80
    "yaml" => YamlLoader::class,
81
    "php" => PhpLoader::class,
82
    "catalogue" => MessagesCatalogue::class,
83
  ];
84
85
  public function getConfigSchema(): \Nette\Schema\Schema {
86 1
    $params = $this->getContainerBuilder()->parameters;
87 1
    return Expect::structure([
88 1
      "localeResolver" => Expect::anyOf(Expect::string(), Expect::arrayOf("string"))->default(["param", "session", "header", ]),
0 ignored issues
show
introduced by Jakub Konečný
Line exceeds 120 characters; contains 128 characters
Loading history...
89 1
      "default" => Expect::string("en"),
90 1
      "debugger" => Expect::bool(Helpers::expand("%debugMode%", $params)),
91 1
      "loader" => Expect::structure([
92 1
        "name" => Expect::string("neon"),
93 1
        "folders" => Expect::array()->default([Helpers::expand("%appDir%/lang", $params)]),
94 1
      ])->castTo("array"),
95 1
      "onUntranslated" => Expect::array()->default([
96 1
        ["@" . $this->prefix(static::SERVICE_TRANSLATOR), "logUntranslatedMessage"]
97
      ]),
98 1
      "compiler" => Expect::structure([
99 1
        "enabled" => Expect::bool(false),
100 1
        "languages" => Expect::arrayOf("string")->default([]),
101 1
      ])->castTo("array"),
102 1
      "messageSelector" => Expect::type("class")->default(MessageSelector::class),
103
    ]);
104
  }
105
106
  /**
107
   * @return string[]
108
   * @throws InvalidLocaleResolverException
109
   */
110
  protected function resolveResolverClass(): array {
111
    /** @var \stdClass $config */
112 1
    $config = $this->getConfig();
113 1
    $return = [];
114 1
    $resolvers = $config->localeResolver;
115 1
    if(!is_array($resolvers)) {
116 1
      $resolvers = [$resolvers];
117
    }
118 1
    foreach($resolvers as $resolverName) {
119 1
      $resolver = Arrays::get($this->resolvers, strtolower($resolverName), "");
120 1
      if($resolver !== "") {
121 1
        $return[] = $resolver;
122 1
      } elseif(class_exists($resolverName) && is_subclass_of($resolverName, ILocaleResolver::class)) {
123 1
        $return[] = $resolverName;
124
      } else {
125 1
        throw new InvalidLocaleResolverException("Invalid locale resolver $resolverName.");
126
      }
127
    }
128 1
    return $return;
0 ignored issues
show
Bug Best Practice introduced by Jakub Konečný
The expression return $return returns an array which contains values of type string[] which are incompatible with the documented value type string.
Loading history...
129
  }
130
  
131
  /**
132
   * @throws InvalidLoaderException
133
   */
134
  protected function resolveLoaderClass(): string {
135
    /** @var \stdClass $config */
136 1
    $config = $this->getConfig();
137
    /** @var string $loaderName */
138 1
    $loaderName = $config->loader["name"];
139 1
    $loader = Arrays::get($this->loaders, strtolower($loaderName), "");
140 1
    if($loader !== "") {
141 1
      return $loader;
0 ignored issues
show
Bug Best Practice introduced by Jakub Konečný
The expression return $loader could return the type string[] which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
142 1
    } elseif(class_exists($loaderName) && is_subclass_of($loaderName, ILoader::class)) {
143 1
      return $loaderName;
144
    }
145 1
    throw new InvalidLoaderException("Invalid translation loader $loaderName.");
146
  }
147
  
148
  /**
149
   * @return string[]
150
   * @throws InvalidFolderException
151
   */
152
  protected function getFolders(): array {
153
    /** @var \stdClass $config */
154 1
    $config = $this->getConfig();
155 1
    $folders = $config->loader["folders"];
156
    /** @var ITranslationProvider $extension */
157 1
    foreach($this->compiler->getExtensions(ITranslationProvider::class) as $extension) {
158 1
      $folders = array_merge($folders, array_values($extension->getTranslationResources()));
159
    }
160 1
    foreach($folders as $folder) {
161 1
      if(!is_dir($folder)) {
162 1
        throw new InvalidFolderException("Folder $folder does not exist.");
163
      }
164
    }
165 1
    return $folders;
166
  }
167
  
168
  /**
169
   * @throws InvalidMessageSelectorException
170
   */
171
  protected function resolveMessageSelector(): string {
172
    /** @var \stdClass $config */
173 1
    $config = $this->getConfig();
174
    /** @var string $messageSelector */
175 1
    $messageSelector = $config->messageSelector;
176 1
    if(class_exists($messageSelector) && is_subclass_of($messageSelector, IMessageSelector::class)) {
177 1
      return $messageSelector;
178
    }
179 1
    throw new InvalidMessageSelectorException("Invalid message selector $messageSelector.");
180
  }
181
  
182
  /**
183
   * @throws AssertionException
184
   * @throws InvalidLocaleResolverException
185
   * @throws InvalidLoaderException
186
   * @throws InvalidMessageSelectorException
187
   */
188
  public function loadConfiguration(): void {
189 1
    $builder = $this->getContainerBuilder();
190
    /** @var \stdClass $config */
191 1
    $config = $this->getConfig();
192 1
    $resolvers = $this->resolveResolverClass();
193 1
    $loader = $this->resolveLoaderClass();
194 1
    $builder->addDefinition($this->prefix(static::SERVICE_TRANSLATOR))
195 1
      ->setType(Translator::class);
196 1
    $builder->addDefinition($this->prefix(static::SERVICE_LOADER))
197 1
      ->setType($loader)
198 1
      ->addSetup("setDefaultLang", [$config->default]);
199 1
    $messageSelector = $this->resolveMessageSelector();
200 1
    $builder->addDefinition($this->prefix(static::SERVICE_MESSAGE_SELECTOR))
201 1
      ->setType($messageSelector);
202 1
    if(count($resolvers) === 1) {
203 1
      $builder->addDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER))
204 1
        ->setType($resolvers[0]);
205
    } else {
206 1
      $chainResolver = $builder->addDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER))
207 1
        ->setType(ChainLocaleResolver::class);
208 1
      foreach($resolvers as $index => $resolver) {
209 1
        $resolverService = $builder->addDefinition($this->prefix("resolver.$index"))
210 1
          ->setType($resolver)
211 1
          ->setAutowired(false);
212 1
        $chainResolver->addSetup('$service[] = ?', [$resolverService]);
213
      }
214
    }
215 1
    if($config->debugger && interface_exists(\Tracy\IBarPanel::class)) {
216 1
      $builder->addDefinition($this->prefix(static::SERVICE_PANEL))
217 1
        ->setType(TranslationPanel::class);
218
      /** @var ServiceDefinition $tracy */
219 1
      $tracy = $builder->getDefinition("tracy.bar");
220 1
      $tracy->addSetup("addPanel", ["@" . $this->prefix(static::SERVICE_PANEL), "translation"]);
221
    }
222 1
  }
223
  
224
  /**
225
   * @throws AssertionException
226
   * @throws InvalidFolderException
227
   */
228
  public function beforeCompile(): void {
229 1
    $builder = $this->getContainerBuilder();
230
    /** @var \stdClass $config */
231 1
    $config = $this->getConfig();
232
    /** @var ServiceDefinition $loader */
233 1
    $loader = $builder->getDefinition($this->prefix(static::SERVICE_LOADER));
234 1
    if(in_array(IFileLoader::class, class_implements((string) $loader->class), true)) {
235 1
      $folders = $this->getFolders();
236 1
      $loader->addSetup("setFolders", [$folders]);
237 1
      foreach($folders as $folder) {
238 1
        $builder->addDependency($folder);
239
      }
240
    }
241
    /** @var ServiceDefinition $resolver */
242 1
    $resolver = $builder->getDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER));
243 1
    if(in_array(IAppRequestAwareLocaleResolver::class, class_implements((string) $resolver->class), true)) {
244 1
      $applicationService = $builder->getByType(Application::class) ?? "application";
245 1
      if($builder->hasDefinition($applicationService)) {
246
        /** @var ServiceDefinition $application */
247 1
        $application = $builder->getDefinition($applicationService);
248 1
        $application->addSetup('$service->onRequest[] = ?', [[$resolver, "onRequest"]]);
249
      }
250
    }
251 1
    if($config->compiler["enabled"]) {
252 1
      $serviceName = $this->prefix(static::SERVICE_LOADER);
253
      /** @var ServiceDefinition $loader */
254 1
      $loader = $builder->getDefinition($serviceName);
255 1
      $builder->addDefinition($this->prefix(static::SERVICE_ORIGINAL_LOADER))
256 1
        ->setFactory((string) $loader->class, [new ManualLocaleResolver(), $config->loader["folders"]])
257 1
        ->addSetup("setDefaultLang", [$config->default])
258 1
        ->setAutowired(false);
259 1
      $folder = Helpers::expand("%tempDir%/catalogues", $builder->parameters);
260 1
      $loader->setFactory(MessagesCatalogue::class);
261 1
      $loader->setType(MessagesCatalogue::class);
262 1
      $loader->addSetup("setFolders", [[$folder]]);
263 1
      $builder->addDefinition($this->prefix(static::SERVICE_CATALOGUE_COMPILER))
264 1
        ->setFactory(CatalogueCompiler::class, [$loader, $folder, $config->compiler["languages"]]);
265
    }
266 1
    $latteFactoryService = $builder->getByType(ILatteFactory::class) ?? "latte.latteFactory";
267 1
    if($builder->hasDefinition($latteFactoryService)) {
268
      /** @var FactoryDefinition $latteFactory */
269 1
      $latteFactory = $builder->getDefinition($latteFactoryService);
270 1
      $latteFactory->getResultDefinition()->addSetup("addFilter", ["translate", ["@" . $this->prefix(static::SERVICE_TRANSLATOR), "translate"]]);
0 ignored issues
show
introduced by Jakub Konečný
Line exceeds 120 characters; contains 145 characters
Loading history...
271 1
      $latteFactory->getResultDefinition()->addSetup("addProvider", ["translator", "@" . $this->prefix(static::SERVICE_TRANSLATOR)]);
0 ignored issues
show
introduced by Jakub Konečný
Line exceeds 120 characters; contains 133 characters
Loading history...
272
    }
273 1
  }
274
  
275
  public function afterCompile(ClassType $class): void {
276
    /** @var \stdClass $config */
277 1
    $config = $this->getConfig();
278 1
    $initialize = $class->methods["initialize"];
279 1
    $initialize->addBody('$translator = $this->getService(?);', [$this->prefix(static::SERVICE_TRANSLATOR)]);
280 1
    foreach($config->onUntranslated as &$task) {
281 1
      if(!is_array($task)) {
282 1
        $task = explode("::", $task);
283 1
      } elseif(substr($task[0], 0, 1) === "@") {
284 1
        $initialize->addBody('$translator->onUntranslated[] = [$this->getService(?), ?];', [substr($task[0], 1), $task[1]]);
0 ignored issues
show
introduced by Jakub Konečný
Line exceeds 120 characters; contains 124 characters
Loading history...
285 1
        continue;
286
      }
287 1
      $initialize->addBody('$translator->onUntranslated[] = [?, ?];', [$task[0], $task[1]]);
288
    }
289 1
    $initialize->addBody('$resolvers = $this->findByType(?);
290 1
foreach($resolvers as $resolver) $this->getService($resolver)->setLoader($this->getService(?));', [ILoaderAwareLocaleResolver::class, $this->prefix(static::SERVICE_LOADER)]);
0 ignored issues
show
introduced by Jakub Konečný
Line exceeds 120 characters; contains 174 characters
Loading history...
291 1
    if($config->compiler["enabled"]) {
292 1
      $initialize->addBody('$this->getService(?)->compile();', [$this->prefix(static::SERVICE_CATALOGUE_COMPILER)]);
293
    }
294 1
  }
295
}
296
?>