Passed
Push — master ( bb74df...01777c )
by Jakub
02:24
created

src/Bridges/NetteDI/TranslationExtension.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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