TranslationExtension::getConfigSchema()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 1

Importance

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