Failed Conditions
Push — master ( 45320b...e3306a )
by Bernhard
04:44
created

Container   F

Complexity

Total Complexity 127

Size/Duplication

Total Lines 1077
Duplicated Lines 13.09 %

Coupling/Cohesion

Components 3
Dependencies 48

Test Coverage

Coverage 89.12%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 127
c 1
b 0
f 0
lcom 3
cbo 48
dl 141
loc 1077
ccs 295
cts 331
cp 0.8912
rs 0.6417

48 Methods

Rating   Name   Duplication   Size   Complexity  
A parseHomeDirectory() 0 15 2
A __construct() 0 5 1
C start() 0 43 8
A isStarted() 0 4 1
A setRootDirectory() 0 10 3
A setEnvironment() 0 10 2
A getEnvironment() 0 4 1
A getRootDirectory() 0 4 1
A setLogger() 0 8 2
A getLogger() 0 4 1
A setEventDispatcher() 0 8 2
A getEventDispatcher() 0 4 1
A enablePlugins() 0 4 1
A disablePlugins() 0 4 1
A arePluginsEnabled() 0 4 1
A getContext() 0 8 2
A getRepository() 16 16 4
A getDiscovery() 16 16 4
A getFactory() 0 12 4
A getFactoryManager() 0 20 4
A getConfigFileManager() 0 15 4
A getRootModuleFileManager() 15 15 4
A getModuleManager() 15 15 4
A getRepositoryManager() 17 17 4
A getDiscoveryManager() 18 18 4
A getAssetManager() 0 15 4
A getInstallationManager() 0 17 4
A getInstallerManager() 0 15 4
A getServerManager() 0 15 4
B getUrlGenerator() 0 17 5
A getStorage() 0 8 2
A getConfigFileConverter() 0 8 2
A getModuleFileConverter() 0 10 2
A getLegacyModuleFileConverter() 22 22 3
A getRootModuleFileConverter() 0 10 2
A getLegacyRootModuleFileConverter() 22 22 3
A getJsonEncoder() 0 11 2
A getJsonDecoder() 0 8 2
A getJsonValidator() 0 13 2
A activatePlugins() 0 10 2
A createGlobalContext() 0 13 2
B createProjectContext() 0 33 3
A createValidatingConverter() 0 4 1
A getJsonStorage() 0 14 2
A getJsonVersioner() 0 13 2
A getModuleFileMigrationManager() 0 10 2
A validatePluginClass() 0 16 3
B loadConfigFile() 0 27 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Container often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Container, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Api;
13
14
use JsonSchema\Uri\UriRetriever;
15
use LogicException;
16
use Psr\Log\LoggerInterface;
17
use Puli\Discovery\Api\Discovery;
18
use Puli\Discovery\Api\EditableDiscovery;
19
use Puli\Manager\Api\Asset\AssetManager;
20
use Puli\Manager\Api\Config\Config;
21
use Puli\Manager\Api\Config\ConfigFileManager;
22
use Puli\Manager\Api\Context\Context;
23
use Puli\Manager\Api\Context\ProjectContext;
24
use Puli\Manager\Api\Discovery\DiscoveryManager;
25
use Puli\Manager\Api\Factory\FactoryManager;
26
use Puli\Manager\Api\Installation\InstallationManager;
27
use Puli\Manager\Api\Installer\InstallerManager;
28
use Puli\Manager\Api\Module\ModuleManager;
29
use Puli\Manager\Api\Module\RootModuleFile;
30
use Puli\Manager\Api\Module\RootModuleFileManager;
31
use Puli\Manager\Api\Repository\RepositoryManager;
32
use Puli\Manager\Api\Server\ServerManager;
33
use Puli\Manager\Api\Storage\Storage;
34
use Puli\Manager\Assert\Assert;
35
use Puli\Manager\Asset\DiscoveryAssetManager;
36
use Puli\Manager\Config\ConfigFileConverter;
37
use Puli\Manager\Config\ConfigFileManagerImpl;
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\ModuleFileInstallerManager;
46
use Puli\Manager\Json\ChainVersioner;
47
use Puli\Manager\Json\JsonConverterProvider;
48
use Puli\Manager\Json\JsonStorage;
49
use Puli\Manager\Json\LocalUriRetriever;
50
use Puli\Manager\Module\Migration\ModuleFile10To20Migration;
51
use Puli\Manager\Module\ModuleFileConverter;
52
use Puli\Manager\Module\ModuleManagerImpl;
53
use Puli\Manager\Module\RootModuleFileConverter;
54
use Puli\Manager\Module\RootModuleFileManagerImpl;
55
use Puli\Manager\Php\ClassWriter;
56
use Puli\Manager\Repository\RepositoryManagerImpl;
57
use Puli\Manager\Server\ModuleFileServerManager;
58
use Puli\Manager\Util\System;
59
use Puli\Repository\Api\EditableRepository;
60
use Puli\Repository\Api\ResourceRepository;
61
use Puli\UrlGenerator\Api\UrlGenerator;
62
use Puli\UrlGenerator\DiscoveryUrlGenerator;
63
use stdClass;
64
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
65
use Webmozart\Expression\Expr;
66
use Webmozart\Json\Conversion\JsonConverter;
67
use Webmozart\Json\JsonDecoder;
68
use Webmozart\Json\JsonEncoder;
69
use Webmozart\Json\JsonValidator;
70
use Webmozart\Json\Migration\MigratingConverter;
71
use Webmozart\Json\Migration\MigrationManager;
72
use Webmozart\Json\Validation\ValidatingConverter;
73
use Webmozart\Json\Versioning\JsonVersioner;
74
use Webmozart\Json\Versioning\SchemaUriVersioner;
75
use Webmozart\Json\Versioning\VersionFieldVersioner;
76
use Webmozart\PathUtil\Path;
77
78
/**
79
 * The Puli service container.
80
 *
81
 * Use this class to access the managers provided by this module:
82
 *
83
 * ```php
84
 * $puli = new Puli(getcwd());
85
 * $puli->start();
86
 *
87
 * $moduleManager = $puli->getModuleManager();
88
 * ```
89
 *
90
 * The `Puli` class either operates in the global or a project context:
91
 *
92
 *  * The "global context" is not tied to a specific root module. A global
93
 *    context only loads the settings of the "config.json" file in the home
94
 *    directory. The `Puli` class operates in the global context if no
95
 *    project root directory is passed to the constructor. In the global
96
 *    context, only the global config file manager is available.
97
 *  * The "project context" is tied to a specific Puli project. You need to
98
 *    pass the path to the project's root directory to the constructor or to
99
 *    {@link setRootDirectory()}. The configuration of the "puli.json" file in
100
 *    the root directory is used to configure the managers.
101
 *
102
 * The `Puli` class creates four kinds of managers:
103
 *
104
 *  * The "config file manager" allows you to modify entries of the
105
 *    "config.json" file in the home directory.
106
 *  * The "module file manager" manages modifications to the "puli.json" file
107
 *    of a Puli project.
108
 *  * The "module manager" manages the module repository of a Puli project.
109
 *  * The "repository manager" manages the resource repository of a Puli
110
 *    project.
111
 *  * The "discovery manager" manages the resource discovery of a Puli project.
112
 *
113
 * The home directory is read from the context variable "PULI_HOME".
114
 * If this variable is not set, the home directory defaults to:
115
 *
116
 *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
117
 *    "HOME".
118
 *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
119
 *    variable "APPDATA".
120
 *
121
 * If none of these variables can be found, an exception is thrown.
122
 *
123
 * A .htaccess file is put into the home directory to protect it from web
124
 * access.
125
 *
126
 * @since  1.0
127
 *
128
 * @author Bernhard Schussek <[email protected]>
129
 */
130
class Container
131
{
132
    /**
133
     * @var string|null
134
     */
135
    private $rootDir;
136
137
    /**
138
     * @var string
139
     */
140
    private $env;
141
142
    /**
143
     * @var EventDispatcherInterface|null
144
     */
145
    private $dispatcher;
146
147
    /**
148
     * @var Context|ProjectContext
149
     */
150
    private $context;
151
152
    /**
153
     * @var ResourceRepository
154
     */
155
    private $repo;
156
157
    /**
158
     * @var Discovery
159
     */
160
    private $discovery;
161
162
    /**
163
     * @var object
164
     */
165
    private $factory;
166
167
    /**
168
     * @var FactoryManager
169
     */
170
    private $factoryManager;
171
172
    /**
173
     * @var ConfigFileManager
174
     */
175
    private $configFileManager;
176
177
    /**
178
     * @var RootModuleFileManager
179
     */
180
    private $rootModuleFileManager;
181
182
    /**
183
     * @var ModuleManager
184
     */
185
    private $moduleManager;
186
187
    /**
188
     * @var RepositoryManager
189
     */
190
    private $repositoryManager;
191
192
    /**
193
     * @var DiscoveryManager
194
     */
195
    private $discoveryManager;
196
197
    /**
198
     * @var AssetManager
199
     */
200
    private $assetManager;
201
202
    /**
203
     * @var InstallationManager
204
     */
205
    private $installationManager;
206
207
    /**
208
     * @var InstallerManager
209
     */
210
    private $installerManager;
211
212
    /**
213
     * @var ServerManager
214
     */
215
    private $serverManager;
216
217
    /**
218
     * @var UrlGenerator
219
     */
220
    private $urlGenerator;
221
222
    /**
223
     * @var Storage|null
224
     */
225
    private $storage;
226
227
    /**
228
     * @var JsonStorage|null
229
     */
230
    private $jsonStorage;
231
232
    /**
233
     * @var ConfigFileConverter|null
234
     */
235
    private $configFileConverter;
236
237
    /**
238
     * @var JsonConverter|null
239
     */
240
    private $moduleFileConverter;
241
242
    /**
243
     * @var JsonConverter|null
244
     */
245
    private $legacyModuleFileConverter;
246
247
    /**
248
     * @var JsonConverter|null
249
     */
250
    private $rootModuleFileConverter;
251
252
    /**
253
     * @var JsonConverter|null
254
     */
255
    private $legacyRootModuleFileConverter;
256
257
    /**
258
     * @var MigrationManager|null
259
     */
260
    private $moduleFileMigrationManager;
261
262
    /**
263
     * @var JsonEncoder
264
     */
265
    private $jsonEncoder;
266
267
    /**
268
     * @var JsonDecoder
269
     */
270
    private $jsonDecoder;
271
272
    /**
273
     * @var JsonValidator
274
     */
275
    private $jsonValidator;
276
277
    /**
278
     * @var JsonVersioner
279
     */
280
    private $jsonVersioner;
281
282
    /**
283
     * @var LoggerInterface
284
     */
285
    private $logger;
286
287
    /**
288
     * @var bool
289
     */
290
    private $started = false;
291
292
    /**
293
     * @var bool
294
     */
295
    private $pluginsEnabled = true;
296
297
    /**
298
     * Parses the system context for a home directory.
299
     *
300
     * @return null|string Returns the path to the home directory or `null`
301
     *                     if none was found.
302
     */
303 50
    private static function parseHomeDirectory()
304
    {
305
        try {
306 50
            $homeDir = System::parseHomeDirectory();
307
308 47
            System::denyWebAccess($homeDir);
309
310 47
            return $homeDir;
311 3
        } catch (InvalidConfigException $e) {
312
            // Context variable was not found -> no home directory
313
            // This happens often on web servers where the home directory is
314
            // not set manually
315 2
            return null;
316
        }
317
    }
318
319
    /**
320
     * Creates a new instance for the given Puli project.
321
     *
322
     * @param string|null $rootDir The root directory of the Puli project.
323
     *                             If none is passed, the object operates in
324
     *                             the global context. You can set or switch
325
     *                             the root directories later on by calling
326
     *                             {@link setRootDirectory()}.
327
     * @param string      $env     One of the {@link Environment} constants.
328
     *
329
     * @see Puli, start()
330
     */
331 52
    public function __construct($rootDir = null, $env = Environment::DEV)
332
    {
333 52
        $this->setRootDirectory($rootDir);
334 52
        $this->setEnvironment($env);
335 52
    }
336
337
    /**
338
     * Starts the service container.
339
     */
340 50
    public function start()
341
    {
342 50
        if ($this->started) {
343
            throw new LogicException('Puli is already started');
344
        }
345
346 50
        if (null !== $this->rootDir) {
347 27
            $this->context = $this->createProjectContext($this->rootDir, $this->env);
348 27
            $bootstrapFile = $this->context->getConfig()->get(Config::BOOTSTRAP_FILE);
349
350
            // Run the project's bootstrap file to enable project-specific
351
            // autoloading
352 27
            if (null !== $bootstrapFile) {
353
                // Backup autoload functions of the PHAR
354 1
                $autoloadFunctions = spl_autoload_functions();
355
356 1
                foreach ($autoloadFunctions as $autoloadFunction) {
357 1
                    spl_autoload_unregister($autoloadFunction);
358
                }
359
360
                // Add project-specific autoload functions
361 1
                require_once Path::makeAbsolute($bootstrapFile, $this->rootDir);
362
363
                // Prepend autoload functions of the PHAR again
364
                // This is needed if the user specific autoload functions were
365
                // added with $prepend=true (as done by Composer)
366
                // Classes in the PHAR should always take precedence
367 27
                for ($i = count($autoloadFunctions) - 1; $i >= 0; --$i) {
368 1
                    spl_autoload_register($autoloadFunctions[$i], true, true);
369
                }
370
            }
371
        } else {
372 23
            $this->context = $this->createGlobalContext();
373
        }
374
375 49
        $this->dispatcher = $this->context->getEventDispatcher();
376 49
        $this->started = true;
377
378
        // Start plugins once the container is running
379 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...
380 26
            $this->activatePlugins();
381
        }
382 47
    }
383
384
    /**
385
     * Returns whether the service container is started.
386
     *
387
     * @return bool Returns `true` if the container is started and `false`
388
     *              otherwise.
389
     */
390
    public function isStarted()
391
    {
392
        return $this->started;
393
    }
394
395
    /**
396
     * Sets the root directory of the managed Puli project.
397
     *
398
     * @param string|null $rootDir The root directory of the managed Puli
399
     *                             project or `null` to start Puli outside of a
400
     *                             specific project.
401
     */
402 52
    public function setRootDirectory($rootDir)
403
    {
404 52
        if ($this->started) {
405
            throw new LogicException('Puli is already started');
406
        }
407
408 52
        Assert::nullOrDirectory($rootDir);
409
410 52
        $this->rootDir = $rootDir ? Path::canonicalize($rootDir) : null;
411 52
    }
412
413
    /**
414
     * Sets the environment of the managed Puli project.
415
     *
416
     * @param string $env One of the {@link Environment} constants.
417
     */
418 52
    public function setEnvironment($env)
419
    {
420 52
        if ($this->started) {
421
            throw new LogicException('Puli is already started');
422
        }
423
424 52
        Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s');
425
426 52
        $this->env = $env;
427 52
    }
428
429
    /**
430
     * Retturns the environment of the managed Puli project.
431
     *
432
     * @return string One of the {@link Environment} constants.
433
     */
434 2
    public function getEnvironment()
435
    {
436 2
        return $this->env;
437
    }
438
439
    /**
440
     * Returns the root directory of the managed Puli project.
441
     *
442
     * If no Puli project is managed at the moment, `null` is returned.
443
     *
444
     * @return string|null The root directory of the managed Puli project or
445
     *                     `null` if none is set.
446
     */
447 4
    public function getRootDirectory()
448
    {
449 4
        return $this->rootDir;
450
    }
451
452
    /**
453
     * Sets the logger to use.
454
     *
455
     * @param LoggerInterface $logger The logger to use.
456
     */
457
    public function setLogger(LoggerInterface $logger)
458
    {
459
        if ($this->started) {
460
            throw new LogicException('Puli is already started');
461
        }
462
463
        $this->logger = $logger;
464
    }
465
466
    /**
467
     * Returns the logger.
468
     *
469
     * @return LoggerInterface The logger.
470
     */
471
    public function getLogger()
472
    {
473
        return $this->logger;
474
    }
475
476
    /**
477
     * Sets the event dispatcher to use.
478
     *
479
     * @param EventDispatcherInterface $dispatcher The event dispatcher to use.
480
     */
481 2
    public function setEventDispatcher(EventDispatcherInterface $dispatcher)
482
    {
483 2
        if ($this->started) {
484
            throw new LogicException('Puli is already started');
485
        }
486
487 2
        $this->dispatcher = $dispatcher;
488 2
    }
489
490
    /**
491
     * Returns the used event dispatcher.
492
     *
493
     * @return EventDispatcherInterface|null The used logger.
494
     */
495 3
    public function getEventDispatcher()
496
    {
497 3
        return $this->dispatcher;
498
    }
499
500
    /**
501
     * Enables all Puli plugins.
502
     */
503
    public function enablePlugins()
504
    {
505
        $this->pluginsEnabled = true;
506
    }
507
508
    /**
509
     * Disables all Puli plugins.
510
     */
511 1
    public function disablePlugins()
512
    {
513 1
        $this->pluginsEnabled = false;
514 1
    }
515
516
    /**
517
     * Returns whether Puli plugins are enabled.
518
     *
519
     * @return bool Returns `true` if Puli plugins will be loaded and `false`
520
     *              otherwise.
521
     */
522
    public function arePluginsEnabled()
523
    {
524
        return $this->pluginsEnabled;
525
    }
526
527
    /**
528
     * Returns the context.
529
     *
530
     * @return Context|ProjectContext The context.
531
     */
532 30
    public function getContext()
533
    {
534 30
        if (!$this->started) {
535
            throw new LogicException('Puli was not started');
536
        }
537
538 30
        return $this->context;
539
    }
540
541
    /**
542
     * Returns the resource repository of the project.
543
     *
544
     * @return EditableRepository The resource repository.
545
     */
546 8 View Code Duplication
    public function getRepository()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
547
    {
548 8
        if (!$this->started) {
549
            throw new LogicException('Puli was not started');
550
        }
551
552 8
        if (!$this->context instanceof ProjectContext) {
553 1
            return null;
554
        }
555
556 7
        if (!$this->repo) {
557 7
            $this->repo = $this->getFactory()->createRepository();
558
        }
559
560 7
        return $this->repo;
561
    }
562
563
    /**
564
     * Returns the resource discovery of the project.
565
     *
566
     * @return EditableDiscovery The resource discovery.
567
     */
568 5 View Code Duplication
    public function getDiscovery()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
569
    {
570 5
        if (!$this->started) {
571
            throw new LogicException('Puli was not started');
572
        }
573
574 5
        if (!$this->context instanceof ProjectContext) {
575 1
            return null;
576
        }
577
578 4
        if (!$this->discovery) {
579 4
            $this->discovery = $this->getFactory()->createDiscovery($this->getRepository());
580
        }
581
582 4
        return $this->discovery;
583
    }
584
585
    /**
586
     * @return object
587
     */
588 9
    public function getFactory()
589
    {
590 9
        if (!$this->started) {
591
            throw new LogicException('Puli was not started');
592
        }
593
594 9
        if (!$this->factory && $this->context instanceof ProjectContext) {
595 8
            $this->factory = $this->getFactoryManager()->createFactory();
596
        }
597
598 9
        return $this->factory;
599
    }
600
601
    /**
602
     * @return FactoryManager
603
     */
604 16
    public function getFactoryManager()
605
    {
606 16
        if (!$this->started) {
607
            throw new LogicException('Puli was not started');
608
        }
609
610 16
        if (!$this->factoryManager && $this->context instanceof ProjectContext) {
611 14
            $this->factoryManager = new FactoryManagerImpl(
612 14
                $this->context,
613 14
                new DefaultGeneratorRegistry(),
614 14
                new ClassWriter()
615
            );
616
617
            // Don't set via the constructor to prevent cyclic dependencies
618 14
            $this->factoryManager->setModules($this->getModuleManager()->getModules());
619 14
            $this->factoryManager->setServers($this->getServerManager()->getServers());
620
        }
621
622 16
        return $this->factoryManager;
623
    }
624
625
    /**
626
     * Returns the configuration file manager.
627
     *
628
     * @return ConfigFileManager The configuration file manager.
629
     */
630 2
    public function getConfigFileManager()
631
    {
632 2
        if (!$this->started) {
633
            throw new LogicException('Puli was not started');
634
        }
635
636 2
        if (!$this->configFileManager && $this->context->getHomeDirectory()) {
637 2
            $this->configFileManager = new ConfigFileManagerImpl(
638 2
                $this->context,
639 2
                $this->getJsonStorage()
640
            );
641
        }
642
643 2
        return $this->configFileManager;
644
    }
645
646
    /**
647
     * Returns the root module file manager.
648
     *
649
     * @return RootModuleFileManager The module file manager.
650
     */
651 15 View Code Duplication
    public function getRootModuleFileManager()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
652
    {
653 15
        if (!$this->started) {
654
            throw new LogicException('Puli was not started');
655
        }
656
657 15
        if (!$this->rootModuleFileManager && $this->context instanceof ProjectContext) {
658 14
            $this->rootModuleFileManager = new RootModuleFileManagerImpl(
659 14
                $this->context,
660 14
                $this->getJsonStorage()
661
            );
662
        }
663
664 15
        return $this->rootModuleFileManager;
665
    }
666
667
    /**
668
     * Returns the module manager.
669
     *
670
     * @return ModuleManager The module manager.
671
     */
672 15 View Code Duplication
    public function getModuleManager()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
673
    {
674 15
        if (!$this->started) {
675
            throw new LogicException('Puli was not started');
676
        }
677
678 15
        if (!$this->moduleManager && $this->context instanceof ProjectContext) {
679 14
            $this->moduleManager = new ModuleManagerImpl(
680 14
                $this->context,
681 14
                $this->getJsonStorage()
682
            );
683
        }
684
685 15
        return $this->moduleManager;
686
    }
687
688
    /**
689
     * Returns the resource repository manager.
690
     *
691
     * @return RepositoryManager The repository manager.
692
     */
693 2 View Code Duplication
    public function getRepositoryManager()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
694
    {
695 2
        if (!$this->started) {
696
            throw new LogicException('Puli was not started');
697
        }
698
699 2
        if (!$this->repositoryManager && $this->context instanceof ProjectContext) {
700 1
            $this->repositoryManager = new RepositoryManagerImpl(
701 1
                $this->context,
702 1
                $this->getRepository(),
703 1
                $this->getModuleManager()->findModules(Expr::method('isEnabled', Expr::same(true))),
704 1
                $this->getJsonStorage()
705
            );
706
        }
707
708 2
        return $this->repositoryManager;
709
    }
710
711
    /**
712
     * Returns the resource discovery manager.
713
     *
714
     * @return DiscoveryManager The discovery manager.
715
     */
716 3 View Code Duplication
    public function getDiscoveryManager()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
717
    {
718 3
        if (!$this->started) {
719
            throw new LogicException('Puli was not started');
720
        }
721
722 3
        if (!$this->discoveryManager && $this->context instanceof ProjectContext) {
723 2
            $this->discoveryManager = new DiscoveryManagerImpl(
724 2
                $this->context,
725 2
                $this->getDiscovery(),
726 2
                $this->getModuleManager()->findModules(Expr::method('isEnabled', Expr::same(true))),
727 2
                $this->getJsonStorage(),
728 2
                $this->logger
729
            );
730
        }
731
732 3
        return $this->discoveryManager;
733
    }
734
735
    /**
736
     * Returns the asset manager.
737
     *
738
     * @return AssetManager The asset manager.
739
     */
740 2
    public function getAssetManager()
741
    {
742 2
        if (!$this->started) {
743
            throw new LogicException('Puli was not started');
744
        }
745
746 2
        if (!$this->assetManager && $this->context instanceof ProjectContext) {
747 1
            $this->assetManager = new DiscoveryAssetManager(
748 1
                $this->getDiscoveryManager(),
749 1
                $this->getServerManager()->getServers()
750
            );
751
        }
752
753 2
        return $this->assetManager;
754
    }
755
756
    /**
757
     * Returns the installation manager.
758
     *
759
     * @return InstallationManager The installation manager.
760
     */
761 2
    public function getInstallationManager()
762
    {
763 2
        if (!$this->started) {
764
            throw new LogicException('Puli was not started');
765
        }
766
767 2
        if (!$this->installationManager && $this->context instanceof ProjectContext) {
768 1
            $this->installationManager = new InstallationManagerImpl(
769 1
                $this->getContext(),
0 ignored issues
show
Compatibility introduced by
$this->getContext() of type object<Puli\Manager\Api\Context\Context> is not a sub-type of object<Puli\Manager\Api\Context\ProjectContext>. It seems like you assume a child class of the class Puli\Manager\Api\Context\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
770 1
                $this->getRepository(),
771 1
                $this->getServerManager()->getServers(),
772 1
                $this->getInstallerManager()
773
            );
774
        }
775
776 2
        return $this->installationManager;
777
    }
778
779
    /**
780
     * Returns the installer manager.
781
     *
782
     * @return InstallerManager The installer manager.
783
     */
784 15
    public function getInstallerManager()
785
    {
786 15
        if (!$this->started) {
787
            throw new LogicException('Puli was not started');
788
        }
789
790 15
        if (!$this->installerManager && $this->context instanceof ProjectContext) {
791 14
            $this->installerManager = new ModuleFileInstallerManager(
792 14
                $this->getRootModuleFileManager(),
793 14
                $this->getModuleManager()->getModules()
794
            );
795
        }
796
797 15
        return $this->installerManager;
798
    }
799
800
    /**
801
     * Returns the server manager.
802
     *
803
     * @return ServerManager The server manager.
804
     */
805 15
    public function getServerManager()
806
    {
807 15
        if (!$this->started) {
808
            throw new LogicException('Puli was not started');
809
        }
810
811 15
        if (!$this->serverManager && $this->context instanceof ProjectContext) {
812 14
            $this->serverManager = new ModuleFileServerManager(
813 14
                $this->getRootModuleFileManager(),
814 14
                $this->getInstallerManager()
815
            );
816
        }
817
818 15
        return $this->serverManager;
819
    }
820
821
    /**
822
     * Returns the resource URL generator.
823
     *
824
     * @return UrlGenerator The resource URL generator.
825
     */
826 2
    public function getUrlGenerator()
827
    {
828 2
        if (!$this->started) {
829
            throw new LogicException('Puli was not started');
830
        }
831
832 2
        if (!$this->urlGenerator && $this->context instanceof ProjectContext) {
833 1
            $urlFormats = array();
834 1
            foreach ($this->getServerManager()->getServers() as $server) {
835
                $urlFormats[$server->getName()] = $server->getUrlFormat();
836
            }
837
838 1
            $this->urlGenerator = new DiscoveryUrlGenerator($this->getDiscovery(), $urlFormats);
839
        }
840
841 2
        return $this->urlGenerator;
842
    }
843
844
    /**
845
     * Returns the file storage.
846
     *
847
     * @return Storage The storage.
848
     */
849 48
    public function getStorage()
850
    {
851 48
        if (!$this->storage) {
852 48
            $this->storage = new FilesystemStorage();
853
        }
854
855 48
        return $this->storage;
856
    }
857
858
    /**
859
     * Returns the configuration file serializer.
860
     *
861
     * @return ConfigFileConverter The configuration file serializer.
862
     */
863 46
    public function getConfigFileConverter()
864
    {
865 46
        if (!$this->configFileConverter) {
866 46
            $this->configFileConverter = new ConfigFileConverter();
867
        }
868
869 46
        return $this->configFileConverter;
870
    }
871
872
    /**
873
     * Returns the module file converter.
874
     *
875
     * @return JsonConverter The module file converter.
876
     */
877 14
    public function getModuleFileConverter()
878
    {
879 14
        if (!$this->moduleFileConverter) {
880 14
            $this->moduleFileConverter = $this->createValidatingConverter(
881 14
                new ModuleFileConverter($this->getJsonVersioner())
882
            );
883
        }
884
885 14
        return $this->moduleFileConverter;
886
    }
887
888
    /**
889
     * Returns the module file serializer with support for legacy versions.
890
     *
891
     * @return JsonConverter The module file converter.
892
     */
893 14 View Code Duplication
    public function getLegacyModuleFileConverter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
894
    {
895 14
        if (!$this->legacyModuleFileConverter) {
896 14
            $this->legacyModuleFileConverter = $this->createValidatingConverter(
897 14
                new MigratingConverter(
898 14
                    $this->getModuleFileConverter(),
899 14
                    ModuleFileConverter::VERSION,
900 14
                    $this->getModuleFileMigrationManager()
901
                ),
902
                function (stdClass $jsonData) {
903 14
                    if (isset($jsonData->{'$schema'})) {
904
                        return $jsonData->{'$schema'};
905
                    }
906
907
                    // BC with 1.0
908 14
                    return 'http://puli.io/schema/1.0/manager/module';
909 14
                }
910
            );
911
        }
912
913 14
        return $this->legacyModuleFileConverter;
914
    }
915
916
    /**
917
     * Returns the module file converter.
918
     *
919
     * @return JsonConverter The module file converter.
920
     */
921 26
    public function getRootModuleFileConverter()
922
    {
923 26
        if (!$this->rootModuleFileConverter) {
924 26
            $this->rootModuleFileConverter = $this->createValidatingConverter(
925 26
                new RootModuleFileConverter($this->getJsonVersioner())
926
            );
927
        }
928
929 26
        return $this->rootModuleFileConverter;
930
    }
931
932
    /**
933
     * Returns the module file serializer with support for legacy versions.
934
     *
935
     * @return JsonConverter The module file converter.
936
     */
937 26 View Code Duplication
    public function getLegacyRootModuleFileConverter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
938
    {
939 26
        if (!$this->legacyRootModuleFileConverter) {
940 26
            $this->legacyRootModuleFileConverter = $this->createValidatingConverter(
941 26
                new MigratingConverter(
942 26
                    $this->getRootModuleFileConverter(),
943 26
                    RootModuleFileConverter::VERSION,
944 26
                    $this->getModuleFileMigrationManager()
945
                ),
946 26
                function (stdClass $jsonData) {
947 26
                    if (isset($jsonData->{'$schema'})) {
948
                        return $jsonData->{'$schema'};
949
                    }
950
951
                    // BC with 1.0
952 26
                    return 'http://puli.io/schema/1.0/manager/module';
953 26
                }
954
            );
955
        }
956
957 26
        return $this->legacyRootModuleFileConverter;
958
    }
959
960
    /**
961
     * Returns the JSON encoder.
962
     *
963
     * @return JsonEncoder The JSON encoder.
964
     */
965 48
    public function getJsonEncoder()
966
    {
967 48
        if (!$this->jsonEncoder) {
968 48
            $this->jsonEncoder = new JsonEncoder();
969 48
            $this->jsonEncoder->setPrettyPrinting(true);
970 48
            $this->jsonEncoder->setEscapeSlash(false);
971 48
            $this->jsonEncoder->setTerminateWithLineFeed(true);
972
        }
973
974 48
        return $this->jsonEncoder;
975
    }
976
977
    /**
978
     * Returns the JSON decoder.
979
     *
980
     * @return JsonDecoder The JSON decoder.
981
     */
982 48
    public function getJsonDecoder()
983
    {
984 48
        if (!$this->jsonDecoder) {
985 48
            $this->jsonDecoder = new JsonDecoder();
986
        }
987
988 48
        return $this->jsonDecoder;
989
    }
990
991
    /**
992
     * Returns the JSON validator.
993
     *
994
     * @return JsonValidator The JSON validator.
995
     */
996 26
    public function getJsonValidator()
997
    {
998 26
        if (!$this->jsonValidator) {
999 26
            $uriRetriever = new UriRetriever();
1000
1001
            // Load puli.io schemas from the schema/ directory
1002 26
            $uriRetriever->setUriRetriever(new LocalUriRetriever());
1003
1004 26
            $this->jsonValidator = new JsonValidator(null, $uriRetriever);
1005
        }
1006
1007 26
        return $this->jsonValidator;
1008
    }
1009
1010 26
    private function activatePlugins()
1011
    {
1012 26
        foreach ($this->context->getRootModuleFile()->getPluginClasses() as $pluginClass) {
0 ignored issues
show
Bug introduced by
The method getRootModuleFile does only exist in Puli\Manager\Api\Context\ProjectContext, but not in Puli\Manager\Api\Context\Context.

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1013 25
            $this->validatePluginClass($pluginClass);
1014
1015
            /** @var PuliPlugin $plugin */
1016 23
            $plugin = new $pluginClass();
1017 23
            $plugin->activate($this);
1018
        }
1019 24
    }
1020
1021 23
    private function createGlobalContext()
1022
    {
1023 23
        $baseConfig = new DefaultConfig();
1024 23
        $homeDir = self::parseHomeDirectory();
1025
1026 22
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
1027 20
            $baseConfig = $configFile->getConfig();
1028
        }
1029
1030 22
        $config = new EnvConfig($baseConfig);
1031
1032 22
        return new Context($homeDir, $config, $configFile, $this->dispatcher);
1033
    }
1034
1035
    /**
1036
     * Creates the context of a Puli project.
1037
     *
1038
     * The home directory is read from the context variable "PULI_HOME".
1039
     * If this variable is not set, the home directory defaults to:
1040
     *
1041
     *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
1042
     *    "HOME".
1043
     *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
1044
     *    variable "APPDATA".
1045
     *
1046
     * If none of these variables can be found, an exception is thrown.
1047
     *
1048
     * A .htaccess file is put into the home directory to protect it from web
1049
     * access.
1050
     *
1051
     * @param string $rootDir The path to the project.
1052
     *
1053
     * @return ProjectContext The project context.
1054
     */
1055 27
    private function createProjectContext($rootDir, $env)
1056
    {
1057 27
        Assert::fileExists($rootDir, 'Could not load Puli context: The root %s does not exist.');
1058 27
        Assert::directory($rootDir, 'Could not load Puli context: The root %s is a file. Expected a directory.');
1059
1060 27
        $baseConfig = new DefaultConfig();
1061 27
        $homeDir = self::parseHomeDirectory();
1062
1063 27
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
1064 26
            $baseConfig = $configFile->getConfig();
1065
        }
1066
1067
        // Create a storage without the factory manager
1068 27
        $jsonStorage = new JsonStorage(
1069 27
            $this->getStorage(),
1070 27
            new JsonConverterProvider($this),
1071 27
            $this->getJsonEncoder(),
1072 27
            $this->getJsonDecoder()
1073
        );
1074
1075 27
        $rootDir = Path::canonicalize($rootDir);
1076 27
        $rootFilePath = $this->rootDir.'/puli.json';
1077
1078
        try {
1079 27
            $rootModuleFile = $jsonStorage->loadRootModuleFile($rootFilePath, $baseConfig);
1080 1
        } catch (FileNotFoundException $e) {
1081 1
            $rootModuleFile = new RootModuleFile(null, $rootFilePath, $baseConfig);
1082
        }
1083
1084 27
        $config = new EnvConfig($rootModuleFile->getConfig());
1085
1086 27
        return new ProjectContext($homeDir, $rootDir, $config, $rootModuleFile, $configFile, $this->dispatcher, $env);
1087
    }
1088
1089
    /**
1090
     * Decorates a converter with a {@link ValidatingConverter}.
1091
     *
1092
     * @param JsonConverter        $innerConverter The converter to decorate.
1093
     * @param string|callable|null $schema         The schema.
1094
     *
1095
     * @return ValidatingConverter The decorated converter.
1096
     */
1097 26
    private function createValidatingConverter(JsonConverter $innerConverter, $schema = null)
1098
    {
1099 26
        return new ValidatingConverter($innerConverter, $schema, $this->getJsonValidator());
1100
    }
1101
1102
    /**
1103
     * Returns the JSON file storage.
1104
     *
1105
     * @return JsonStorage The JSON file storage.
1106
     */
1107 15
    private function getJsonStorage()
1108
    {
1109 15
        if (!$this->jsonStorage) {
1110 15
            $this->jsonStorage = new JsonStorage(
1111 15
                $this->getStorage(),
1112 15
                new JsonConverterProvider($this),
1113 15
                $this->getJsonEncoder(),
1114 15
                $this->getJsonDecoder(),
1115 15
                $this->getFactoryManager()
1116
            );
1117
        }
1118
1119 15
        return $this->jsonStorage;
1120
    }
1121
1122
    /**
1123
     * Returns the JSON versioner.
1124
     *
1125
     * @return JsonVersioner The JSON versioner.
1126
     */
1127 26
    private function getJsonVersioner()
1128
    {
1129 26
        if (!$this->jsonVersioner) {
1130 26
            $this->jsonVersioner = new ChainVersioner(array(
1131
                // check the schema of the "$schema" field by default
1132 26
                new SchemaUriVersioner(),
1133
                // fall back to the "version" field for 1.0
1134 26
                new VersionFieldVersioner(),
1135
            ));
1136
        }
1137
1138 26
        return $this->jsonVersioner;
1139
    }
1140
1141
    /**
1142
     * Returns the migration manager for module files.
1143
     *
1144
     * @return MigrationManager The migration manager.
1145
     */
1146 26
    private function getModuleFileMigrationManager()
1147
    {
1148 26
        if (!$this->moduleFileMigrationManager) {
1149 26
            $this->moduleFileMigrationManager = new MigrationManager(array(
1150 26
                new ModuleFile10To20Migration(),
1151 26
            ), $this->getJsonVersioner());
1152
        }
1153
1154 26
        return $this->moduleFileMigrationManager;
1155
    }
1156
1157
    /**
1158
     * Validates the given plugin class name.
1159
     *
1160
     * @param string $pluginClass The fully qualified name of a plugin class.
1161
     */
1162 25
    private function validatePluginClass($pluginClass)
1163
    {
1164 25
        if (!class_exists($pluginClass)) {
1165 1
            throw new InvalidConfigException(sprintf(
1166 1
                'The plugin class %s does not exist.',
1167
                $pluginClass
1168
            ));
1169
        }
1170
1171 24
        if (!in_array('Puli\Manager\Api\PuliPlugin', class_implements($pluginClass))) {
1172 1
            throw new InvalidConfigException(sprintf(
1173 1
                'The plugin class %s must implement PuliPlugin.',
1174
                $pluginClass
1175
            ));
1176
        }
1177 23
    }
1178
1179 49
    private function loadConfigFile($homeDir, Config $baseConfig)
1180
    {
1181 49
        if (null === $homeDir) {
1182 2
            return null;
1183
        }
1184
1185 47
        Assert::fileExists($homeDir, 'Could not load Puli context: The home directory %s does not exist.');
1186 47
        Assert::directory($homeDir, 'Could not load Puli context: The home directory %s is a file. Expected a directory.');
1187
1188
        // Create a storage without the factory manager
1189 47
        $jsonStorage = new JsonStorage(
1190 47
            $this->getStorage(),
1191 47
            new JsonConverterProvider($this),
1192 47
            $this->getJsonEncoder(),
1193 47
            $this->getJsonDecoder()
1194
        );
1195
1196 47
        $configPath = Path::canonicalize($homeDir).'/config.json';
1197
1198
        try {
1199 47
            return $jsonStorage->loadConfigFile($configPath, $baseConfig);
1200 1
        } catch (FileNotFoundException $e) {
1201
            // It's ok if no config.json exists. We'll work with
1202
            // DefaultConfig instead
1203 1
            return null;
1204
        }
1205
    }
1206
}
1207