Failed Conditions
Push — master ( d49c03...6343d0 )
by Bernhard
05:03
created

src/Installer/ModuleFileInstallerManager.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
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
The expression $parametersData of type object<stdClass> is not traversable.
Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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