Failed Conditions
Pull Request — master (#47)
by Mateusz
05:47
created

src/Api/Container.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\Api;
13
14
use JsonSchema\Uri\UriRetriever;
15
use LogicException;
16
use Psr\Log\LoggerInterface;
17
use Puli\Discovery\Api\Discovery;
18
use Puli\Discovery\Api\EditableDiscovery;
19
use Puli\Manager\Api\Asset\AssetManager;
20
use Puli\Manager\Api\Cache\CacheManager;
21
use Puli\Manager\Api\Config\Config;
22
use Puli\Manager\Api\Config\ConfigFileManager;
23
use Puli\Manager\Api\Context\Context;
24
use Puli\Manager\Api\Context\ProjectContext;
25
use Puli\Manager\Api\Discovery\DiscoveryManager;
26
use Puli\Manager\Api\Factory\FactoryManager;
27
use Puli\Manager\Api\Installation\InstallationManager;
28
use Puli\Manager\Api\Installer\InstallerManager;
29
use Puli\Manager\Api\Module\ModuleManager;
30
use Puli\Manager\Api\Module\RootModuleFile;
31
use Puli\Manager\Api\Module\RootModuleFileManager;
32
use Puli\Manager\Api\Repository\RepositoryManager;
33
use Puli\Manager\Api\Server\ServerManager;
34
use Puli\Manager\Api\Storage\Storage;
35
use Puli\Manager\Assert\Assert;
36
use Puli\Manager\Asset\DiscoveryAssetManager;
37
use Puli\Manager\Cache\CacheFileConverter;
38
use Puli\Manager\Cache\CacheFileStorage;
39
use Puli\Manager\Cache\CacheManagerImpl;
40
use Puli\Manager\Config\ConfigFileConverter;
41
use Puli\Manager\Config\ConfigFileManagerImpl;
42
use Puli\Manager\Config\DefaultConfig;
43
use Puli\Manager\Config\EnvConfig;
44
use Puli\Manager\Discovery\DiscoveryManagerImpl;
45
use Puli\Manager\Factory\FactoryManagerImpl;
46
use Puli\Manager\Factory\Generator\DefaultGeneratorRegistry;
47
use Puli\Manager\Filesystem\FilesystemStorage;
48
use Puli\Manager\Installation\InstallationManagerImpl;
49
use Puli\Manager\Installer\ModuleFileInstallerManager;
50
use Puli\Manager\Json\ChainVersioner;
51
use Puli\Manager\Json\JsonConverterProvider;
52
use Puli\Manager\Json\JsonStorage;
53
use Puli\Manager\Json\LocalUriRetriever;
54
use Puli\Manager\Module\Migration\ModuleFile10To20Migration;
55
use Puli\Manager\Module\ModuleFileConverter;
56
use Puli\Manager\Module\ModuleManagerImpl;
57
use Puli\Manager\Module\RootModuleFileConverter;
58
use Puli\Manager\Module\RootModuleFileManagerImpl;
59
use Puli\Manager\Php\ClassWriter;
60
use Puli\Manager\Repository\RepositoryManagerImpl;
61
use Puli\Manager\Server\ModuleFileServerManager;
62
use Puli\Manager\Util\System;
63
use Puli\Repository\Api\EditableRepository;
64
use Puli\Repository\Api\ResourceRepository;
65
use Puli\UrlGenerator\Api\UrlGenerator;
66
use Puli\UrlGenerator\DiscoveryUrlGenerator;
67
use stdClass;
68
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
69
use Webmozart\Expression\Expr;
70
use Webmozart\Json\Conversion\JsonConverter;
71
use Webmozart\Json\JsonDecoder;
72
use Webmozart\Json\JsonEncoder;
73
use Webmozart\Json\JsonValidator;
74
use Webmozart\Json\Migration\MigratingConverter;
75
use Webmozart\Json\Migration\MigrationManager;
76
use Webmozart\Json\Validation\ValidatingConverter;
77
use Webmozart\Json\Versioning\JsonVersioner;
78
use Webmozart\Json\Versioning\SchemaUriVersioner;
79
use Webmozart\Json\Versioning\VersionFieldVersioner;
80
use Webmozart\PathUtil\Path;
81
82
/**
83
 * The Puli service container.
84
 *
85
 * Use this class to access the managers provided by this module:
86
 *
87
 * ```php
88
 * $puli = new Puli(getcwd());
89
 * $puli->start();
90
 *
91
 * $moduleManager = $puli->getModuleManager();
92
 * ```
93
 *
94
 * The `Puli` class either operates in the global or a project context:
95
 *
96
 *  * The "global context" is not tied to a specific root module. A global
97
 *    context only loads the settings of the "config.json" file in the home
98
 *    directory. The `Puli` class operates in the global context if no
99
 *    project root directory is passed to the constructor. In the global
100
 *    context, only the global config file manager is available.
101
 *  * The "project context" is tied to a specific Puli project. You need to
102
 *    pass the path to the project's root directory to the constructor or to
103
 *    {@link setRootDirectory()}. The configuration of the "puli.json" file in
104
 *    the root directory is used to configure the managers.
105
 *
106
 * The `Puli` class creates four kinds of managers:
107
 *
108
 *  * The "config file manager" allows you to modify entries of the
109
 *    "config.json" file in the home directory.
110
 *  * The "module file manager" manages modifications to the "puli.json" file
111
 *    of a Puli project.
112
 *  * The "module manager" manages the module repository of a Puli project.
113
 *  * The "repository manager" manages the resource repository of a Puli
114
 *    project.
115
 *  * The "discovery manager" manages the resource discovery of a Puli project.
116
 *
117
 * The home directory is read from the context variable "PULI_HOME".
118
 * If this variable is not set, the home directory defaults to:
119
 *
120
 *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
121
 *    "HOME".
122
 *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
123
 *    variable "APPDATA".
124
 *
125
 * If none of these variables can be found, an exception is thrown.
126
 *
127
 * A .htaccess file is put into the home directory to protect it from web
128
 * access.
129
 *
130
 * @since  1.0
131
 *
132
 * @author Bernhard Schussek <[email protected]>
133
 */
134
class Container
135
{
136
    /**
137
     * @var string|null
138
     */
139
    private $rootDir;
140
141
    /**
142
     * @var string
143
     */
144
    private $env;
145
146
    /**
147
     * @var EventDispatcherInterface|null
148
     */
149
    private $dispatcher;
150
151
    /**
152
     * @var Context|ProjectContext
153
     */
154
    private $context;
155
156
    /**
157
     * @var ResourceRepository
158
     */
159
    private $repo;
160
161
    /**
162
     * @var Discovery
163
     */
164
    private $discovery;
165
166
    /**
167
     * @var object
168
     */
169
    private $factory;
170
171
    /**
172
     * @var FactoryManager
173
     */
174
    private $factoryManager;
175
176
    /**
177
     * @var ConfigFileManager
178
     */
179
    private $configFileManager;
180
181
    /**
182
     * @var RootModuleFileManager
183
     */
184
    private $rootModuleFileManager;
185
186
    /**
187
     * @var ModuleManager
188
     */
189
    private $moduleManager;
190
191
    /**
192
     * @var RepositoryManager
193
     */
194
    private $repositoryManager;
195
196
    /**
197
     * @var DiscoveryManager
198
     */
199
    private $discoveryManager;
200
201
    /**
202
     * @var AssetManager
203
     */
204
    private $assetManager;
205
206
    /**
207
     * @var InstallationManager
208
     */
209
    private $installationManager;
210
211
    /**
212
     * @var InstallerManager
213
     */
214
    private $installerManager;
215
216
    /**
217
     * @var ServerManager
218
     */
219
    private $serverManager;
220
221
    /**
222
     * @var UrlGenerator
223
     */
224
    private $urlGenerator;
225
226
    /**
227
     * @var Storage|null
228
     */
229
    private $storage;
230
231
    /**
232
     * @var JsonStorage|null
233
     */
234
    private $jsonStorage;
235
236
    /**
237
     * @var ConfigFileConverter|null
238
     */
239
    private $configFileConverter;
240
241
    /**
242
     * @var JsonConverter|null
243
     */
244
    private $moduleFileConverter;
245
246
    /**
247
     * @var JsonConverter|null
248
     */
249
    private $legacyModuleFileConverter;
250
251
    /**
252
     * @var JsonConverter|null
253
     */
254
    private $rootModuleFileConverter;
255
256
    /**
257
     * @var JsonConverter|null
258
     */
259
    private $legacyRootModuleFileConverter;
260
261
    /**
262
     * @var MigrationManager|null
263
     */
264
    private $moduleFileMigrationManager;
265
266
    /**
267
     * @var JsonEncoder
268
     */
269
    private $jsonEncoder;
270
271
    /**
272
     * @var JsonDecoder
273
     */
274
    private $jsonDecoder;
275
276
    /**
277
     * @var JsonValidator
278
     */
279
    private $jsonValidator;
280
281
    /**
282
     * @var JsonVersioner
283
     */
284
    private $jsonVersioner;
285
286
    /**
287
     * @var LoggerInterface
288
     */
289
    private $logger;
290
291
    /**
292
     * @var CacheFileConverter|null
293
     */
294
    private $cacheFileConverter;
295
296
    /**
297
     * @var CacheFileStorage|null
298
     */
299
    private $cacheFileStorage;
300
301
    /**
302
     * @var CacheManager|null
303
     */
304
    private $cacheManager;
305
306
    /**
307
     * @var bool
308
     */
309
    private $started = false;
310
311
    /**
312
     * @var bool
313
     */
314
    private $pluginsEnabled = true;
315
316
    /**
317
     * Parses the system context for a home directory.
318
     *
319
     * @return null|string Returns the path to the home directory or `null`
320
     *                     if none was found.
321
     */
322 52
    private static function parseHomeDirectory()
323
    {
324
        try {
325 52
            $homeDir = System::parseHomeDirectory();
326
327 49
            System::denyWebAccess($homeDir);
328
329 49
            return $homeDir;
330 3
        } catch (InvalidConfigException $e) {
331
            // Context variable was not found -> no home directory
332
            // This happens often on web servers where the home directory is
333
            // not set manually
334 2
            return null;
335
        }
336
    }
337
338
    /**
339
     * Creates a new instance for the given Puli project.
340
     *
341
     * @param string|null $rootDir The root directory of the Puli project.
342
     *                             If none is passed, the object operates in
343
     *                             the global context. You can set or switch
344
     *                             the root directories later on by calling
345
     *                             {@link setRootDirectory()}.
346
     * @param string      $env     One of the {@link Environment} constants.
347
     *
348
     * @see Puli, start()
349
     */
350 54
    public function __construct($rootDir = null, $env = Environment::DEV)
351
    {
352 54
        $this->setRootDirectory($rootDir);
353 54
        $this->setEnvironment($env);
354 54
    }
355
356
    /**
357
     * Starts the service container.
358
     */
359 52
    public function start()
360
    {
361 52
        if ($this->started) {
362
            throw new LogicException('Puli is already started');
363
        }
364
365 52
        if (null !== $this->rootDir) {
366 28
            $this->context = $this->createProjectContext($this->rootDir, $this->env);
367 28
            $bootstrapFile = $this->context->getConfig()->get(Config::BOOTSTRAP_FILE);
368
369
            // Run the project's bootstrap file to enable project-specific
370
            // autoloading
371 28
            if (null !== $bootstrapFile) {
372
                // Backup autoload functions of the PHAR
373 1
                $autoloadFunctions = spl_autoload_functions();
374
375 1
                foreach ($autoloadFunctions as $autoloadFunction) {
376 1
                    spl_autoload_unregister($autoloadFunction);
377
                }
378
379
                // Add project-specific autoload functions
380 1
                require_once Path::makeAbsolute($bootstrapFile, $this->rootDir);
381
382
                // Prepend autoload functions of the PHAR again
383
                // This is needed if the user specific autoload functions were
384
                // added with $prepend=true (as done by Composer)
385
                // Classes in the PHAR should always take precedence
386 28
                for ($i = count($autoloadFunctions) - 1; $i >= 0; --$i) {
387 1
                    spl_autoload_register($autoloadFunctions[$i], true, true);
388
                }
389
            }
390
        } else {
391 24
            $this->context = $this->createGlobalContext();
392
        }
393
394 51
        $this->dispatcher = $this->context->getEventDispatcher();
395 51
        $this->started = true;
396
397
        // Start plugins once the container is running
398 51
        if ($this->rootDir && $this->pluginsEnabled) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rootDir 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...
399 27
            $this->activatePlugins();
400
        }
401 49
    }
402
403
    /**
404
     * Returns whether the service container is started.
405
     *
406
     * @return bool Returns `true` if the container is started and `false`
407
     *              otherwise.
408
     */
409
    public function isStarted()
410
    {
411
        return $this->started;
412
    }
413
414
    /**
415
     * Sets the root directory of the managed Puli project.
416
     *
417
     * @param string|null $rootDir The root directory of the managed Puli
418
     *                             project or `null` to start Puli outside of a
419
     *                             specific project.
420
     */
421 54
    public function setRootDirectory($rootDir)
422
    {
423 54
        if ($this->started) {
424
            throw new LogicException('Puli is already started');
425
        }
426
427 54
        Assert::nullOrDirectory($rootDir);
428
429 54
        $this->rootDir = $rootDir ? Path::canonicalize($rootDir) : null;
430 54
    }
431
432
    /**
433
     * Sets the environment of the managed Puli project.
434
     *
435
     * @param string $env One of the {@link Environment} constants.
436
     */
437 54
    public function setEnvironment($env)
438
    {
439 54
        if ($this->started) {
440
            throw new LogicException('Puli is already started');
441
        }
442
443 54
        Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s');
444
445 54
        $this->env = $env;
446 54
    }
447
448
    /**
449
     * Retturns the environment of the managed Puli project.
450
     *
451
     * @return string One of the {@link Environment} constants.
452
     */
453 2
    public function getEnvironment()
454
    {
455 2
        return $this->env;
456
    }
457
458
    /**
459
     * Returns the root directory of the managed Puli project.
460
     *
461
     * If no Puli project is managed at the moment, `null` is returned.
462
     *
463
     * @return string|null The root directory of the managed Puli project or
464
     *                     `null` if none is set.
465
     */
466 4
    public function getRootDirectory()
467
    {
468 4
        return $this->rootDir;
469
    }
470
471
    /**
472
     * Sets the logger to use.
473
     *
474
     * @param LoggerInterface $logger The logger to use.
475
     */
476
    public function setLogger(LoggerInterface $logger)
477
    {
478
        if ($this->started) {
479
            throw new LogicException('Puli is already started');
480
        }
481
482
        $this->logger = $logger;
483
    }
484
485
    /**
486
     * Returns the logger.
487
     *
488
     * @return LoggerInterface The logger.
489
     */
490
    public function getLogger()
491
    {
492
        return $this->logger;
493
    }
494
495
    /**
496
     * Sets the event dispatcher to use.
497
     *
498
     * @param EventDispatcherInterface $dispatcher The event dispatcher to use.
499
     */
500 2
    public function setEventDispatcher(EventDispatcherInterface $dispatcher)
501
    {
502 2
        if ($this->started) {
503
            throw new LogicException('Puli is already started');
504
        }
505
506 2
        $this->dispatcher = $dispatcher;
507 2
    }
508
509
    /**
510
     * Returns the used event dispatcher.
511
     *
512
     * @return EventDispatcherInterface|null The used logger.
513
     */
514 3
    public function getEventDispatcher()
515
    {
516 3
        return $this->dispatcher;
517
    }
518
519
    /**
520
     * Enables all Puli plugins.
521
     */
522
    public function enablePlugins()
523
    {
524
        $this->pluginsEnabled = true;
525
    }
526
527
    /**
528
     * Disables all Puli plugins.
529
     */
530 1
    public function disablePlugins()
531
    {
532 1
        $this->pluginsEnabled = false;
533 1
    }
534
535
    /**
536
     * Returns whether Puli plugins are enabled.
537
     *
538
     * @return bool Returns `true` if Puli plugins will be loaded and `false`
539
     *              otherwise.
540
     */
541
    public function arePluginsEnabled()
542
    {
543
        return $this->pluginsEnabled;
544
    }
545
546
    /**
547
     * Returns the context.
548
     *
549
     * @return Context|ProjectContext The context.
550
     */
551 31
    public function getContext()
552
    {
553 31
        if (!$this->started) {
554
            throw new LogicException('Puli was not started');
555
        }
556
557 31
        return $this->context;
558
    }
559
560
    /**
561
     * Returns the resource repository of the project.
562
     *
563
     * @return EditableRepository The resource repository.
564
     */
565 8 View Code Duplication
    public function getRepository()
566
    {
567 8
        if (!$this->started) {
568
            throw new LogicException('Puli was not started');
569
        }
570
571 8
        if (!$this->context instanceof ProjectContext) {
572 1
            return null;
573
        }
574
575 7
        if (!$this->repo) {
576 7
            $this->repo = $this->getFactory()->createRepository();
577
        }
578
579 7
        return $this->repo;
580
    }
581
582
    /**
583
     * Returns the resource discovery of the project.
584
     *
585
     * @return EditableDiscovery The resource discovery.
586
     */
587 5 View Code Duplication
    public function getDiscovery()
588
    {
589 5
        if (!$this->started) {
590
            throw new LogicException('Puli was not started');
591
        }
592
593 5
        if (!$this->context instanceof ProjectContext) {
594 1
            return null;
595
        }
596
597 4
        if (!$this->discovery) {
598 4
            $this->discovery = $this->getFactory()->createDiscovery($this->getRepository());
599
        }
600
601 4
        return $this->discovery;
602
    }
603
604
    /**
605
     * @return object
606
     */
607 9
    public function getFactory()
608
    {
609 9
        if (!$this->started) {
610
            throw new LogicException('Puli was not started');
611
        }
612
613 9
        if (!$this->factory && $this->context instanceof ProjectContext) {
614 8
            $this->factory = $this->getFactoryManager()->createFactory();
615
        }
616
617 9
        return $this->factory;
618
    }
619
620
    /**
621
     * @return FactoryManager
622
     */
623 17
    public function getFactoryManager()
624
    {
625 17
        if (!$this->started) {
626
            throw new LogicException('Puli was not started');
627
        }
628
629 17
        if (!$this->factoryManager && $this->context instanceof ProjectContext) {
630 15
            $this->factoryManager = new FactoryManagerImpl(
631 15
                $this->context,
632 15
                new DefaultGeneratorRegistry(),
633 15
                new ClassWriter()
634
            );
635
636
            // Don't set via the constructor to prevent cyclic dependencies
637 15
            $this->factoryManager->setModules($this->getModuleManager()->getModules());
638 15
            $this->factoryManager->setServers($this->getServerManager()->getServers());
639
        }
640
641 17
        return $this->factoryManager;
642
    }
643
644
    /**
645
     * Returns the configuration file manager.
646
     *
647
     * @return ConfigFileManager The configuration file manager.
648
     */
649 2
    public function getConfigFileManager()
650
    {
651 2
        if (!$this->started) {
652
            throw new LogicException('Puli was not started');
653
        }
654
655 2
        if (!$this->configFileManager && $this->context->getHomeDirectory()) {
656 2
            $this->configFileManager = new ConfigFileManagerImpl(
657 2
                $this->context,
658 2
                $this->getJsonStorage()
659
            );
660
        }
661
662 2
        return $this->configFileManager;
663
    }
664
665
    /**
666
     * Returns the root module file manager.
667
     *
668
     * @return RootModuleFileManager The module file manager.
669
     */
670 16 View Code Duplication
    public function getRootModuleFileManager()
671
    {
672 16
        if (!$this->started) {
673
            throw new LogicException('Puli was not started');
674
        }
675
676 16
        if (!$this->rootModuleFileManager && $this->context instanceof ProjectContext) {
677 15
            $this->rootModuleFileManager = new RootModuleFileManagerImpl(
678 15
                $this->context,
679 15
                $this->getJsonStorage()
680
            );
681
        }
682
683 16
        return $this->rootModuleFileManager;
684
    }
685
686
    /**
687
     * Returns the module manager.
688
     *
689
     * @return ModuleManager The module manager.
690
     */
691 16 View Code Duplication
    public function getModuleManager()
692
    {
693 16
        if (!$this->started) {
694
            throw new LogicException('Puli was not started');
695
        }
696
697 16
        if (!$this->moduleManager && $this->context instanceof ProjectContext) {
698 15
            $this->moduleManager = new ModuleManagerImpl(
699 15
                $this->context,
700 15
                $this->getJsonStorage()
701
            );
702
        }
703
704 16
        return $this->moduleManager;
705
    }
706
707
    /**
708
     * Returns the resource repository manager.
709
     *
710
     * @return RepositoryManager The repository manager.
711
     */
712 2 View Code Duplication
    public function getRepositoryManager()
713
    {
714 2
        if (!$this->started) {
715
            throw new LogicException('Puli was not started');
716
        }
717
718 2
        if (!$this->repositoryManager && $this->context instanceof ProjectContext) {
719 1
            $this->repositoryManager = new RepositoryManagerImpl(
720 1
                $this->context,
721 1
                $this->getRepository(),
722 1
                $this->getModuleManager()->findModules(Expr::method('isEnabled', Expr::same(true))),
723 1
                $this->getJsonStorage()
724
            );
725
        }
726
727 2
        return $this->repositoryManager;
728
    }
729
730
    /**
731
     * Returns the resource discovery manager.
732
     *
733
     * @return DiscoveryManager The discovery manager.
734
     */
735 3 View Code Duplication
    public function getDiscoveryManager()
736
    {
737 3
        if (!$this->started) {
738
            throw new LogicException('Puli was not started');
739
        }
740
741 3
        if (!$this->discoveryManager && $this->context instanceof ProjectContext) {
742 2
            $this->discoveryManager = new DiscoveryManagerImpl(
743 2
                $this->context,
744 2
                $this->getDiscovery(),
745 2
                $this->getModuleManager()->findModules(Expr::method('isEnabled', Expr::same(true))),
746 2
                $this->getJsonStorage(),
747 2
                $this->logger
748
            );
749
        }
750
751 3
        return $this->discoveryManager;
752
    }
753
754
    /**
755
     * Returns the asset manager.
756
     *
757
     * @return AssetManager The asset manager.
758
     */
759 2
    public function getAssetManager()
760
    {
761 2
        if (!$this->started) {
762
            throw new LogicException('Puli was not started');
763
        }
764
765 2
        if (!$this->assetManager && $this->context instanceof ProjectContext) {
766 1
            $this->assetManager = new DiscoveryAssetManager(
767 1
                $this->getDiscoveryManager(),
768 1
                $this->getServerManager()->getServers()
769
            );
770
        }
771
772 2
        return $this->assetManager;
773
    }
774
775
    /**
776
     * Returns the installation manager.
777
     *
778
     * @return InstallationManager The installation manager.
779
     */
780 2
    public function getInstallationManager()
781
    {
782 2
        if (!$this->started) {
783
            throw new LogicException('Puli was not started');
784
        }
785
786 2
        if (!$this->installationManager && $this->context instanceof ProjectContext) {
787 1
            $this->installationManager = new InstallationManagerImpl(
788 1
                $this->getContext(),
789 1
                $this->getRepository(),
790 1
                $this->getServerManager()->getServers(),
791 1
                $this->getInstallerManager()
792
            );
793
        }
794
795 2
        return $this->installationManager;
796
    }
797
798
    /**
799
     * Returns the installer manager.
800
     *
801
     * @return InstallerManager The installer manager.
802
     */
803 16
    public function getInstallerManager()
804
    {
805 16
        if (!$this->started) {
806
            throw new LogicException('Puli was not started');
807
        }
808
809 16
        if (!$this->installerManager && $this->context instanceof ProjectContext) {
810 15
            $this->installerManager = new ModuleFileInstallerManager(
811 15
                $this->getRootModuleFileManager(),
812 15
                $this->getModuleManager()->getModules()
813
            );
814
        }
815
816 16
        return $this->installerManager;
817
    }
818
819
    /**
820
     * Returns the server manager.
821
     *
822
     * @return ServerManager The server manager.
823
     */
824 16
    public function getServerManager()
825
    {
826 16
        if (!$this->started) {
827
            throw new LogicException('Puli was not started');
828
        }
829
830 16
        if (!$this->serverManager && $this->context instanceof ProjectContext) {
831 15
            $this->serverManager = new ModuleFileServerManager(
832 15
                $this->getRootModuleFileManager(),
833 15
                $this->getInstallerManager()
834
            );
835
        }
836
837 16
        return $this->serverManager;
838
    }
839
840
    /**
841
     * Returns the resource URL generator.
842
     *
843
     * @return UrlGenerator The resource URL generator.
844
     */
845 2
    public function getUrlGenerator()
846
    {
847 2
        if (!$this->started) {
848
            throw new LogicException('Puli was not started');
849
        }
850
851 2
        if (!$this->urlGenerator && $this->context instanceof ProjectContext) {
852 1
            $urlFormats = array();
853 1
            foreach ($this->getServerManager()->getServers() as $server) {
854
                $urlFormats[$server->getName()] = $server->getUrlFormat();
855
            }
856
857 1
            $this->urlGenerator = new DiscoveryUrlGenerator($this->getDiscovery(), $urlFormats);
858
        }
859
860 2
        return $this->urlGenerator;
861
    }
862
863
    /**
864
     * Returns the file storage.
865
     *
866
     * @return Storage The storage.
867
     */
868 50
    public function getStorage()
869
    {
870 50
        if (!$this->storage) {
871 50
            $this->storage = new FilesystemStorage();
872
        }
873
874 50
        return $this->storage;
875
    }
876
877
    /**
878
     * Returns the configuration file serializer.
879
     *
880
     * @return ConfigFileConverter The configuration file serializer.
881
     */
882 48
    public function getConfigFileConverter()
883
    {
884 48
        if (!$this->configFileConverter) {
885 48
            $this->configFileConverter = new ConfigFileConverter();
886
        }
887
888 48
        return $this->configFileConverter;
889
    }
890
891
    /**
892
     * Returns the module file converter.
893
     *
894
     * @return JsonConverter The module file converter.
895
     */
896 15
    public function getModuleFileConverter()
897
    {
898 15
        if (!$this->moduleFileConverter) {
899 15
            $this->moduleFileConverter = $this->createValidatingConverter(
900 15
                new ModuleFileConverter($this->getJsonVersioner())
901
            );
902
        }
903
904 15
        return $this->moduleFileConverter;
905
    }
906
907
    /**
908
     * Returns the module file serializer with support for legacy versions.
909
     *
910
     * @return JsonConverter The module file converter.
911
     */
912 15 View Code Duplication
    public function getLegacyModuleFileConverter()
913
    {
914 15
        if (!$this->legacyModuleFileConverter) {
915 15
            $this->legacyModuleFileConverter = $this->createValidatingConverter(
916 15
                new MigratingConverter(
917 15
                    $this->getModuleFileConverter(),
918 15
                    ModuleFileConverter::VERSION,
919 15
                    $this->getModuleFileMigrationManager()
920
                ),
921
                function (stdClass $jsonData) {
922 15
                    if (isset($jsonData->{'$schema'})) {
923
                        return $jsonData->{'$schema'};
924
                    }
925
926
                    // BC with 1.0
927 15
                    return 'http://puli.io/schema/1.0/manager/module';
928 15
                }
929
            );
930
        }
931
932 15
        return $this->legacyModuleFileConverter;
933
    }
934
935
    /**
936
     * Returns the module file converter.
937
     *
938
     * @return JsonConverter The module file converter.
939
     */
940 27
    public function getRootModuleFileConverter()
941
    {
942 27
        if (!$this->rootModuleFileConverter) {
943 27
            $this->rootModuleFileConverter = $this->createValidatingConverter(
944 27
                new RootModuleFileConverter($this->getJsonVersioner())
945
            );
946
        }
947
948 27
        return $this->rootModuleFileConverter;
949
    }
950
951
    /**
952
     * Returns the module file serializer with support for legacy versions.
953
     *
954
     * @return JsonConverter The module file converter.
955
     */
956 27 View Code Duplication
    public function getLegacyRootModuleFileConverter()
957
    {
958 27
        if (!$this->legacyRootModuleFileConverter) {
959 27
            $this->legacyRootModuleFileConverter = $this->createValidatingConverter(
960 27
                new MigratingConverter(
961 27
                    $this->getRootModuleFileConverter(),
962 27
                    RootModuleFileConverter::VERSION,
963 27
                    $this->getModuleFileMigrationManager()
964
                ),
965 27
                function (stdClass $jsonData) {
966 27
                    if (isset($jsonData->{'$schema'})) {
967
                        return $jsonData->{'$schema'};
968
                    }
969
970
                    // BC with 1.0
971 27
                    return 'http://puli.io/schema/1.0/manager/module';
972 27
                }
973
            );
974
        }
975
976 27
        return $this->legacyRootModuleFileConverter;
977
    }
978
979
    /**
980
     * Returns the JSON encoder.
981
     *
982
     * @return JsonEncoder The JSON encoder.
983
     */
984 50
    public function getJsonEncoder()
985
    {
986 50
        if (!$this->jsonEncoder) {
987 50
            $this->jsonEncoder = new JsonEncoder();
988 50
            $this->jsonEncoder->setPrettyPrinting(true);
989 50
            $this->jsonEncoder->setEscapeSlash(false);
990 50
            $this->jsonEncoder->setTerminateWithLineFeed(true);
991
        }
992
993 50
        return $this->jsonEncoder;
994
    }
995
996
    /**
997
     * Returns the JSON decoder.
998
     *
999
     * @return JsonDecoder The JSON decoder.
1000
     */
1001 50
    public function getJsonDecoder()
1002
    {
1003 50
        if (!$this->jsonDecoder) {
1004 50
            $this->jsonDecoder = new JsonDecoder();
1005
        }
1006
1007 50
        return $this->jsonDecoder;
1008
    }
1009
1010
    /**
1011
     * Returns the JSON validator.
1012
     *
1013
     * @return JsonValidator The JSON validator.
1014
     */
1015 27
    public function getJsonValidator()
1016
    {
1017 27
        if (!$this->jsonValidator) {
1018 27
            $uriRetriever = new UriRetriever();
1019
1020
            // Load puli.io schemas from the schema/ directory
1021 27
            $uriRetriever->setUriRetriever(new LocalUriRetriever());
1022
1023 27
            $this->jsonValidator = new JsonValidator(null, $uriRetriever);
1024
        }
1025
1026 27
        return $this->jsonValidator;
1027
    }
1028
1029
    /**
1030
     * Returns the cache file converter.
1031
     *
1032
     * @return CacheFileConverter The cache file converter.
1033
     */
1034 1
    public function getCacheFileConverter()
1035
    {
1036 1
        if (!$this->cacheFileConverter) {
1037 1
            $this->cacheFileConverter = new CacheFileConverter(new ModuleFileConverter(
1038 1
                $this->getJsonVersioner()
1039
            ));
1040
        }
1041
1042 1
        return $this->cacheFileConverter;
1043
    }
1044
1045
    /**
1046
     * Returns the cache file storage.
1047
     *
1048
     * @return CacheFileStorage The cache file storage.
1049
     */
1050 1
    public function getCacheFileStorage()
1051
    {
1052 1
        if (!$this->cacheFileStorage) {
1053 1
            $this->cacheFileStorage = new CacheFileStorage(
1054 1
                $this->getStorage(),
1055 1
                $this->getCacheFileConverter(),
1056 1
                $this->getJsonEncoder(),
1057 1
                $this->getJsonDecoder()
1058
            );
1059
        }
1060
1061 1
        return $this->cacheFileStorage;
1062
    }
1063
1064
    /**
1065
     * Returns the cached configuration manager.
1066
     *
1067
     * @return CacheManager The cached configuration manager.
1068
     */
1069 2
    public function getCacheManager()
1070
    {
1071 2
        if (!$this->cacheManager && $this->context instanceof ProjectContext) {
1072 1
            $this->cacheManager = new CacheManagerImpl(
1073 1
                $this->getModuleManager(),
1074 1
                $this->getCacheFileStorage(),
1075 1
                $this->context
1076
            );
1077
        }
1078
1079 2
        return $this->cacheManager;
1080
    }
1081
1082 27
    private function activatePlugins()
1083
    {
1084 27
        foreach ($this->context->getRootModuleFile()->getPluginClasses() as $pluginClass) {
0 ignored issues
show
The method getRootModuleFile does only exist in Puli\Manager\Api\Context\ProjectContext, but not in Puli\Manager\Api\Context\Context.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1085 26
            $this->validatePluginClass($pluginClass);
1086
1087
            /** @var PuliPlugin $plugin */
1088 24
            $plugin = new $pluginClass();
1089 24
            $plugin->activate($this);
1090
        }
1091 25
    }
1092
1093 24
    private function createGlobalContext()
1094
    {
1095 24
        $baseConfig = new DefaultConfig();
1096 24
        $homeDir = self::parseHomeDirectory();
1097
1098 23
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
1099 21
            $baseConfig = $configFile->getConfig();
1100
        }
1101
1102 23
        $config = new EnvConfig($baseConfig);
1103
1104 23
        return new Context($homeDir, $config, $configFile, $this->dispatcher);
1105
    }
1106
1107
    /**
1108
     * Creates the context of a Puli project.
1109
     *
1110
     * The home directory is read from the context variable "PULI_HOME".
1111
     * If this variable is not set, the home directory defaults to:
1112
     *
1113
     *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
1114
     *    "HOME".
1115
     *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
1116
     *    variable "APPDATA".
1117
     *
1118
     * If none of these variables can be found, an exception is thrown.
1119
     *
1120
     * A .htaccess file is put into the home directory to protect it from web
1121
     * access.
1122
     *
1123
     * @param string $rootDir The path to the project.
1124
     *
1125
     * @return ProjectContext The project context.
1126
     */
1127 28
    private function createProjectContext($rootDir, $env)
1128
    {
1129 28
        Assert::fileExists($rootDir, 'Could not load Puli context: The root %s does not exist.');
1130 28
        Assert::directory($rootDir, 'Could not load Puli context: The root %s is a file. Expected a directory.');
1131
1132 28
        $baseConfig = new DefaultConfig();
1133 28
        $homeDir = self::parseHomeDirectory();
1134
1135 28
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
1136 27
            $baseConfig = $configFile->getConfig();
1137
        }
1138
1139
        // Create a storage without the factory manager
1140 28
        $jsonStorage = new JsonStorage(
1141 28
            $this->getStorage(),
1142 28
            new JsonConverterProvider($this),
1143 28
            $this->getJsonEncoder(),
1144 28
            $this->getJsonDecoder()
1145
        );
1146
1147 28
        $rootDir = Path::canonicalize($rootDir);
1148 28
        $rootFilePath = $this->rootDir.'/puli.json';
1149
1150
        try {
1151 28
            $rootModuleFile = $jsonStorage->loadRootModuleFile($rootFilePath, $baseConfig);
1152 1
        } catch (FileNotFoundException $e) {
1153 1
            $rootModuleFile = new RootModuleFile(null, $rootFilePath, $baseConfig);
1154
        }
1155
1156 28
        $config = new EnvConfig($rootModuleFile->getConfig());
1157
1158 28
        return new ProjectContext($homeDir, $rootDir, $config, $rootModuleFile, $configFile, $this->dispatcher, $env);
1159
    }
1160
1161
    /**
1162
     * Decorates a converter with a {@link ValidatingConverter}.
1163
     *
1164
     * @param JsonConverter        $innerConverter The converter to decorate.
1165
     * @param string|callable|null $schema         The schema.
1166
     *
1167
     * @return ValidatingConverter The decorated converter.
1168
     */
1169 27
    private function createValidatingConverter(JsonConverter $innerConverter, $schema = null)
1170
    {
1171 27
        return new ValidatingConverter($innerConverter, $schema, $this->getJsonValidator());
1172
    }
1173
1174
    /**
1175
     * Returns the JSON file storage.
1176
     *
1177
     * @return JsonStorage The JSON file storage.
1178
     */
1179 16
    private function getJsonStorage()
1180
    {
1181 16
        if (!$this->jsonStorage) {
1182 16
            $this->jsonStorage = new JsonStorage(
1183 16
                $this->getStorage(),
1184 16
                new JsonConverterProvider($this),
1185 16
                $this->getJsonEncoder(),
1186 16
                $this->getJsonDecoder(),
1187 16
                $this->getFactoryManager()
1188
            );
1189
        }
1190
1191 16
        return $this->jsonStorage;
1192
    }
1193
1194
    /**
1195
     * Returns the JSON versioner.
1196
     *
1197
     * @return JsonVersioner The JSON versioner.
1198
     */
1199 27
    private function getJsonVersioner()
1200
    {
1201 27
        if (!$this->jsonVersioner) {
1202 27
            $this->jsonVersioner = new ChainVersioner(array(
1203
                // check the schema of the "$schema" field by default
1204 27
                new SchemaUriVersioner(),
1205
                // fall back to the "version" field for 1.0
1206 27
                new VersionFieldVersioner(),
1207
            ));
1208
        }
1209
1210 27
        return $this->jsonVersioner;
1211
    }
1212
1213
    /**
1214
     * Returns the migration manager for module files.
1215
     *
1216
     * @return MigrationManager The migration manager.
1217
     */
1218 27
    private function getModuleFileMigrationManager()
1219
    {
1220 27
        if (!$this->moduleFileMigrationManager) {
1221 27
            $this->moduleFileMigrationManager = new MigrationManager(array(
1222 27
                new ModuleFile10To20Migration(),
1223 27
            ), $this->getJsonVersioner());
1224
        }
1225
1226 27
        return $this->moduleFileMigrationManager;
1227
    }
1228
1229
    /**
1230
     * Validates the given plugin class name.
1231
     *
1232
     * @param string $pluginClass The fully qualified name of a plugin class.
1233
     */
1234 26
    private function validatePluginClass($pluginClass)
1235
    {
1236 26
        if (!class_exists($pluginClass)) {
1237 1
            throw new InvalidConfigException(sprintf(
1238 1
                'The plugin class %s does not exist.',
1239
                $pluginClass
1240
            ));
1241
        }
1242
1243 25
        if (!in_array('Puli\Manager\Api\PuliPlugin', class_implements($pluginClass))) {
1244 1
            throw new InvalidConfigException(sprintf(
1245 1
                'The plugin class %s must implement PuliPlugin.',
1246
                $pluginClass
1247
            ));
1248
        }
1249 24
    }
1250
1251 51
    private function loadConfigFile($homeDir, Config $baseConfig)
1252
    {
1253 51
        if (null === $homeDir) {
1254 2
            return null;
1255
        }
1256
1257 49
        Assert::fileExists($homeDir, 'Could not load Puli context: The home directory %s does not exist.');
1258 49
        Assert::directory($homeDir, 'Could not load Puli context: The home directory %s is a file. Expected a directory.');
1259
1260
        // Create a storage without the factory manager
1261 49
        $jsonStorage = new JsonStorage(
1262 49
            $this->getStorage(),
1263 49
            new JsonConverterProvider($this),
1264 49
            $this->getJsonEncoder(),
1265 49
            $this->getJsonDecoder()
1266
        );
1267
1268 49
        $configPath = Path::canonicalize($homeDir).'/config.json';
1269
1270
        try {
1271 49
            return $jsonStorage->loadConfigFile($configPath, $baseConfig);
1272 1
        } catch (FileNotFoundException $e) {
1273
            // It's ok if no config.json exists. We'll work with
1274
            // DefaultConfig instead
1275 1
            return null;
1276
        }
1277
    }
1278
}
1279