Failed Conditions
Pull Request — master (#49)
by Bernhard
22:36 queued 12:57
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\Config\Config;
20
use Puli\Manager\Api\Config\ConfigFileManager;
21
use Puli\Manager\Api\Context\Context;
22
use Puli\Manager\Api\Context\ProjectContext;
23
use Puli\Manager\Api\Discovery\DiscoveryManager;
24
use Puli\Manager\Api\Factory\FactoryManager;
25
use Puli\Manager\Api\Installation\InstallationManager;
26
use Puli\Manager\Api\Installer\InstallerManager;
27
use Puli\Manager\Api\Package\PackageManager;
28
use Puli\Manager\Api\Package\RootPackageFile;
29
use Puli\Manager\Api\Package\RootPackageFileManager;
30
use Puli\Manager\Api\Repository\RepositoryManager;
31
use Puli\Manager\Api\Server\ServerManager;
32
use Puli\Manager\Api\Storage\Storage;
33
use Puli\Manager\Assert\Assert;
34
use Puli\Manager\Asset\DiscoveryAssetManager;
35
use Puli\Manager\Config\ConfigFileConverter;
36
use Puli\Manager\Config\ConfigFileManagerImpl;
37
use Puli\Manager\Config\ConfigFileStorage;
38
use Puli\Manager\Config\DefaultConfig;
39
use Puli\Manager\Config\EnvConfig;
40
use Puli\Manager\Discovery\DiscoveryManagerImpl;
41
use Puli\Manager\Factory\FactoryManagerImpl;
42
use Puli\Manager\Factory\Generator\DefaultGeneratorRegistry;
43
use Puli\Manager\Filesystem\FilesystemStorage;
44
use Puli\Manager\Installation\InstallationManagerImpl;
45
use Puli\Manager\Installer\PackageFileInstallerManager;
46
use Puli\Manager\Package\PackageFileConverter;
47
use Puli\Manager\Package\PackageFileStorage;
48
use Puli\Manager\Package\PackageManagerImpl;
49
use Puli\Manager\Package\RootPackageFileConverter;
50
use Puli\Manager\Package\RootPackageFileManagerImpl;
51
use Puli\Manager\Php\ClassWriter;
52
use Puli\Manager\Repository\RepositoryManagerImpl;
53
use Puli\Manager\Server\PackageFileServerManager;
54
use Puli\Manager\Util\System;
55
use Puli\Repository\Api\EditableRepository;
56
use Puli\Repository\Api\ResourceRepository;
57
use Puli\UrlGenerator\Api\UrlGenerator;
58
use Puli\UrlGenerator\DiscoveryUrlGenerator;
59
use stdClass;
60
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
61
use Webmozart\Expression\Expr;
62
use Webmozart\Json\Conversion\JsonConverter;
63
use Webmozart\Json\JsonDecoder;
64
use Webmozart\Json\JsonEncoder;
65
use Webmozart\Json\Migration\MigratingConverter;
66
use Webmozart\Json\Migration\MigrationManager;
67
use Webmozart\Json\Validation\ValidatingConverter;
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 ConfigFileConverter|null
226
     */
227
    private $configFileConverter;
228
229
    /**
230
     * @var PackageFileStorage|null
231
     */
232
    private $packageFileStorage;
233
234
    /**
235
     * @var JsonConverter|null
236
     */
237
    private $packageFileConverter;
238
239
    /**
240
     * @var JsonConverter|null
241
     */
242
    private $legacyPackageFileConverter;
243
244
    /**
245
     * @var JsonConverter|null
246
     */
247
    private $rootPackageFileConverter;
248
249
    /**
250
     * @var JsonConverter|null
251
     */
252
    private $legacyRootPackageFileConverter;
253
254
    /**
255
     * @var JsonEncoder
256
     */
257
    private $jsonEncoder;
258
259
    /**
260
     * @var JsonDecoder
261
     */
262
    private $jsonDecoder;
263
264
    /**
265
     * @var LoggerInterface
266
     */
267
    private $logger;
268
269
    /**
270
     * @var bool
271
     */
272
    private $started = false;
273
274
    /**
275
     * @var bool
276
     */
277
    private $pluginsEnabled = true;
278
279
    /**
280
     * Parses the system context for a home directory.
281
     *
282
     * @return null|string Returns the path to the home directory or `null`
283
     *                     if none was found.
284
     */
285 50
    private static function parseHomeDirectory()
286
    {
287
        try {
288 50
            $homeDir = System::parseHomeDirectory();
289
290 47
            System::denyWebAccess($homeDir);
291
292 47
            return $homeDir;
293 3
        } catch (InvalidConfigException $e) {
294
            // Context variable was not found -> no home directory
295
            // This happens often on web servers where the home directory is
296
            // not set manually
297 2
            return null;
298
        }
299
    }
300
301
    /**
302
     * Creates a new instance for the given Puli project.
303
     *
304
     * @param string|null $rootDir The root directory of the Puli project.
305
     *                             If none is passed, the object operates in
306
     *                             the global context. You can set or switch
307
     *                             the root directories later on by calling
308
     *                             {@link setRootDirectory()}.
309
     * @param string      $env     One of the {@link Environment} constants.
310
     *
311
     * @see Puli, start()
312
     */
313 52
    public function __construct($rootDir = null, $env = Environment::DEV)
314
    {
315 52
        $this->setRootDirectory($rootDir);
316 52
        $this->setEnvironment($env);
317 52
    }
318
319
    /**
320
     * Starts the service container.
321
     */
322 50
    public function start()
323
    {
324 50
        if ($this->started) {
325
            throw new LogicException('Puli is already started');
326
        }
327
328 50
        if (null !== $this->rootDir) {
329 27
            $this->context = $this->createProjectContext($this->rootDir, $this->env);
330 27
            $bootstrapFile = $this->context->getConfig()->get(Config::BOOTSTRAP_FILE);
331
332
            // Run the project's bootstrap file to enable project-specific
333
            // autoloading
334 27
            if (null !== $bootstrapFile) {
335
                // Backup autoload functions of the PHAR
336 1
                $autoloadFunctions = spl_autoload_functions();
337
338 1
                foreach ($autoloadFunctions as $autoloadFunction) {
339 1
                    spl_autoload_unregister($autoloadFunction);
340
                }
341
342
                // Add project-specific autoload functions
343 1
                require_once Path::makeAbsolute($bootstrapFile, $this->rootDir);
344
345
                // Prepend autoload functions of the PHAR again
346
                // This is needed if the user specific autoload functions were
347
                // added with $prepend=true (as done by Composer)
348
                // Classes in the PHAR should always take precedence
349 27
                for ($i = count($autoloadFunctions) - 1; $i >= 0; --$i) {
350 1
                    spl_autoload_register($autoloadFunctions[$i], true, true);
351
                }
352
            }
353
        } else {
354 23
            $this->context = $this->createGlobalContext();
355
        }
356
357 49
        $this->dispatcher = $this->context->getEventDispatcher();
358 49
        $this->started = true;
359
360
        // Start plugins once the container is running
361 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...
362 26
            $this->activatePlugins();
363
        }
364 47
    }
365
366
    /**
367
     * Returns whether the service container is started.
368
     *
369
     * @return bool Returns `true` if the container is started and `false`
370
     *              otherwise.
371
     */
372
    public function isStarted()
373
    {
374
        return $this->started;
375
    }
376
377
    /**
378
     * Sets the root directory of the managed Puli project.
379
     *
380
     * @param string|null $rootDir The root directory of the managed Puli
381
     *                             project or `null` to start Puli outside of a
382
     *                             specific project.
383
     */
384 52
    public function setRootDirectory($rootDir)
385
    {
386 52
        if ($this->started) {
387
            throw new LogicException('Puli is already started');
388
        }
389
390 52
        Assert::nullOrDirectory($rootDir);
391
392 52
        $this->rootDir = $rootDir ? Path::canonicalize($rootDir) : null;
393 52
    }
394
395
    /**
396
     * Sets the environment of the managed Puli project.
397
     *
398
     * @param string $env One of the {@link Environment} constants.
399
     */
400 52
    public function setEnvironment($env)
401
    {
402 52
        if ($this->started) {
403
            throw new LogicException('Puli is already started');
404
        }
405
406 52
        Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s');
407
408 52
        $this->env = $env;
409 52
    }
410
411
    /**
412
     * Retturns the environment of the managed Puli project.
413
     *
414
     * @return string One of the {@link Environment} constants.
415
     */
416 2
    public function getEnvironment()
417
    {
418 2
        return $this->env;
419
    }
420
421
    /**
422
     * Returns the root directory of the managed Puli project.
423
     *
424
     * If no Puli project is managed at the moment, `null` is returned.
425
     *
426
     * @return string|null The root directory of the managed Puli project or
427
     *                     `null` if none is set.
428
     */
429 4
    public function getRootDirectory()
430
    {
431 4
        return $this->rootDir;
432
    }
433
434
    /**
435
     * Sets the logger to use.
436
     *
437
     * @param LoggerInterface $logger The logger to use.
438
     */
439
    public function setLogger(LoggerInterface $logger)
440
    {
441
        if ($this->started) {
442
            throw new LogicException('Puli is already started');
443
        }
444
445
        $this->logger = $logger;
446
    }
447
448
    /**
449
     * Returns the logger.
450
     *
451
     * @return LoggerInterface The logger.
452
     */
453
    public function getLogger()
454
    {
455
        return $this->logger;
456
    }
457
458
    /**
459
     * Sets the event dispatcher to use.
460
     *
461
     * @param EventDispatcherInterface $dispatcher The event dispatcher to use.
462
     */
463 2
    public function setEventDispatcher(EventDispatcherInterface $dispatcher)
464
    {
465 2
        if ($this->started) {
466
            throw new LogicException('Puli is already started');
467
        }
468
469 2
        $this->dispatcher = $dispatcher;
470 2
    }
471
472
    /**
473
     * Returns the used event dispatcher.
474
     *
475
     * @return EventDispatcherInterface|null The used logger.
476
     */
477 3
    public function getEventDispatcher()
478
    {
479 3
        return $this->dispatcher;
480
    }
481
482
    /**
483
     * Enables all Puli plugins.
484
     */
485
    public function enablePlugins()
486
    {
487
        $this->pluginsEnabled = true;
488
    }
489
490
    /**
491
     * Disables all Puli plugins.
492
     */
493 1
    public function disablePlugins()
494
    {
495 1
        $this->pluginsEnabled = false;
496 1
    }
497
498
    /**
499
     * Returns whether Puli plugins are enabled.
500
     *
501
     * @return bool Returns `true` if Puli plugins will be loaded and `false`
502
     *              otherwise.
503
     */
504
    public function arePluginsEnabled()
505
    {
506
        return $this->pluginsEnabled;
507
    }
508
509
    /**
510
     * Returns the context.
511
     *
512
     * @return Context|ProjectContext The context.
513
     */
514 30
    public function getContext()
515
    {
516 30
        if (!$this->started) {
517
            throw new LogicException('Puli was not started');
518
        }
519
520 30
        return $this->context;
521
    }
522
523
    /**
524
     * Returns the resource repository of the project.
525
     *
526
     * @return EditableRepository The resource repository.
527
     */
528 8 View Code Duplication
    public function getRepository()
529
    {
530 8
        if (!$this->started) {
531
            throw new LogicException('Puli was not started');
532
        }
533
534 8
        if (!$this->context instanceof ProjectContext) {
535 1
            return null;
536
        }
537
538 7
        if (!$this->repo) {
539 7
            $this->repo = $this->getFactory()->createRepository();
540
        }
541
542 7
        return $this->repo;
543
    }
544
545
    /**
546
     * Returns the resource discovery of the project.
547
     *
548
     * @return EditableDiscovery The resource discovery.
549
     */
550 5 View Code Duplication
    public function getDiscovery()
551
    {
552 5
        if (!$this->started) {
553
            throw new LogicException('Puli was not started');
554
        }
555
556 5
        if (!$this->context instanceof ProjectContext) {
557 1
            return null;
558
        }
559
560 4
        if (!$this->discovery) {
561 4
            $this->discovery = $this->getFactory()->createDiscovery($this->getRepository());
562
        }
563
564 4
        return $this->discovery;
565
    }
566
567
    /**
568
     * @return object
569
     */
570 9
    public function getFactory()
571
    {
572 9
        if (!$this->started) {
573
            throw new LogicException('Puli was not started');
574
        }
575
576 9
        if (!$this->factory && $this->context instanceof ProjectContext) {
577 8
            $this->factory = $this->getFactoryManager()->createFactory();
578
        }
579
580 9
        return $this->factory;
581
    }
582
583
    /**
584
     * @return FactoryManager
585
     */
586 16
    public function getFactoryManager()
587
    {
588 16
        if (!$this->started) {
589
            throw new LogicException('Puli was not started');
590
        }
591
592 16
        if (!$this->factoryManager && $this->context instanceof ProjectContext) {
593 14
            $this->factoryManager = new FactoryManagerImpl(
594 14
                $this->context,
595 14
                new DefaultGeneratorRegistry(),
596 14
                new ClassWriter()
597
            );
598
599
            // Don't set via the constructor to prevent cyclic dependencies
600 14
            $this->factoryManager->setPackages($this->getPackageManager()->getPackages());
601 14
            $this->factoryManager->setServers($this->getServerManager()->getServers());
602
        }
603
604 16
        return $this->factoryManager;
605
    }
606
607
    /**
608
     * Returns the configuration file manager.
609
     *
610
     * @return ConfigFileManager The configuration file manager.
611
     */
612 2
    public function getConfigFileManager()
613
    {
614 2
        if (!$this->started) {
615
            throw new LogicException('Puli was not started');
616
        }
617
618 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...
619 2
            $this->configFileManager = new ConfigFileManagerImpl(
620 2
                $this->context,
621 2
                $this->getConfigFileStorage()
622
            );
623
        }
624
625 2
        return $this->configFileManager;
626
    }
627
628
    /**
629
     * Returns the root package file manager.
630
     *
631
     * @return RootPackageFileManager The package file manager.
632
     */
633 15 View Code Duplication
    public function getRootPackageFileManager()
634
    {
635 15
        if (!$this->started) {
636
            throw new LogicException('Puli was not started');
637
        }
638
639 15
        if (!$this->rootPackageFileManager && $this->context instanceof ProjectContext) {
640 14
            $this->rootPackageFileManager = new RootPackageFileManagerImpl(
641 14
                $this->context,
642 14
                $this->getPackageFileStorage()
643
            );
644
        }
645
646 15
        return $this->rootPackageFileManager;
647
    }
648
649
    /**
650
     * Returns the package manager.
651
     *
652
     * @return PackageManager The package manager.
653
     */
654 15 View Code Duplication
    public function getPackageManager()
655
    {
656 15
        if (!$this->started) {
657
            throw new LogicException('Puli was not started');
658
        }
659
660 15
        if (!$this->packageManager && $this->context instanceof ProjectContext) {
661 14
            $this->packageManager = new PackageManagerImpl(
662 14
                $this->context,
663 14
                $this->getPackageFileStorage()
664
            );
665
        }
666
667 15
        return $this->packageManager;
668
    }
669
670
    /**
671
     * Returns the resource repository manager.
672
     *
673
     * @return RepositoryManager The repository manager.
674
     */
675 2 View Code Duplication
    public function getRepositoryManager()
676
    {
677 2
        if (!$this->started) {
678
            throw new LogicException('Puli was not started');
679
        }
680
681 2
        if (!$this->repositoryManager && $this->context instanceof ProjectContext) {
682 1
            $this->repositoryManager = new RepositoryManagerImpl(
683 1
                $this->context,
684 1
                $this->getRepository(),
685 1
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
686 1
                $this->getPackageFileStorage()
687
            );
688
        }
689
690 2
        return $this->repositoryManager;
691
    }
692
693
    /**
694
     * Returns the resource discovery manager.
695
     *
696
     * @return DiscoveryManager The discovery manager.
697
     */
698 3 View Code Duplication
    public function getDiscoveryManager()
699
    {
700 3
        if (!$this->started) {
701
            throw new LogicException('Puli was not started');
702
        }
703
704 3
        if (!$this->discoveryManager && $this->context instanceof ProjectContext) {
705 2
            $this->discoveryManager = new DiscoveryManagerImpl(
706 2
                $this->context,
707 2
                $this->getDiscovery(),
708 2
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
709 2
                $this->getPackageFileStorage(),
710 2
                $this->logger
711
            );
712
        }
713
714 3
        return $this->discoveryManager;
715
    }
716
717
    /**
718
     * Returns the asset manager.
719
     *
720
     * @return AssetManager The asset manager.
721
     */
722 2
    public function getAssetManager()
723
    {
724 2
        if (!$this->started) {
725
            throw new LogicException('Puli was not started');
726
        }
727
728 2
        if (!$this->assetManager && $this->context instanceof ProjectContext) {
729 1
            $this->assetManager = new DiscoveryAssetManager(
730 1
                $this->getDiscoveryManager(),
731 1
                $this->getServerManager()->getServers()
732
            );
733
        }
734
735 2
        return $this->assetManager;
736
    }
737
738
    /**
739
     * Returns the installation manager.
740
     *
741
     * @return InstallationManager The installation manager.
742
     */
743 2
    public function getInstallationManager()
744
    {
745 2
        if (!$this->started) {
746
            throw new LogicException('Puli was not started');
747
        }
748
749 2
        if (!$this->installationManager && $this->context instanceof ProjectContext) {
750 1
            $this->installationManager = new InstallationManagerImpl(
751 1
                $this->getContext(),
752 1
                $this->getRepository(),
753 1
                $this->getServerManager()->getServers(),
754 1
                $this->getInstallerManager()
755
            );
756
        }
757
758 2
        return $this->installationManager;
759
    }
760
761
    /**
762
     * Returns the installer manager.
763
     *
764
     * @return InstallerManager The installer manager.
765
     */
766 15
    public function getInstallerManager()
767
    {
768 15
        if (!$this->started) {
769
            throw new LogicException('Puli was not started');
770
        }
771
772 15
        if (!$this->installerManager && $this->context instanceof ProjectContext) {
773 14
            $this->installerManager = new PackageFileInstallerManager(
774 14
                $this->getRootPackageFileManager(),
775 14
                $this->getPackageManager()->getPackages()
776
            );
777
        }
778
779 15
        return $this->installerManager;
780
    }
781
782
    /**
783
     * Returns the server manager.
784
     *
785
     * @return ServerManager The server manager.
786
     */
787 15
    public function getServerManager()
788
    {
789 15
        if (!$this->started) {
790
            throw new LogicException('Puli was not started');
791
        }
792
793 15
        if (!$this->serverManager && $this->context instanceof ProjectContext) {
794 14
            $this->serverManager = new PackageFileServerManager(
795 14
                $this->getRootPackageFileManager(),
796 14
                $this->getInstallerManager()
797
            );
798
        }
799
800 15
        return $this->serverManager;
801
    }
802
803
    /**
804
     * Returns the resource URL generator.
805
     *
806
     * @return UrlGenerator The resource URL generator.
807
     */
808 2
    public function getUrlGenerator()
809
    {
810 2
        if (!$this->started) {
811
            throw new LogicException('Puli was not started');
812
        }
813
814 2
        if (!$this->urlGenerator && $this->context instanceof ProjectContext) {
815 1
            $urlFormats = array();
816 1
            foreach ($this->getServerManager()->getServers() as $server) {
817
                $urlFormats[$server->getName()] = $server->getUrlFormat();
818
            }
819
820 1
            $this->urlGenerator = new DiscoveryUrlGenerator($this->getDiscovery(), $urlFormats);
821
        }
822
823 2
        return $this->urlGenerator;
824
    }
825
826
    /**
827
     * Returns the file storage.
828
     *
829
     * @return Storage The storage.
830
     */
831 48
    public function getStorage()
832
    {
833 48
        if (!$this->storage) {
834 48
            $this->storage = new FilesystemStorage();
835
        }
836
837 48
        return $this->storage;
838
    }
839
840
    /**
841
     * Returns the configuration file serializer.
842
     *
843
     * @return ConfigFileConverter The configuration file serializer.
844
     */
845 47
    public function getConfigFileConverter()
846
    {
847 47
        if (!$this->configFileConverter) {
848 47
            $this->configFileConverter = new ConfigFileConverter();
849
        }
850
851 47
        return $this->configFileConverter;
852
    }
853
854
    /**
855
     * Returns the package file converter.
856
     *
857
     * @return JsonConverter The package file converter.
858
     */
859 27
    public function getPackageFileConverter()
860
    {
861 27
        if (!$this->packageFileConverter) {
862 27
            $this->packageFileConverter = new ValidatingConverter(
863 27
                new PackageFileConverter(),
864 27
                __DIR__.'/../../res/schema/package-schema-'.PackageFileConverter::VERSION.'.json'
865
            );
866
        }
867
868 27
        return $this->packageFileConverter;
869
    }
870
871
    /**
872
     * Returns the package file serializer with support for legacy versions.
873
     *
874
     * @return JsonConverter The package file converter.
875
     */
876 27 View Code Duplication
    public function getLegacyPackageFileConverter()
877
    {
878 27
        if (!$this->legacyPackageFileConverter) {
879 27
            $this->legacyPackageFileConverter = new ValidatingConverter(
880 27
                new MigratingConverter(
881 27
                    $this->getPackageFileConverter(),
882 27
                    PackageFileConverter::VERSION,
883 27
                    new MigrationManager(array(
884
                        // add future migrations here
885 27
                    ))
886
                ),
887
                function (stdClass $jsonData) {
888 14
                    return __DIR__.'/../../res/schema/package-schema-'.$jsonData->version.'.json';
889 27
                }
890
            );
891
        }
892
893 27
        return $this->legacyPackageFileConverter;
894
    }
895
896
    /**
897
     * Returns the package file converter.
898
     *
899
     * @return JsonConverter The package file converter.
900
     */
901 27
    public function getRootPackageFileConverter()
902
    {
903 27
        if (!$this->rootPackageFileConverter) {
904 27
            $this->rootPackageFileConverter = new ValidatingConverter(
905 27
                new RootPackageFileConverter(),
906 27
                __DIR__.'/../../res/schema/package-schema-'.RootPackageFileConverter::VERSION.'.json'
907
            );
908
        }
909
910 27
        return $this->rootPackageFileConverter;
911
    }
912
913
    /**
914
     * Returns the package file serializer with support for legacy versions.
915
     *
916
     * @return JsonConverter The package file converter.
917
     */
918 27 View Code Duplication
    public function getLegacyRootPackageFileConverter()
919
    {
920 27
        if (!$this->legacyRootPackageFileConverter) {
921 27
            $this->legacyRootPackageFileConverter = new ValidatingConverter(
922 27
                new MigratingConverter(
923 27
                    $this->getRootPackageFileConverter(),
924 27
                    RootPackageFileConverter::VERSION,
925 27
                    new MigrationManager(array(
926
                        // add future migrations here
927 27
                    ))
928
                ),
929 27
                function (stdClass $jsonData) {
930 26
                    return __DIR__.'/../../res/schema/package-schema-'.$jsonData->version.'.json';
931 27
                }
932
            );
933
        }
934
935 27
        return $this->legacyRootPackageFileConverter;
936
    }
937
938
    /**
939
     * Returns the JSON encoder.
940
     *
941
     * @return JsonEncoder The JSON encoder.
942
     */
943 48
    public function getJsonEncoder()
944
    {
945 48
        if (!$this->jsonEncoder) {
946 48
            $this->jsonEncoder = new JsonEncoder();
947 48
            $this->jsonEncoder->setPrettyPrinting(true);
948 48
            $this->jsonEncoder->setEscapeSlash(false);
949 48
            $this->jsonEncoder->setTerminateWithLineFeed(true);
950
        }
951
952 48
        return $this->jsonEncoder;
953
    }
954
955
    /**
956
     * Returns the JSON decoder.
957
     *
958
     * @return JsonDecoder The JSON decoder.
959
     */
960 48
    public function getJsonDecoder()
961
    {
962 48
        if (!$this->jsonDecoder) {
963 48
            $this->jsonDecoder = new JsonDecoder();
964
        }
965
966 48
        return $this->jsonDecoder;
967
    }
968
969 26
    private function activatePlugins()
970
    {
971 26
        foreach ($this->context->getRootPackageFile()->getPluginClasses() as $pluginClass) {
972 25
            $this->validatePluginClass($pluginClass);
973
974
            /** @var PuliPlugin $plugin */
975 23
            $plugin = new $pluginClass();
976 23
            $plugin->activate($this);
977
        }
978 24
    }
979
980 23
    private function createGlobalContext()
981
    {
982 23
        $baseConfig = new DefaultConfig();
983 23
        $homeDir = self::parseHomeDirectory();
984
985 22
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
986 20
            $baseConfig = $configFile->getConfig();
987
        }
988
989 22
        $config = new EnvConfig($baseConfig);
990
991 22
        return new Context($homeDir, $config, $configFile, $this->dispatcher);
992
    }
993
994
    /**
995
     * Creates the context of a Puli project.
996
     *
997
     * The home directory is read from the context variable "PULI_HOME".
998
     * If this variable is not set, the home directory defaults to:
999
     *
1000
     *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
1001
     *    "HOME".
1002
     *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
1003
     *    variable "APPDATA".
1004
     *
1005
     * If none of these variables can be found, an exception is thrown.
1006
     *
1007
     * A .htaccess file is put into the home directory to protect it from web
1008
     * access.
1009
     *
1010
     * @param string $rootDir The path to the project.
1011
     *
1012
     * @return ProjectContext The project context.
1013
     */
1014 27
    private function createProjectContext($rootDir, $env)
1015
    {
1016 27
        Assert::fileExists($rootDir, 'Could not load Puli context: The root %s does not exist.');
1017 27
        Assert::directory($rootDir, 'Could not load Puli context: The root %s is a file. Expected a directory.');
1018
1019 27
        $baseConfig = new DefaultConfig();
1020 27
        $homeDir = self::parseHomeDirectory();
1021
1022 27
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
1023 26
            $baseConfig = $configFile->getConfig();
1024
        }
1025
1026
        // Create a storage without the factory manager
1027 27
        $packageFileStorage = new PackageFileStorage(
1028 27
            $this->getStorage(),
1029 27
            $this->getLegacyPackageFileConverter(),
1030 27
            $this->getLegacyRootPackageFileConverter(),
1031 27
            $this->getJsonEncoder(),
1032 27
            $this->getJsonDecoder()
1033
        );
1034
1035 27
        $rootDir = Path::canonicalize($rootDir);
1036 27
        $rootFilePath = $this->rootDir.'/puli.json';
1037
1038
        try {
1039 27
            $rootPackageFile = $packageFileStorage->loadRootPackageFile($rootFilePath, $baseConfig);
1040 1
        } catch (FileNotFoundException $e) {
1041 1
            $rootPackageFile = new RootPackageFile(null, $rootFilePath, $baseConfig);
1042
        }
1043
1044 27
        $config = new EnvConfig($rootPackageFile->getConfig());
1045
1046 27
        return new ProjectContext($homeDir, $rootDir, $config, $rootPackageFile, $configFile, $this->dispatcher, $env);
1047
    }
1048
1049
    /**
1050
     * Returns the configuration file storage.
1051
     *
1052
     * @return ConfigFileStorage The configuration file storage.
1053
     */
1054 2
    private function getConfigFileStorage()
1055
    {
1056 2
        if (!$this->configFileStorage) {
1057 2
            $this->configFileStorage = new ConfigFileStorage(
1058 2
                $this->getStorage(),
1059 2
                $this->getConfigFileConverter(),
1060 2
                $this->getJsonEncoder(),
1061 2
                $this->getJsonDecoder(),
1062 2
                $this->getFactoryManager()
1063
            );
1064
        }
1065
1066 2
        return $this->configFileStorage;
1067
    }
1068
1069
    /**
1070
     * Returns the package file storage.
1071
     *
1072
     * @return PackageFileStorage The package file storage.
1073
     */
1074 14
    private function getPackageFileStorage()
1075
    {
1076 14
        if (!$this->packageFileStorage) {
1077 14
            $this->packageFileStorage = new PackageFileStorage(
1078 14
                $this->getStorage(),
1079 14
                $this->getLegacyPackageFileConverter(),
1080 14
                $this->getLegacyRootPackageFileConverter(),
1081 14
                $this->getJsonEncoder(),
1082 14
                $this->getJsonDecoder(),
1083 14
                $this->getFactoryManager()
1084
            );
1085
        }
1086
1087 14
        return $this->packageFileStorage;
1088
    }
1089
1090
    /**
1091
     * Validates the given plugin class name.
1092
     *
1093
     * @param string $pluginClass The fully qualified name of a plugin class.
1094
     */
1095 25
    private function validatePluginClass($pluginClass)
1096
    {
1097 25
        if (!class_exists($pluginClass)) {
1098 1
            throw new InvalidConfigException(sprintf(
1099 1
                'The plugin class %s does not exist.',
1100
                $pluginClass
1101
            ));
1102
        }
1103
1104 24
        if (!in_array('Puli\Manager\Api\PuliPlugin', class_implements($pluginClass))) {
1105 1
            throw new InvalidConfigException(sprintf(
1106 1
                'The plugin class %s must implement PuliPlugin.',
1107
                $pluginClass
1108
            ));
1109
        }
1110 23
    }
1111
1112 49
    private function loadConfigFile($homeDir, Config $baseConfig)
1113
    {
1114 49
        if (null === $homeDir) {
1115 2
            return null;
1116
        }
1117
1118 47
        Assert::fileExists($homeDir, 'Could not load Puli context: The home directory %s does not exist.');
1119 47
        Assert::directory($homeDir, 'Could not load Puli context: The home directory %s is a file. Expected a directory.');
1120
1121
        // Create a storage without the factory manager
1122 47
        $configStorage = new ConfigFileStorage(
1123 47
            $this->getStorage(),
1124 47
            $this->getConfigFileConverter(),
1125 47
            $this->getJsonEncoder(),
1126 47
            $this->getJsonDecoder()
1127
        );
1128
1129 47
        $configPath = Path::canonicalize($homeDir).'/config.json';
1130
1131
        try {
1132 47
            return $configStorage->loadConfigFile($configPath, $baseConfig);
1133 1
        } catch (FileNotFoundException $e) {
1134
            // It's ok if no config.json exists. We'll work with
1135
            // DefaultConfig instead
1136 1
            return null;
1137
        }
1138
    }
1139
}
1140