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

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