Failed Conditions
Pull Request — master (#41)
by Bernhard
12:50
created

src/Api/Puli.php (1 issue)

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