These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the puli/manager package. |
||
5 | * |
||
6 | * (c) Bernhard Schussek <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace Puli\Manager\Installer; |
||
13 | |||
14 | use Exception; |
||
15 | use Puli\Manager\Api\Installer\InstallerDescriptor; |
||
16 | use Puli\Manager\Api\Installer\InstallerManager; |
||
17 | use Puli\Manager\Api\Installer\InstallerParameter; |
||
18 | use Puli\Manager\Api\Installer\NoSuchInstallerException; |
||
19 | use Puli\Manager\Api\Module\Module; |
||
20 | use Puli\Manager\Api\Module\ModuleCollection; |
||
21 | use Puli\Manager\Api\Module\RootModule; |
||
22 | use Puli\Manager\Api\Module\RootModuleFileManager; |
||
23 | use RuntimeException; |
||
24 | use stdClass; |
||
25 | use Webmozart\Expression\Expr; |
||
26 | use Webmozart\Expression\Expression; |
||
27 | use Webmozart\Json\JsonValidator; |
||
28 | use Webmozart\Json\ValidationFailedException; |
||
29 | |||
30 | /** |
||
31 | * An installer manager that stores the installers in the module file. |
||
32 | * |
||
33 | * @since 1.0 |
||
34 | * |
||
35 | * @author Bernhard Schussek <[email protected]> |
||
36 | */ |
||
37 | class ModuleFileInstallerManager implements InstallerManager |
||
38 | { |
||
39 | /** |
||
40 | * The extra key that stores the installer data. |
||
41 | */ |
||
42 | const INSTALLERS_KEY = 'installers'; |
||
43 | |||
44 | /** |
||
45 | * @var array |
||
46 | */ |
||
47 | private static $builtinInstallers = array( |
||
48 | 'copy' => array( |
||
49 | 'class' => 'Puli\Manager\Installer\CopyInstaller', |
||
50 | 'description' => 'Copies assets to a target directory', |
||
51 | ), |
||
52 | 'symlink' => array( |
||
53 | 'class' => 'Puli\Manager\Installer\SymlinkInstaller', |
||
54 | 'description' => 'Creates asset symlinks in a target directory', |
||
55 | 'parameters' => array( |
||
56 | 'relative' => array( |
||
57 | 'default' => true, |
||
58 | 'description' => 'Whether to create relative or absolute links', |
||
59 | ), |
||
60 | ), |
||
61 | ), |
||
62 | ); |
||
63 | |||
64 | /** |
||
65 | * @var RootModuleFileManager |
||
66 | */ |
||
67 | private $rootModuleFileManager; |
||
68 | |||
69 | /** |
||
70 | * @var ModuleCollection |
||
71 | */ |
||
72 | private $modules; |
||
73 | |||
74 | /** |
||
75 | * @var RootModule |
||
76 | */ |
||
77 | private $rootModule; |
||
78 | |||
79 | /** |
||
80 | * @var InstallerDescriptor[] |
||
81 | */ |
||
82 | private $installerDescriptors; |
||
83 | |||
84 | /** |
||
85 | * @var InstallerDescriptor[] |
||
86 | */ |
||
87 | private $rootInstallerDescriptors; |
||
88 | |||
89 | 82 | public function __construct(RootModuleFileManager $rootModuleFileManager, ModuleCollection $modules) |
|
90 | { |
||
91 | 82 | $this->rootModuleFileManager = $rootModuleFileManager; |
|
92 | 82 | $this->modules = $modules; |
|
93 | 82 | $this->rootModule = $modules->getRootModule(); |
|
94 | 82 | } |
|
95 | |||
96 | /** |
||
97 | * {@inheritdoc} |
||
98 | */ |
||
99 | 14 | public function addRootInstallerDescriptor(InstallerDescriptor $descriptor) |
|
100 | { |
||
101 | 14 | $this->assertInstallersLoaded(); |
|
102 | |||
103 | 14 | $name = $descriptor->getName(); |
|
104 | |||
105 | 14 | $previouslySetInRoot = isset($this->rootInstallerDescriptors[$name]); |
|
106 | 14 | $previousInstaller = $previouslySetInRoot ? $this->rootInstallerDescriptors[$name] : null; |
|
107 | |||
108 | 14 | View Code Duplication | if (isset($this->installerDescriptors[$name]) && !$previouslySetInRoot) { |
109 | 4 | throw new RuntimeException(sprintf( |
|
110 | 4 | 'An installer with the name "%s" exists already.', |
|
111 | $name |
||
112 | )); |
||
113 | } |
||
114 | |||
115 | try { |
||
116 | 10 | $this->installerDescriptors[$name] = $descriptor; |
|
117 | 10 | $this->rootInstallerDescriptors[$name] = $descriptor; |
|
118 | |||
119 | 10 | $this->persistInstallersData(); |
|
120 | 4 | } catch (Exception $e) { |
|
121 | 4 | if ($previouslySetInRoot) { |
|
122 | 2 | $this->installerDescriptors[$name] = $previousInstaller; |
|
123 | 2 | $this->rootInstallerDescriptors[$name] = $previousInstaller; |
|
124 | } else { |
||
125 | 2 | unset($this->installerDescriptors[$name]); |
|
126 | 2 | unset($this->rootInstallerDescriptors[$name]); |
|
127 | } |
||
128 | |||
129 | 4 | throw $e; |
|
130 | } |
||
131 | 6 | } |
|
132 | |||
133 | /** |
||
134 | * {@inheritdoc} |
||
135 | */ |
||
136 | 12 | public function removeRootInstallerDescriptor($name) |
|
137 | { |
||
138 | 12 | $this->assertInstallersLoaded(); |
|
139 | |||
140 | 12 | $previouslySetInRoot = isset($this->rootInstallerDescriptors[$name]); |
|
141 | 12 | $previousInstaller = $previouslySetInRoot ? $this->rootInstallerDescriptors[$name] : null; |
|
142 | |||
143 | 12 | View Code Duplication | if (isset($this->installerDescriptors[$name]) && !$previouslySetInRoot) { |
144 | 4 | throw new RuntimeException(sprintf( |
|
145 | 'Cannot remove installer "%s": Can only remove installers '. |
||
146 | 4 | 'configured in the root module.', |
|
147 | $name |
||
148 | )); |
||
149 | } |
||
150 | |||
151 | 8 | if (!$previouslySetInRoot) { |
|
152 | 2 | return; |
|
153 | } |
||
154 | |||
155 | try { |
||
156 | 6 | unset($this->installerDescriptors[$name]); |
|
157 | 6 | unset($this->rootInstallerDescriptors[$name]); |
|
158 | |||
159 | 6 | $this->persistInstallersData(); |
|
160 | 2 | } catch (Exception $e) { |
|
161 | 2 | if ($previouslySetInRoot) { |
|
162 | 2 | $this->installerDescriptors[$name] = $previousInstaller; |
|
163 | 2 | $this->rootInstallerDescriptors[$name] = $previousInstaller; |
|
164 | } |
||
165 | |||
166 | 2 | throw $e; |
|
167 | } |
||
168 | 4 | } |
|
169 | |||
170 | /** |
||
171 | * {@inheritdoc} |
||
172 | */ |
||
173 | 6 | public function removeRootInstallerDescriptors(Expression $expr) |
|
174 | { |
||
175 | 6 | $this->assertInstallersLoaded(); |
|
176 | |||
177 | 6 | $previousInstallers = $this->rootInstallerDescriptors; |
|
178 | 6 | $previousRootInstallers = $this->rootInstallerDescriptors; |
|
179 | |||
180 | try { |
||
181 | // Only remove root installers |
||
182 | 6 | foreach ($previousRootInstallers as $installer) { |
|
183 | 6 | if ($expr->evaluate($installer)) { |
|
184 | 6 | unset($this->installerDescriptors[$installer->getName()]); |
|
185 | 6 | unset($this->rootInstallerDescriptors[$installer->getName()]); |
|
186 | } |
||
187 | } |
||
188 | |||
189 | 6 | $this->persistInstallersData(); |
|
190 | 2 | } catch (Exception $e) { |
|
191 | 2 | $this->installerDescriptors = $previousInstallers; |
|
192 | 2 | $this->rootInstallerDescriptors = $previousRootInstallers; |
|
193 | |||
194 | 2 | throw $e; |
|
195 | } |
||
196 | 4 | } |
|
197 | |||
198 | /** |
||
199 | * {@inheritdoc} |
||
200 | */ |
||
201 | 2 | public function clearRootInstallerDescriptors() |
|
202 | { |
||
203 | 2 | $this->removeRootInstallerDescriptors(Expr::true()); |
|
204 | 2 | } |
|
205 | |||
206 | /** |
||
207 | * {@inheritdoc} |
||
208 | */ |
||
209 | 8 | public function getRootInstallerDescriptor($name) |
|
210 | { |
||
211 | 8 | $this->assertInstallersLoaded(); |
|
212 | |||
213 | 8 | if (!isset($this->rootInstallerDescriptors[$name])) { |
|
214 | 6 | throw NoSuchInstallerException::forInstallerNameAndModuleName($name, $this->rootModule->getName()); |
|
215 | } |
||
216 | |||
217 | 2 | return $this->rootInstallerDescriptors[$name]; |
|
218 | } |
||
219 | |||
220 | /** |
||
221 | * {@inheritdoc} |
||
222 | */ |
||
223 | 2 | public function getRootInstallerDescriptors() |
|
224 | { |
||
225 | 2 | $this->assertInstallersLoaded(); |
|
226 | |||
227 | 2 | return $this->rootInstallerDescriptors; |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * {@inheritdoc} |
||
232 | */ |
||
233 | 2 | View Code Duplication | public function findRootInstallerDescriptors(Expression $expr) |
234 | { |
||
235 | 2 | $this->assertInstallersLoaded(); |
|
236 | |||
237 | 2 | $installers = array(); |
|
238 | |||
239 | 2 | foreach ($this->rootInstallerDescriptors as $installer) { |
|
240 | 2 | if ($expr->evaluate($installer)) { |
|
241 | 2 | $installers[] = $installer; |
|
242 | } |
||
243 | } |
||
244 | |||
245 | 2 | return $installers; |
|
246 | } |
||
247 | |||
248 | /** |
||
249 | * {@inheritdoc} |
||
250 | */ |
||
251 | 2 | public function hasRootInstallerDescriptor($name) |
|
252 | { |
||
253 | 2 | $this->assertInstallersLoaded(); |
|
254 | |||
255 | 2 | return isset($this->rootInstallerDescriptors[$name]); |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * {@inheritdoc} |
||
260 | */ |
||
261 | 4 | View Code Duplication | public function hasRootInstallerDescriptors(Expression $expr = null) |
262 | { |
||
263 | 4 | $this->assertInstallersLoaded(); |
|
264 | |||
265 | 4 | if (!$expr) { |
|
266 | 4 | return count($this->rootInstallerDescriptors) > 0; |
|
267 | } |
||
268 | |||
269 | 2 | foreach ($this->rootInstallerDescriptors as $installer) { |
|
270 | 2 | if ($expr->evaluate($installer)) { |
|
271 | 2 | return true; |
|
272 | } |
||
273 | } |
||
274 | |||
275 | 2 | return false; |
|
276 | } |
||
277 | |||
278 | /** |
||
279 | * {@inheritdoc} |
||
280 | */ |
||
281 | 24 | public function getInstallerDescriptor($name) |
|
282 | { |
||
283 | 24 | $this->assertInstallersLoaded(); |
|
284 | |||
285 | 22 | if (!isset($this->installerDescriptors[$name])) { |
|
286 | 2 | throw NoSuchInstallerException::forInstallerName($name); |
|
287 | } |
||
288 | |||
289 | 20 | return $this->installerDescriptors[$name]; |
|
290 | } |
||
291 | |||
292 | /** |
||
293 | * {@inheritdoc} |
||
294 | */ |
||
295 | 9 | public function getInstallerDescriptors() |
|
296 | { |
||
297 | 9 | $this->assertInstallersLoaded(); |
|
298 | |||
299 | 9 | return $this->installerDescriptors; |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * {@inheritdoc} |
||
304 | */ |
||
305 | 2 | View Code Duplication | public function findInstallerDescriptors(Expression $expr) |
306 | { |
||
307 | 2 | $this->assertInstallersLoaded(); |
|
308 | |||
309 | 2 | $installers = array(); |
|
310 | |||
311 | 2 | foreach ($this->installerDescriptors as $installer) { |
|
312 | 2 | if ($expr->evaluate($installer)) { |
|
313 | 2 | $installers[] = $installer; |
|
314 | } |
||
315 | } |
||
316 | |||
317 | 2 | return $installers; |
|
318 | } |
||
319 | |||
320 | /** |
||
321 | * {@inheritdoc} |
||
322 | */ |
||
323 | 14 | public function hasInstallerDescriptor($name) |
|
324 | { |
||
325 | 14 | $this->assertInstallersLoaded(); |
|
326 | |||
327 | 14 | return isset($this->installerDescriptors[$name]); |
|
328 | } |
||
329 | |||
330 | /** |
||
331 | * {@inheritdoc} |
||
332 | */ |
||
333 | 2 | View Code Duplication | public function hasInstallerDescriptors(Expression $expr = null) |
334 | { |
||
335 | 2 | $this->assertInstallersLoaded(); |
|
336 | |||
337 | 2 | if (!$expr) { |
|
338 | 2 | return count($this->installerDescriptors) > 0; |
|
339 | } |
||
340 | |||
341 | 2 | foreach ($this->installerDescriptors as $installer) { |
|
342 | 2 | if ($expr->evaluate($installer)) { |
|
343 | 2 | return true; |
|
344 | } |
||
345 | } |
||
346 | |||
347 | 2 | return false; |
|
348 | } |
||
349 | |||
350 | 68 | private function assertInstallersLoaded() |
|
351 | { |
||
352 | 68 | if (null !== $this->installerDescriptors) { |
|
353 | 38 | return; |
|
354 | } |
||
355 | |||
356 | 68 | $this->installerDescriptors = array(); |
|
357 | |||
358 | 68 | foreach ($this->modules as $module) { |
|
359 | 68 | if ($this->rootModule !== $module) { |
|
360 | 68 | $this->loadInstallers($module); |
|
361 | } |
||
362 | } |
||
363 | |||
364 | 66 | $this->loadInstallers($this->rootModule); |
|
365 | 66 | } |
|
366 | |||
367 | 22 | private function persistInstallersData() |
|
368 | { |
||
369 | 22 | $data = array(); |
|
370 | |||
371 | 22 | foreach ($this->rootInstallerDescriptors as $installerName => $installer) { |
|
372 | 16 | $data[$installerName] = $this->installerToData($installer); |
|
373 | } |
||
374 | |||
375 | 22 | if ($data) { |
|
376 | 16 | $this->rootModuleFileManager->setExtraKey(self::INSTALLERS_KEY, (object) $data); |
|
377 | } else { |
||
378 | 6 | $this->rootModuleFileManager->removeExtraKey(self::INSTALLERS_KEY); |
|
379 | } |
||
380 | 14 | } |
|
381 | |||
382 | 68 | private function loadInstallers(Module $module) |
|
383 | { |
||
384 | 68 | foreach (self::$builtinInstallers as $name => $installerData) { |
|
385 | 68 | $installer = $this->dataToInstaller($name, (object) $installerData); |
|
386 | |||
387 | 68 | $this->installerDescriptors[$name] = $installer; |
|
388 | } |
||
389 | |||
390 | 68 | $moduleFile = $module->getModuleFile(); |
|
391 | |||
392 | 68 | if (null === $moduleFile) { |
|
393 | 66 | return; |
|
394 | } |
||
395 | |||
396 | 68 | $moduleName = $module->getName(); |
|
397 | 68 | $installersData = $moduleFile->getExtraKey(self::INSTALLERS_KEY); |
|
398 | |||
399 | 68 | if (!$installersData) { |
|
400 | 66 | return; |
|
401 | } |
||
402 | |||
403 | 54 | $jsonValidator = new JsonValidator(); |
|
404 | 54 | $errors = $jsonValidator->validate($installersData, __DIR__.'/../../res/schema/installers-schema-1.0.json'); |
|
405 | |||
406 | 54 | View Code Duplication | if (count($errors) > 0) { |
407 | 2 | throw new ValidationFailedException(sprintf( |
|
408 | 2 | "The extra key \"%s\" of module \"%s\" is invalid:\n%s", |
|
409 | 2 | self::INSTALLERS_KEY, |
|
410 | $moduleName, |
||
411 | 2 | implode("\n", $errors) |
|
412 | )); |
||
413 | } |
||
414 | |||
415 | 52 | foreach ($installersData as $name => $installerData) { |
|
416 | 52 | $installer = $this->dataToInstaller($name, $installerData); |
|
417 | |||
418 | 52 | $this->installerDescriptors[$name] = $installer; |
|
419 | |||
420 | 52 | if ($module instanceof RootModule) { |
|
421 | 52 | $this->rootInstallerDescriptors[$name] = $installer; |
|
422 | } |
||
423 | } |
||
424 | 52 | } |
|
425 | |||
426 | 68 | private function dataToInstaller($installerName, stdClass $installerData) |
|
427 | { |
||
428 | 68 | $parameters = array(); |
|
429 | |||
430 | 68 | if (isset($installerData->parameters)) { |
|
431 | 68 | $parameters = $this->dataToParameters((object) $installerData->parameters); |
|
432 | } |
||
433 | |||
434 | 68 | return new InstallerDescriptor( |
|
435 | $installerName, |
||
436 | 68 | $installerData->class, |
|
437 | 68 | isset($installerData->description) ? $installerData->description : null, |
|
438 | $parameters |
||
439 | ); |
||
440 | } |
||
441 | |||
442 | 68 | private function dataToParameters(stdClass $parametersData) |
|
443 | { |
||
444 | 68 | $parameters = array(); |
|
445 | |||
446 | 68 | foreach ($parametersData as $parameterName => $parameterData) { |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
447 | 68 | $parameters[$parameterName] = $this->dataToParameter($parameterName, (object) $parameterData); |
|
448 | } |
||
449 | |||
450 | 68 | return $parameters; |
|
451 | } |
||
452 | |||
453 | 68 | private function dataToParameter($parameterName, stdClass $parameterData) |
|
454 | { |
||
455 | 68 | return new InstallerParameter( |
|
456 | $parameterName, |
||
457 | 68 | isset($parameterData->required) && $parameterData->required |
|
458 | 2 | ? InstallerParameter::REQUIRED |
|
459 | 68 | : InstallerParameter::OPTIONAL, |
|
460 | 68 | isset($parameterData->default) ? $parameterData->default : null, |
|
461 | 68 | isset($parameterData->description) ? $parameterData->description : null |
|
462 | ); |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Extracting an object containing the data from an installer descriptor. |
||
467 | * |
||
468 | * @param InstallerDescriptor $installer The installer descriptor. |
||
469 | * |
||
470 | * @return stdClass |
||
471 | */ |
||
472 | 16 | private function installerToData(InstallerDescriptor $installer) |
|
473 | { |
||
474 | $data = (object) array( |
||
475 | 16 | 'class' => $installer->getClassName(), |
|
476 | ); |
||
477 | |||
478 | 16 | if ($installer->getDescription()) { |
|
0 ignored issues
–
show
The expression
$installer->getDescription() of type string|null is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
479 | 2 | $data->description = $installer->getDescription(); |
|
480 | } |
||
481 | |||
482 | 16 | if ($installer->getParameters()) { |
|
483 | 2 | $data->parameters = $this->parametersToData($installer->getParameters()); |
|
484 | } |
||
485 | |||
486 | 16 | return $data; |
|
487 | } |
||
488 | |||
489 | /** |
||
490 | * @param InstallerParameter[] $parameters |
||
491 | * |
||
492 | * @return array |
||
493 | */ |
||
494 | 2 | private function parametersToData(array $parameters) |
|
495 | { |
||
496 | 2 | $data = array(); |
|
497 | |||
498 | 2 | foreach ($parameters as $parameter) { |
|
499 | 2 | $data[$parameter->getName()] = $this->parameterToData($parameter); |
|
500 | } |
||
501 | |||
502 | 2 | return (object) $data; |
|
503 | } |
||
504 | |||
505 | 2 | private function parameterToData(InstallerParameter $parameter) |
|
506 | { |
||
507 | 2 | $data = new stdClass(); |
|
508 | |||
509 | 2 | if ($parameter->isRequired()) { |
|
510 | 2 | $data->required = true; |
|
511 | } |
||
512 | |||
513 | 2 | if (null !== $default = $parameter->getDefaultValue()) { |
|
514 | 2 | $data->default = $default; |
|
515 | } |
||
516 | |||
517 | 2 | if ($description = $parameter->getDescription()) { |
|
518 | 2 | $data->description = $description; |
|
519 | } |
||
520 | |||
521 | 2 | return $data; |
|
522 | } |
||
523 | } |
||
524 |