Failed Conditions
Pull Request — master (#33)
by Bernhard
04:32
created

src/Api/Puli.php (16 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\ConfigFile;
21
use Puli\Manager\Api\Config\ConfigFileManager;
22
use Puli\Manager\Api\Config\ConfigFileSerializer;
23
use Puli\Manager\Api\Context\Context;
24
use Puli\Manager\Api\Context\ProjectContext;
25
use Puli\Manager\Api\Discovery\DiscoveryManager;
26
use Puli\Manager\Api\Factory\FactoryManager;
27
use Puli\Manager\Api\Installation\InstallationManager;
28
use Puli\Manager\Api\Installer\InstallerManager;
29
use Puli\Manager\Api\Package\PackageFileSerializer;
30
use Puli\Manager\Api\Package\PackageManager;
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 49
    private static function parseHomeDirectory()
256
    {
257
        try {
258 49
            $homeDir = System::parseHomeDirectory();
259
260 46
            System::denyWebAccess($homeDir);
261
262 46
            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 51
    public function __construct($rootDir = null, $env = Environment::DEV)
284
    {
285 51
        $this->setRootDirectory($rootDir);
286 51
        $this->setEnvironment($env);
287 51
    }
288
289
    /**
290
     * Starts the service container.
291
     */
292 49
    public function start()
293
    {
294 49
        if ($this->started) {
295
            throw new LogicException('Puli is already started');
296
        }
297
298 49
        if ($this->rootDir) {
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...
299 26
            $this->context = $this->createProjectContext($this->rootDir, $this->env);
300 26
            $bootstrapFile = $this->context->getConfig()->get(Config::BOOTSTRAP_FILE);
301
302
            // Run the project's bootstrap file to enable project-specific
303
            // autoloading
304 26
            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 26
        } else {
324 23
            $this->context = $this->createGlobalContext();
325
        }
326
327 48
        $this->dispatcher = $this->context->getEventDispatcher();
328 48
        $this->started = true;
329
330
        // Start plugins once the container is running
331 48
        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...
332 25
            $this->activatePlugins();
333 23
        }
334 46
    }
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 51
    public function setRootDirectory($rootDir)
355
    {
356 51
        if ($this->started) {
357
            throw new LogicException('Puli is already started');
358
        }
359
360 51
        Assert::nullOrDirectory($rootDir);
361
362 51
        $this->rootDir = $rootDir ? Path::canonicalize($rootDir) : null;
363 51
    }
364
365
    /**
366
     * Sets the environment of the managed Puli project.
367
     *
368
     * @param string $env One of the {@link Environment} constants.
369
     */
370 51
    public function setEnvironment($env)
371
    {
372 51
        if ($this->started) {
373
            throw new LogicException('Puli is already started');
374
        }
375
376 51
        Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s');
377
378 51
        $this->env = $env;
379 51
    }
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 29
    public function getContext()
485
    {
486 29
        if (!$this->started) {
487
            throw new LogicException('Puli was not started');
488
        }
489
490 29
        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->rootDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rootDir of type string|null is loosely compared to false; 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...
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->rootDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rootDir of type string|null is loosely compared to false; 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...
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->rootDir) {
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...
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->rootDir) {
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...
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 a cyclic dependency
570 14
            $this->factoryManager->setServers($this->getServerManager()->getServers());
571 14
        }
572
573 16
        return $this->factoryManager;
574
    }
575
576
    /**
577
     * Returns the configuration file manager.
578
     *
579
     * @return ConfigFileManager The configuration file manager.
580
     */
581 2
    public function getConfigFileManager()
582
    {
583 2
        if (!$this->started) {
584
            throw new LogicException('Puli was not started');
585
        }
586
587 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...
588 2
            $this->configFileManager = new ConfigFileManagerImpl(
589 2
                $this->context,
590 2
                $this->getConfigFileStorage(),
591 2
                $this->getFactoryManager()
592 2
            );
593 2
        }
594
595 2
        return $this->configFileManager;
596
    }
597
598
    /**
599
     * Returns the root package file manager.
600
     *
601
     * @return RootPackageFileManager The package file manager.
602
     */
603 15 View Code Duplication
    public function getRootPackageFileManager()
604
    {
605 15
        if (!$this->started) {
606
            throw new LogicException('Puli was not started');
607
        }
608
609 15
        if (!$this->rootPackageFileManager && $this->rootDir) {
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...
610 14
            $this->rootPackageFileManager = new RootPackageFileManagerImpl(
611 14
                $this->context,
612 14
                $this->getPackageFileStorage()
613 14
            );
614 14
        }
615
616 15
        return $this->rootPackageFileManager;
617
    }
618
619
    /**
620
     * Returns the package manager.
621
     *
622
     * @return PackageManager The package manager.
623
     */
624 15 View Code Duplication
    public function getPackageManager()
625
    {
626 15
        if (!$this->started) {
627
            throw new LogicException('Puli was not started');
628
        }
629
630 15
        if (!$this->packageManager && $this->rootDir) {
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...
631 14
            $this->packageManager = new PackageManagerImpl(
632 14
                $this->context,
633 14
                $this->getPackageFileStorage()
634 14
            );
635 14
        }
636
637 15
        return $this->packageManager;
638
    }
639
640
    /**
641
     * Returns the resource repository manager.
642
     *
643
     * @return RepositoryManager The repository manager.
644
     */
645 2 View Code Duplication
    public function getRepositoryManager()
646
    {
647 2
        if (!$this->started) {
648
            throw new LogicException('Puli was not started');
649
        }
650
651 2
        if (!$this->repositoryManager && $this->rootDir) {
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...
652 1
            $this->repositoryManager = new RepositoryManagerImpl(
653 1
                $this->context,
654 1
                $this->getRepository(),
655 1
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
656 1
                $this->getPackageFileStorage()
657 1
            );
658 1
        }
659
660 2
        return $this->repositoryManager;
661
    }
662
663
    /**
664
     * Returns the resource discovery manager.
665
     *
666
     * @return DiscoveryManager The discovery manager.
667
     */
668 3 View Code Duplication
    public function getDiscoveryManager()
669
    {
670 3
        if (!$this->started) {
671
            throw new LogicException('Puli was not started');
672
        }
673
674 3
        if (!$this->discoveryManager && $this->rootDir) {
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...
675 2
            $this->discoveryManager = new DiscoveryManagerImpl(
676 2
                $this->context,
677 2
                $this->getDiscovery(),
678 2
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
679 2
                $this->getPackageFileStorage(),
680 2
                $this->logger
681 2
            );
682 2
        }
683
684 3
        return $this->discoveryManager;
685
    }
686
687
    /**
688
     * Returns the asset manager.
689
     *
690
     * @return AssetManager The asset manager.
691
     */
692 2
    public function getAssetManager()
693
    {
694 2
        if (!$this->started) {
695
            throw new LogicException('Puli was not started');
696
        }
697
698 2
        if (!$this->assetManager && $this->rootDir) {
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...
699 1
            $this->assetManager = new DiscoveryAssetManager(
700 1
                $this->getDiscoveryManager(),
701 1
                $this->getServerManager()->getServers()
702 1
            );
703 1
        }
704
705 2
        return $this->assetManager;
706
    }
707
708
    /**
709
     * Returns the installation manager.
710
     *
711
     * @return InstallationManager The installation manager.
712
     */
713 2
    public function getInstallationManager()
714
    {
715 2
        if (!$this->started) {
716
            throw new LogicException('Puli was not started');
717
        }
718
719 2
        if (!$this->installationManager && $this->rootDir) {
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...
720 1
            $this->installationManager = new InstallationManagerImpl(
721 1
                $this->getContext(),
722 1
                $this->getRepository(),
723 1
                $this->getServerManager()->getServers(),
724 1
                $this->getInstallerManager()
725 1
            );
726 1
        }
727
728 2
        return $this->installationManager;
729
    }
730
731
    /**
732
     * Returns the installer manager.
733
     *
734
     * @return InstallerManager The installer manager.
735
     */
736 15
    public function getInstallerManager()
737
    {
738 15
        if (!$this->started) {
739
            throw new LogicException('Puli was not started');
740
        }
741
742 15
        if (!$this->installerManager && $this->rootDir) {
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...
743 14
            $this->installerManager = new PackageFileInstallerManager(
744 14
                $this->getRootPackageFileManager(),
745 14
                $this->getPackageManager()->getPackages()
746 14
            );
747 14
        }
748
749 15
        return $this->installerManager;
750
    }
751
752
    /**
753
     * Returns the server manager.
754
     *
755
     * @return ServerManager The server manager.
756
     */
757 15
    public function getServerManager()
758
    {
759 15
        if (!$this->started) {
760
            throw new LogicException('Puli was not started');
761
        }
762
763 15
        if (!$this->serverManager && $this->rootDir) {
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...
764 14
            $this->serverManager = new PackageFileServerManager(
765 14
                $this->getRootPackageFileManager(),
766 14
                $this->getInstallerManager()
767 14
            );
768 14
        }
769
770 15
        return $this->serverManager;
771
    }
772
773
    /**
774
     * Returns the resource URL generator.
775
     *
776
     * @return UrlGenerator The resource URL generator.
777
     */
778 2
    public function getUrlGenerator()
779
    {
780 2
        if (!$this->started) {
781
            throw new LogicException('Puli was not started');
782
        }
783
784 2
        if (!$this->urlGenerator && $this->rootDir) {
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...
785 1
            $urlFormats = array();
786 1
            foreach ($this->getServerManager()->getServers() as $server) {
787
                $urlFormats[$server->getName()] = $server->getUrlFormat();
788 1
            }
789
790 1
            $this->urlGenerator = new DiscoveryUrlGenerator($this->getDiscovery(), $urlFormats);
791 1
        }
792
793 2
        return $this->urlGenerator;
794
    }
795
796
    /**
797
     * Returns the cached file storage.
798
     *
799
     * @return Storage The storage.
800
     */
801 47
    public function getStorage()
802
    {
803 47
        if (!$this->storage) {
804 47
            $this->storage = new FilesystemStorage();
805 47
        }
806
807 47
        return $this->storage;
808
    }
809
810
    /**
811
     * Returns the cached configuration file serializer.
812
     *
813
     * @return ConfigFileSerializer The configuration file serializer.
814
     */
815 46
    public function getConfigFileSerializer()
816
    {
817 46
        if (!$this->configFileSerializer) {
818 46
            $this->configFileSerializer = new ConfigJsonSerializer();
819 46
        }
820
821 46
        return $this->configFileSerializer;
822
    }
823
824
    /**
825
     * Returns the cached package file serializer.
826
     *
827
     * @return PackageFileSerializer The package file serializer.
828
     */
829 26
    public function getPackageFileSerializer()
830
    {
831 26
        if (!$this->packageFileSerializer) {
832 26
            $this->packageFileSerializer = new PackageJsonSerializer(
833 26
                new MigrationManager(array(
834
                    // Add future migrations here
835 26
                )),
836
                __DIR__.'/../../res/schema'
837 26
            );
838 26
        }
839
840 26
        return $this->packageFileSerializer;
841
    }
842
843 25
    private function activatePlugins()
844
    {
845 25
        foreach ($this->context->getRootPackageFile()->getPluginClasses() as $pluginClass) {
846 25
            $this->validatePluginClass($pluginClass);
847
848
            /** @var PuliPlugin $plugin */
849 23
            $plugin = new $pluginClass();
850 23
            $plugin->activate($this);
851 23
        }
852 23
    }
853
854 23
    private function createGlobalContext()
855
    {
856 23
        $baseConfig = new DefaultConfig();
857 23
        $homeDir = self::parseHomeDirectory();
858
859 22
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
860 20
            $baseConfig = $configFile->getConfig();
861 20
        }
862
863 22
        $config = new EnvConfig($baseConfig);
864
865 22
        return new Context($homeDir, $config, $configFile, $this->dispatcher);
866
    }
867
868
    /**
869
     * Creates the context of a Puli project.
870
     *
871
     * The home directory is read from the context variable "PULI_HOME".
872
     * If this variable is not set, the home directory defaults to:
873
     *
874
     *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
875
     *    "HOME".
876
     *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
877
     *    variable "APPDATA".
878
     *
879
     * If none of these variables can be found, an exception is thrown.
880
     *
881
     * A .htaccess file is put into the home directory to protect it from web
882
     * access.
883
     *
884
     * @param string $rootDir The path to the project.
885
     *
886
     * @return ProjectContext The project context.
887
     */
888 26
    private function createProjectContext($rootDir, $env)
889
    {
890 26
        Assert::fileExists($rootDir, 'Could not load Puli context: The root %s does not exist.');
891 26
        Assert::directory($rootDir, 'Could not load Puli context: The root %s is a file. Expected a directory.');
892
893 26
        $baseConfig = new DefaultConfig();
894 26
        $homeDir = self::parseHomeDirectory();
895
896 26
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
897 25
            $baseConfig = $configFile->getConfig();
898 25
        }
899
900
        // Create a storage without the factory manager
901 26
        $packageFileStorage = new PackageFileStorage($this->getStorage(), $this->getPackageFileSerializer());
902 26
        $rootDir = Path::canonicalize($rootDir);
903 26
        $rootFilePath = $this->rootDir.'/puli.json';
904 26
        $rootPackageFile = $packageFileStorage->loadRootPackageFile($rootFilePath, $baseConfig);
905 26
        $config = new EnvConfig($rootPackageFile->getConfig());
906
907 26
        return new ProjectContext($homeDir, $rootDir, $config, $rootPackageFile, $configFile, $this->dispatcher, $env);
908
    }
909
910
    /**
911
     * Returns the cached configuration file storage.
912
     *
913
     * @return ConfigFileStorage The configuration file storage.
914
     */
915 2
    private function getConfigFileStorage()
916
    {
917 2
        if (!$this->configFileStorage) {
918 2
            $this->configFileStorage = new ConfigFileStorage(
919 2
                $this->getStorage(),
920 2
                $this->getConfigFileSerializer(),
921 2
                $this->getFactoryManager()
922 2
            );
923 2
        }
924
925 2
        return $this->configFileStorage;
926
    }
927
928
    /**
929
     * Returns the cached package file storage.
930
     *
931
     * @return PackageFileStorage The package file storage.
932
     */
933 14
    private function getPackageFileStorage()
934
    {
935 14
        if (!$this->packageFileStorage) {
936 14
            $this->packageFileStorage = new PackageFileStorage(
937 14
                $this->getStorage(),
938 14
                $this->getPackageFileSerializer(),
939 14
                $this->getFactoryManager()
940 14
            );
941 14
        }
942
943 14
        return $this->packageFileStorage;
944
    }
945
946
    /**
947
     * Validates the given plugin class name.
948
     *
949
     * @param string $pluginClass The fully qualified name of a plugin class.
950
     */
951 25
    private function validatePluginClass($pluginClass)
952
    {
953 25
        if (!class_exists($pluginClass)) {
954 1
            throw new InvalidConfigException(sprintf(
955 1
                'The plugin class %s does not exist.',
956
                $pluginClass
957 1
            ));
958
        }
959
960 24
        if (!in_array('Puli\Manager\Api\PuliPlugin', class_implements($pluginClass))) {
961 1
            throw new InvalidConfigException(sprintf(
962 1
                'The plugin class %s must implement PuliPlugin.',
963
                $pluginClass
964 1
            ));
965
        }
966 23
    }
967
968 48
    private function loadConfigFile($homeDir, Config $baseConfig)
969
    {
970 48
        if (null === $homeDir) {
971 2
            return null;
972
        }
973
974 46
        Assert::fileExists($homeDir, 'Could not load Puli context: The home directory %s does not exist.');
975 46
        Assert::directory($homeDir, 'Could not load Puli context: The home directory %s is a file. Expected a directory.');
976
977
        // Create a storage without the factory manager
978 46
        $configStorage = new ConfigFileStorage($this->getStorage(), $this->getConfigFileSerializer());
979 46
        $configPath = Path::canonicalize($homeDir).'/config.json';
980
981
        try {
982 46
            return $configStorage->loadConfigFile($configPath, $baseConfig);
983 1
        } catch (FileNotFoundException $e) {
984
            // It's ok if no config.json exists. We'll work with
985
            // DefaultConfig instead
986 1
            return null;
987
        }
988
    }
989
}
990