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

Puli   F

Complexity

Total Complexity 116

Size/Duplication

Total Lines 944
Duplicated Lines 10.28 %

Coupling/Cohesion

Components 1
Dependencies 39

Test Coverage

Coverage 82.23%

Importance

Changes 16
Bugs 5 Features 5
Metric Value
wmc 116
c 16
b 5
f 5
lcom 1
cbo 39
dl 97
loc 944
ccs 236
cts 287
cp 0.8223
rs 1.0434

43 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 16 4
A getRootPackageFileManager() 15 15 4
A getPackageManager() 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 getConfigFileSerializer() 0 8 2
A getPackageFileSerializer() 0 13 2
A getCacheFileSerializer() 0 8 2
A getCacheFileStorage() 0 11 2
A getCacheManager() 0 12 2
A activatePlugins() 0 10 2
A createGlobalContext() 0 13 2
B createProjectContext() 0 27 3
A getConfigFileStorage() 0 12 2
A getPackageFileStorage() 0 12 2
A validatePluginClass() 0 16 3
A loadConfigFile() 0 21 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 Puli 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 Puli, 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 LogicException;
15
use Psr\Log\LoggerInterface;
16
use Puli\Discovery\Api\Discovery;
17
use Puli\Discovery\Api\EditableDiscovery;
18
use Puli\Manager\Api\Asset\AssetManager;
19
use Puli\Manager\Api\Cache\CacheFileSerializer;
20
use Puli\Manager\Api\Cache\CacheManager;
21
use Puli\Manager\Api\Config\Config;
22
use Puli\Manager\Api\Config\ConfigFileManager;
23
use Puli\Manager\Api\Config\ConfigFileSerializer;
24
use Puli\Manager\Api\Context\Context;
25
use Puli\Manager\Api\Context\ProjectContext;
26
use Puli\Manager\Api\Discovery\DiscoveryManager;
27
use Puli\Manager\Api\Factory\FactoryManager;
28
use Puli\Manager\Api\Installation\InstallationManager;
29
use Puli\Manager\Api\Installer\InstallerManager;
30
use Puli\Manager\Api\Package\PackageFileSerializer;
31
use Puli\Manager\Api\Package\PackageManager;
32
use Puli\Manager\Api\Package\RootPackageFile;
33
use Puli\Manager\Api\Package\RootPackageFileManager;
34
use Puli\Manager\Api\Repository\RepositoryManager;
35
use Puli\Manager\Api\Server\ServerManager;
36
use Puli\Manager\Api\Storage\Storage;
37
use Puli\Manager\Assert\Assert;
38
use Puli\Manager\Asset\DiscoveryAssetManager;
39
use Puli\Manager\Cache\CacheFileStorage;
40
use Puli\Manager\Cache\CacheJsonSerializer;
41
use Puli\Manager\Cache\CacheManagerImpl;
42
use Puli\Manager\Config\ConfigFileManagerImpl;
43
use Puli\Manager\Config\ConfigFileStorage;
44
use Puli\Manager\Config\ConfigJsonSerializer;
45
use Puli\Manager\Config\DefaultConfig;
46
use Puli\Manager\Config\EnvConfig;
47
use Puli\Manager\Discovery\DiscoveryManagerImpl;
48
use Puli\Manager\Factory\FactoryManagerImpl;
49
use Puli\Manager\Factory\Generator\DefaultGeneratorRegistry;
50
use Puli\Manager\Filesystem\FilesystemStorage;
51
use Puli\Manager\Installation\InstallationManagerImpl;
52
use Puli\Manager\Installer\PackageFileInstallerManager;
53
use Puli\Manager\Migration\MigrationManager;
54
use Puli\Manager\Package\PackageFileStorage;
55
use Puli\Manager\Package\PackageJsonSerializer;
56
use Puli\Manager\Package\PackageManagerImpl;
57
use Puli\Manager\Package\RootPackageFileManagerImpl;
58
use Puli\Manager\Php\ClassWriter;
59
use Puli\Manager\Repository\RepositoryManagerImpl;
60
use Puli\Manager\Server\PackageFileServerManager;
61
use Puli\Manager\Util\System;
62
use Puli\Repository\Api\EditableRepository;
63
use Puli\Repository\Api\ResourceRepository;
64
use Puli\UrlGenerator\Api\UrlGenerator;
65
use Puli\UrlGenerator\DiscoveryUrlGenerator;
66
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
67
use Webmozart\Expression\Expr;
68
use Webmozart\PathUtil\Path;
69
70
/**
71
 * The Puli service locator.
72
 *
73
 * Use this class to access the managers provided by this package:
74
 *
75
 * ```php
76
 * $puli = new Puli(getcwd());
77
 * $puli->start();
78
 *
79
 * $packageManager = $puli->getPackageManager();
80
 * ```
81
 *
82
 * The `Puli` class either operates in the global or a project context:
83
 *
84
 *  * The "global context" is not tied to a specific root package. A global
85
 *    context only loads the settings of the "config.json" file in the home
86
 *    directory. The `Puli` class operates in the global context if no
87
 *    project root directory is passed to the constructor. In the global
88
 *    context, only the global config file manager is available.
89
 *  * The "project context" is tied to a specific Puli project. You need to
90
 *    pass the path to the project's root directory to the constructor or to
91
 *    {@link setRootDirectory()}. The configuration of the "puli.json" file in
92
 *    the root directory is used to configure the managers.
93
 *
94
 * The `Puli` class creates four kinds of managers:
95
 *
96
 *  * The "config file manager" allows you to modify entries of the
97
 *    "config.json" file in the home directory.
98
 *  * The "package file manager" manages modifications to the "puli.json" file
99
 *    of a Puli project.
100
 *  * The "package manager" manages the package repository of a Puli project.
101
 *  * The "repository manager" manages the resource repository of a Puli
102
 *    project.
103
 *  * The "discovery manager" manages the resource discovery of a Puli project.
104
 *
105
 * The home directory is read from the context variable "PULI_HOME".
106
 * If this variable is not set, the home directory defaults to:
107
 *
108
 *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
109
 *    "HOME".
110
 *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
111
 *    variable "APPDATA".
112
 *
113
 * If none of these variables can be found, an exception is thrown.
114
 *
115
 * A .htaccess file is put into the home directory to protect it from web
116
 * access.
117
 *
118
 * @since  1.0
119
 *
120
 * @author Bernhard Schussek <[email protected]>
121
 */
122
class Puli
123
{
124
    /**
125
     * @var string|null
126
     */
127
    private $rootDir;
128
129
    /**
130
     * @var string
131
     */
132
    private $env;
133
134
    /**
135
     * @var EventDispatcherInterface|null
136
     */
137
    private $dispatcher;
138
139
    /**
140
     * @var Context|ProjectContext
141
     */
142
    private $context;
143
144
    /**
145
     * @var ResourceRepository
146
     */
147
    private $repo;
148
149
    /**
150
     * @var Discovery
151
     */
152
    private $discovery;
153
154
    /**
155
     * @var object
156
     */
157
    private $factory;
158
159
    /**
160
     * @var FactoryManager
161
     */
162
    private $factoryManager;
163
164
    /**
165
     * @var ConfigFileManager
166
     */
167
    private $configFileManager;
168
169
    /**
170
     * @var RootPackageFileManager
171
     */
172
    private $rootPackageFileManager;
173
174
    /**
175
     * @var PackageManager
176
     */
177
    private $packageManager;
178
179
    /**
180
     * @var RepositoryManager
181
     */
182
    private $repositoryManager;
183
184
    /**
185
     * @var DiscoveryManager
186
     */
187
    private $discoveryManager;
188
189
    /**
190
     * @var AssetManager
191
     */
192
    private $assetManager;
193
194
    /**
195
     * @var InstallationManager
196
     */
197
    private $installationManager;
198
199
    /**
200
     * @var InstallerManager
201
     */
202
    private $installerManager;
203
204
    /**
205
     * @var ServerManager
206
     */
207
    private $serverManager;
208
209
    /**
210
     * @var UrlGenerator
211
     */
212
    private $urlGenerator;
213
214
    /**
215
     * @var Storage|null
216
     */
217
    private $storage;
218
219
    /**
220
     * @var ConfigFileStorage|null
221
     */
222
    private $configFileStorage;
223
224
    /**
225
     * @var ConfigFileSerializer|null
226
     */
227
    private $configFileSerializer;
228
229
    /**
230
     * @var PackageFileStorage|null
231
     */
232
    private $packageFileStorage;
233
234
    /**
235
     * @var PackageFileSerializer|null
236
     */
237
    private $packageFileSerializer;
238
239
    /**
240
     * @var LoggerInterface
241
     */
242
    private $logger;
243
244
    /**
245
     * @var bool
246
     */
247
    private $started = false;
248
249
    /**
250
     * @var bool
251
     */
252
    private $pluginsEnabled = true;
253
254
    /**
255
     * @var CacheFileSerializer|null
256
     */
257
    private $cacheFileSerializer;
258
259
    /**
260
     * @var CacheFileStorage|null
261
     */
262
    private $cacheFileStorage;
263
264
    /**
265
     * @var CacheManager|null
266
     */
267
    private $cacheManager;
268
269
    /**
270
     * Parses the system context for a home directory.
271
     *
272
     * @return null|string Returns the path to the home directory or `null`
273
     *                     if none was found.
274
     */
275 50
    private static function parseHomeDirectory()
276
    {
277
        try {
278 50
            $homeDir = System::parseHomeDirectory();
279
280 47
            System::denyWebAccess($homeDir);
281
282 47
            return $homeDir;
283 3
        } catch (InvalidConfigException $e) {
284
            // Context variable was not found -> no home directory
285
            // This happens often on web servers where the home directory is
286
            // not set manually
287 2
            return null;
288
        }
289
    }
290
291
    /**
292
     * Creates a new instance for the given Puli project.
293
     *
294
     * @param string|null $rootDir The root directory of the Puli project.
295
     *                             If none is passed, the object operates in
296
     *                             the global context. You can set or switch
297
     *                             the root directories later on by calling
298
     *                             {@link setRootDirectory()}.
299
     * @param string      $env     One of the {@link Environment} constants.
300
     *
301
     * @see Puli, start()
302
     */
303 52
    public function __construct($rootDir = null, $env = Environment::DEV)
304
    {
305 52
        $this->setRootDirectory($rootDir);
306 52
        $this->setEnvironment($env);
307 52
    }
308
309
    /**
310
     * Starts the service container.
311
     */
312 50
    public function start()
313
    {
314 50
        if ($this->started) {
315
            throw new LogicException('Puli is already started');
316
        }
317
318 50
        if (null !== $this->rootDir) {
319 27
            $this->context = $this->createProjectContext($this->rootDir, $this->env);
320 27
            $bootstrapFile = $this->context->getConfig()->get(Config::BOOTSTRAP_FILE);
321
322
            // Run the project's bootstrap file to enable project-specific
323
            // autoloading
324 27
            if (null !== $bootstrapFile) {
325
                // Backup autoload functions of the PHAR
326 1
                $autoloadFunctions = spl_autoload_functions();
327
328 1
                foreach ($autoloadFunctions as $autoloadFunction) {
329 1
                    spl_autoload_unregister($autoloadFunction);
330
                }
331
332
                // Add project-specific autoload functions
333 1
                require_once Path::makeAbsolute($bootstrapFile, $this->rootDir);
334
335
                // Prepend autoload functions of the PHAR again
336
                // This is needed if the user specific autoload functions were
337
                // added with $prepend=true (as done by Composer)
338
                // Classes in the PHAR should always take precedence
339 27
                for ($i = count($autoloadFunctions) - 1; $i >= 0; --$i) {
340 1
                    spl_autoload_register($autoloadFunctions[$i], true, true);
341
                }
342
            }
343
        } else {
344 23
            $this->context = $this->createGlobalContext();
345
        }
346
347 49
        $this->dispatcher = $this->context->getEventDispatcher();
348 49
        $this->started = true;
349
350
        // Start plugins once the container is running
351 49
        if ($this->rootDir && $this->pluginsEnabled) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rootDir of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
352 26
            $this->activatePlugins();
353
        }
354 47
    }
355
356
    /**
357
     * Returns whether the service container is started.
358
     *
359
     * @return bool Returns `true` if the container is started and `false`
360
     *              otherwise.
361
     */
362
    public function isStarted()
363
    {
364
        return $this->started;
365
    }
366
367
    /**
368
     * Sets the root directory of the managed Puli project.
369
     *
370
     * @param string|null $rootDir The root directory of the managed Puli
371
     *                             project or `null` to start Puli outside of a
372
     *                             specific project.
373
     */
374 52
    public function setRootDirectory($rootDir)
375
    {
376 52
        if ($this->started) {
377
            throw new LogicException('Puli is already started');
378
        }
379
380 52
        Assert::nullOrDirectory($rootDir);
381
382 52
        $this->rootDir = $rootDir ? Path::canonicalize($rootDir) : null;
383 52
    }
384
385
    /**
386
     * Sets the environment of the managed Puli project.
387
     *
388
     * @param string $env One of the {@link Environment} constants.
389
     */
390 52
    public function setEnvironment($env)
391
    {
392 52
        if ($this->started) {
393
            throw new LogicException('Puli is already started');
394
        }
395
396 52
        Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s');
397
398 52
        $this->env = $env;
399 52
    }
400
401
    /**
402
     * Retturns the environment of the managed Puli project.
403
     *
404
     * @return string One of the {@link Environment} constants.
405
     */
406 2
    public function getEnvironment()
407
    {
408 2
        return $this->env;
409
    }
410
411
    /**
412
     * Returns the root directory of the managed Puli project.
413
     *
414
     * If no Puli project is managed at the moment, `null` is returned.
415
     *
416
     * @return string|null The root directory of the managed Puli project or
417
     *                     `null` if none is set.
418
     */
419 4
    public function getRootDirectory()
420
    {
421 4
        return $this->rootDir;
422
    }
423
424
    /**
425
     * Sets the logger to use.
426
     *
427
     * @param LoggerInterface $logger The logger to use.
428
     */
429
    public function setLogger(LoggerInterface $logger)
430
    {
431
        if ($this->started) {
432
            throw new LogicException('Puli is already started');
433
        }
434
435
        $this->logger = $logger;
436
    }
437
438
    /**
439
     * Returns the used logger.
440
     *
441
     * @return LoggerInterface The used logger.
442
     */
443
    public function getLogger()
444
    {
445
        return $this->logger;
446
    }
447
448
    /**
449
     * Sets the event dispatcher to use.
450
     *
451
     * @param EventDispatcherInterface $dispatcher The event dispatcher to use.
452
     */
453 2
    public function setEventDispatcher(EventDispatcherInterface $dispatcher)
454
    {
455 2
        if ($this->started) {
456
            throw new LogicException('Puli is already started');
457
        }
458
459 2
        $this->dispatcher = $dispatcher;
460 2
    }
461
462
    /**
463
     * Returns the used event dispatcher.
464
     *
465
     * @return EventDispatcherInterface|null The used logger.
466
     */
467 3
    public function getEventDispatcher()
468
    {
469 3
        return $this->dispatcher;
470
    }
471
472
    /**
473
     * Enables all Puli plugins.
474
     */
475
    public function enablePlugins()
476
    {
477
        $this->pluginsEnabled = true;
478
    }
479
480
    /**
481
     * Disables all Puli plugins.
482
     */
483 1
    public function disablePlugins()
484
    {
485 1
        $this->pluginsEnabled = false;
486 1
    }
487
488
    /**
489
     * Returns whether Puli plugins are enabled.
490
     *
491
     * @return bool Returns `true` if Puli plugins will be loaded and `false`
492
     *              otherwise.
493
     */
494
    public function arePluginsEnabled()
495
    {
496
        return $this->pluginsEnabled;
497
    }
498
499
    /**
500
     * Returns the context.
501
     *
502
     * @return Context|ProjectContext The context.
503
     */
504 30
    public function getContext()
505
    {
506 30
        if (!$this->started) {
507
            throw new LogicException('Puli was not started');
508
        }
509
510 30
        return $this->context;
511
    }
512
513
    /**
514
     * Returns the resource repository of the project.
515
     *
516
     * @return EditableRepository The resource repository.
517
     */
518 8 View Code Duplication
    public function getRepository()
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...
519
    {
520 8
        if (!$this->started) {
521
            throw new LogicException('Puli was not started');
522
        }
523
524 8
        if (!$this->context instanceof ProjectContext) {
525 1
            return null;
526
        }
527
528 7
        if (!$this->repo) {
529 7
            $this->repo = $this->getFactory()->createRepository();
530
        }
531
532 7
        return $this->repo;
533
    }
534
535
    /**
536
     * Returns the resource discovery of the project.
537
     *
538
     * @return EditableDiscovery The resource discovery.
539
     */
540 5 View Code Duplication
    public function getDiscovery()
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...
541
    {
542 5
        if (!$this->started) {
543
            throw new LogicException('Puli was not started');
544
        }
545
546 5
        if (!$this->context instanceof ProjectContext) {
547 1
            return null;
548
        }
549
550 4
        if (!$this->discovery) {
551 4
            $this->discovery = $this->getFactory()->createDiscovery($this->getRepository());
552
        }
553
554 4
        return $this->discovery;
555
    }
556
557
    /**
558
     * @return object
559
     */
560 9
    public function getFactory()
561
    {
562 9
        if (!$this->started) {
563
            throw new LogicException('Puli was not started');
564
        }
565
566 9
        if (!$this->factory && $this->context instanceof ProjectContext) {
567 8
            $this->factory = $this->getFactoryManager()->createFactory();
568
        }
569
570 9
        return $this->factory;
571
    }
572
573
    /**
574
     * @return FactoryManager
575
     */
576 16
    public function getFactoryManager()
577
    {
578 16
        if (!$this->started) {
579
            throw new LogicException('Puli was not started');
580
        }
581
582 16
        if (!$this->factoryManager && $this->context instanceof ProjectContext) {
583 14
            $this->factoryManager = new FactoryManagerImpl(
584 14
                $this->context,
585 14
                new DefaultGeneratorRegistry(),
586 14
                new ClassWriter()
587
            );
588
589
            // Don't set via the constructor to prevent cyclic dependencies
590 14
            $this->factoryManager->setPackages($this->getPackageManager()->getPackages());
591 14
            $this->factoryManager->setServers($this->getServerManager()->getServers());
592
        }
593
594 16
        return $this->factoryManager;
595
    }
596
597
    /**
598
     * Returns the configuration file manager.
599
     *
600
     * @return ConfigFileManager The configuration file manager.
601
     */
602 2
    public function getConfigFileManager()
603
    {
604 2
        if (!$this->started) {
605
            throw new LogicException('Puli was not started');
606
        }
607
608 2
        if (!$this->configFileManager && $this->context->getHomeDirectory()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->context->getHomeDirectory() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
609 2
            $this->configFileManager = new ConfigFileManagerImpl(
610 2
                $this->context,
611 2
                $this->getConfigFileStorage(),
612 2
                $this->getFactoryManager()
0 ignored issues
show
Unused Code introduced by
The call to ConfigFileManagerImpl::__construct() has too many arguments starting with $this->getFactoryManager().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
613
            );
614
        }
615
616 2
        return $this->configFileManager;
617
    }
618
619
    /**
620
     * Returns the root package file manager.
621
     *
622
     * @return RootPackageFileManager The package file manager.
623
     */
624 15 View Code Duplication
    public function getRootPackageFileManager()
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...
625
    {
626 15
        if (!$this->started) {
627
            throw new LogicException('Puli was not started');
628
        }
629
630 15
        if (!$this->rootPackageFileManager && $this->context instanceof ProjectContext) {
631 14
            $this->rootPackageFileManager = new RootPackageFileManagerImpl(
632 14
                $this->context,
633 14
                $this->getPackageFileStorage()
634
            );
635
        }
636
637 15
        return $this->rootPackageFileManager;
638
    }
639
640
    /**
641
     * Returns the package manager.
642
     *
643
     * @return PackageManager The package manager.
644
     */
645 15 View Code Duplication
    public function getPackageManager()
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...
646
    {
647 15
        if (!$this->started) {
648
            throw new LogicException('Puli was not started');
649
        }
650
651 15
        if (!$this->packageManager && $this->context instanceof ProjectContext) {
652 14
            $this->packageManager = new PackageManagerImpl(
653 14
                $this->context,
654 14
                $this->getPackageFileStorage()
655
            );
656
        }
657
658 15
        return $this->packageManager;
659
    }
660
661
    /**
662
     * Returns the resource repository manager.
663
     *
664
     * @return RepositoryManager The repository manager.
665
     */
666 2 View Code Duplication
    public function getRepositoryManager()
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...
667
    {
668 2
        if (!$this->started) {
669
            throw new LogicException('Puli was not started');
670
        }
671
672 2
        if (!$this->repositoryManager && $this->context instanceof ProjectContext) {
673 1
            $this->repositoryManager = new RepositoryManagerImpl(
674 1
                $this->context,
675 1
                $this->getRepository(),
676 1
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
677 1
                $this->getPackageFileStorage()
678
            );
679
        }
680
681 2
        return $this->repositoryManager;
682
    }
683
684
    /**
685
     * Returns the resource discovery manager.
686
     *
687
     * @return DiscoveryManager The discovery manager.
688
     */
689 3 View Code Duplication
    public function getDiscoveryManager()
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...
690
    {
691 3
        if (!$this->started) {
692
            throw new LogicException('Puli was not started');
693
        }
694
695 3
        if (!$this->discoveryManager && $this->context instanceof ProjectContext) {
696 2
            $this->discoveryManager = new DiscoveryManagerImpl(
697 2
                $this->context,
698 2
                $this->getDiscovery(),
699 2
                $this->getPackageManager()->findPackages(Expr::method('isEnabled', Expr::same(true))),
700 2
                $this->getPackageFileStorage(),
701 2
                $this->logger
702
            );
703
        }
704
705 3
        return $this->discoveryManager;
706
    }
707
708
    /**
709
     * Returns the asset manager.
710
     *
711
     * @return AssetManager The asset manager.
712
     */
713 2
    public function getAssetManager()
714
    {
715 2
        if (!$this->started) {
716
            throw new LogicException('Puli was not started');
717
        }
718
719 2
        if (!$this->assetManager && $this->context instanceof ProjectContext) {
720 1
            $this->assetManager = new DiscoveryAssetManager(
721 1
                $this->getDiscoveryManager(),
722 1
                $this->getServerManager()->getServers()
723
            );
724
        }
725
726 2
        return $this->assetManager;
727
    }
728
729
    /**
730
     * Returns the installation manager.
731
     *
732
     * @return InstallationManager The installation manager.
733
     */
734 2
    public function getInstallationManager()
735
    {
736 2
        if (!$this->started) {
737
            throw new LogicException('Puli was not started');
738
        }
739
740 2
        if (!$this->installationManager && $this->context instanceof ProjectContext) {
741 1
            $this->installationManager = new InstallationManagerImpl(
742 1
                $this->getContext(),
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...
743 1
                $this->getRepository(),
744 1
                $this->getServerManager()->getServers(),
745 1
                $this->getInstallerManager()
746
            );
747
        }
748
749 2
        return $this->installationManager;
750
    }
751
752
    /**
753
     * Returns the installer manager.
754
     *
755
     * @return InstallerManager The installer manager.
756
     */
757 15
    public function getInstallerManager()
758
    {
759 15
        if (!$this->started) {
760
            throw new LogicException('Puli was not started');
761
        }
762
763 15
        if (!$this->installerManager && $this->context instanceof ProjectContext) {
764 14
            $this->installerManager = new PackageFileInstallerManager(
765 14
                $this->getRootPackageFileManager(),
766 14
                $this->getPackageManager()->getPackages()
767
            );
768
        }
769
770 15
        return $this->installerManager;
771
    }
772
773
    /**
774
     * Returns the server manager.
775
     *
776
     * @return ServerManager The server manager.
777
     */
778 15
    public function getServerManager()
779
    {
780 15
        if (!$this->started) {
781
            throw new LogicException('Puli was not started');
782
        }
783
784 15
        if (!$this->serverManager && $this->context instanceof ProjectContext) {
785 14
            $this->serverManager = new PackageFileServerManager(
786 14
                $this->getRootPackageFileManager(),
787 14
                $this->getInstallerManager()
788
            );
789
        }
790
791 15
        return $this->serverManager;
792
    }
793
794
    /**
795
     * Returns the resource URL generator.
796
     *
797
     * @return UrlGenerator The resource URL generator.
798
     */
799 2
    public function getUrlGenerator()
800
    {
801 2
        if (!$this->started) {
802
            throw new LogicException('Puli was not started');
803
        }
804
805 2
        if (!$this->urlGenerator && $this->context instanceof ProjectContext) {
806 1
            $urlFormats = array();
807 1
            foreach ($this->getServerManager()->getServers() as $server) {
808
                $urlFormats[$server->getName()] = $server->getUrlFormat();
809
            }
810
811 1
            $this->urlGenerator = new DiscoveryUrlGenerator($this->getDiscovery(), $urlFormats);
812
        }
813
814 2
        return $this->urlGenerator;
815
    }
816
817
    /**
818
     * Returns the cached file storage.
819
     *
820
     * @return Storage The storage.
821
     */
822 48
    public function getStorage()
823
    {
824 48
        if (!$this->storage) {
825 48
            $this->storage = new FilesystemStorage();
826
        }
827
828 48
        return $this->storage;
829
    }
830
831
    /**
832
     * Returns the cached configuration file serializer.
833
     *
834
     * @return ConfigFileSerializer The configuration file serializer.
835
     */
836 47
    public function getConfigFileSerializer()
837
    {
838 47
        if (!$this->configFileSerializer) {
839 47
            $this->configFileSerializer = new ConfigJsonSerializer();
840
        }
841
842 47
        return $this->configFileSerializer;
843
    }
844
845
    /**
846
     * Returns the cached package file serializer.
847
     *
848
     * @return PackageFileSerializer The package file serializer.
849
     */
850 27
    public function getPackageFileSerializer()
851
    {
852 27
        if (!$this->packageFileSerializer) {
853 27
            $this->packageFileSerializer = new PackageJsonSerializer(
854 27
                new MigrationManager(array(
855
                    // Add future migrations here
856 27
                )),
857 27
                __DIR__.'/../../res/schema'
858
            );
859
        }
860
861 27
        return $this->packageFileSerializer;
862
    }
863
864
    /**
865
     * Returns the cache file serializer.
866
     *
867
     * @return CacheFileSerializer The cache file serializer.
868
     */
869
    public function getCacheFileSerializer()
870
    {
871
        if (!$this->cacheFileSerializer) {
872
            $this->cacheFileSerializer = new CacheJsonSerializer($this->getPackageFileSerializer());
0 ignored issues
show
Compatibility introduced by
$this->getPackageFileSerializer() of type object<Puli\Manager\Api\...\PackageFileSerializer> is not a sub-type of object<Puli\Manager\Pack...\PackageJsonSerializer>. It seems like you assume a concrete implementation of the interface Puli\Manager\Api\Package\PackageFileSerializer 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...
873
        }
874
875
        return $this->cacheFileSerializer;
876
    }
877
878
    /**
879
     * Returns the cache file storage.
880
     *
881
     * @return CacheFileStorage The cache file storage.
882
     */
883
    public function getCacheFileStorage()
884
    {
885
        if (!$this->cacheFileStorage) {
886
            $this->cacheFileStorage = new CacheFileStorage(
887
                $this->getStorage(),
888
                $this->getCacheFileSerializer()
889
            );
890
        }
891
892
        return $this->cacheFileStorage;
893
    }
894
895
    /**
896
     * Returns the cached configuration manager.
897
     *
898
     * @return CacheManager The cached configuration manager.
899
     */
900
    public function getCacheManager()
901
    {
902
        if (!$this->cacheManager) {
903
            $this->cacheManager = new CacheManagerImpl(
904
                $this->getPackageManager(),
905
                $this->getCacheFileStorage(),
906
                $this->context
0 ignored issues
show
Compatibility introduced by
$this->context 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...
907
            );
908
        }
909
910
        return $this->cacheManager;
911
    }
912
913 26
    private function activatePlugins()
914
    {
915 26
        foreach ($this->context->getRootPackageFile()->getPluginClasses() as $pluginClass) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Puli\Manager\Api\Context\Context as the method getRootPackageFile() does only exist in the following sub-classes of Puli\Manager\Api\Context\Context: Puli\Manager\Api\Context\ProjectContext. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
916 25
            $this->validatePluginClass($pluginClass);
917
918
            /** @var PuliPlugin $plugin */
919 23
            $plugin = new $pluginClass();
920 23
            $plugin->activate($this);
921
        }
922 24
    }
923
924 23
    private function createGlobalContext()
925
    {
926 23
        $baseConfig = new DefaultConfig();
927 23
        $homeDir = self::parseHomeDirectory();
928
929 22
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
930 20
            $baseConfig = $configFile->getConfig();
931
        }
932
933 22
        $config = new EnvConfig($baseConfig);
934
935 22
        return new Context($homeDir, $config, $configFile, $this->dispatcher);
936
    }
937
938
    /**
939
     * Creates the context of a Puli project.
940
     *
941
     * The home directory is read from the context variable "PULI_HOME".
942
     * If this variable is not set, the home directory defaults to:
943
     *
944
     *  * `$HOME/.puli` on Linux, where `$HOME` is the context variable
945
     *    "HOME".
946
     *  * `$APPDATA/Puli` on Windows, where `$APPDATA` is the context
947
     *    variable "APPDATA".
948
     *
949
     * If none of these variables can be found, an exception is thrown.
950
     *
951
     * A .htaccess file is put into the home directory to protect it from web
952
     * access.
953
     *
954
     * @param string $rootDir The path to the project.
955
     *
956
     * @return ProjectContext The project context.
957
     */
958 27
    private function createProjectContext($rootDir, $env)
959
    {
960 27
        Assert::fileExists($rootDir, 'Could not load Puli context: The root %s does not exist.');
961 27
        Assert::directory($rootDir, 'Could not load Puli context: The root %s is a file. Expected a directory.');
962
963 27
        $baseConfig = new DefaultConfig();
964 27
        $homeDir = self::parseHomeDirectory();
965
966 27
        if (null !== $configFile = $this->loadConfigFile($homeDir, $baseConfig)) {
967 26
            $baseConfig = $configFile->getConfig();
968
        }
969
970
        // Create a storage without the factory manager
971 27
        $packageFileStorage = new PackageFileStorage($this->getStorage(), $this->getPackageFileSerializer());
972 27
        $rootDir = Path::canonicalize($rootDir);
973 27
        $rootFilePath = $this->rootDir.'/puli.json';
974
975
        try {
976 27
            $rootPackageFile = $packageFileStorage->loadRootPackageFile($rootFilePath, $baseConfig);
977 1
        } catch (FileNotFoundException $e) {
978 1
            $rootPackageFile = new RootPackageFile(null, $rootFilePath, $baseConfig);
979
        }
980
981 27
        $config = new EnvConfig($rootPackageFile->getConfig());
982
983 27
        return new ProjectContext($homeDir, $rootDir, $config, $rootPackageFile, $configFile, $this->dispatcher, $env);
984
    }
985
986
    /**
987
     * Returns the cached configuration file storage.
988
     *
989
     * @return ConfigFileStorage The configuration file storage.
990
     */
991 2
    private function getConfigFileStorage()
992
    {
993 2
        if (!$this->configFileStorage) {
994 2
            $this->configFileStorage = new ConfigFileStorage(
995 2
                $this->getStorage(),
996 2
                $this->getConfigFileSerializer(),
997 2
                $this->getFactoryManager()
998
            );
999
        }
1000
1001 2
        return $this->configFileStorage;
1002
    }
1003
1004
    /**
1005
     * Returns the cached package file storage.
1006
     *
1007
     * @return PackageFileStorage The package file storage.
1008
     */
1009 14
    private function getPackageFileStorage()
1010
    {
1011 14
        if (!$this->packageFileStorage) {
1012 14
            $this->packageFileStorage = new PackageFileStorage(
1013 14
                $this->getStorage(),
1014 14
                $this->getPackageFileSerializer(),
1015 14
                $this->getFactoryManager()
1016
            );
1017
        }
1018
1019 14
        return $this->packageFileStorage;
1020
    }
1021
1022
    /**
1023
     * Validates the given plugin class name.
1024
     *
1025
     * @param string $pluginClass The fully qualified name of a plugin class.
1026
     */
1027 25
    private function validatePluginClass($pluginClass)
1028
    {
1029 25
        if (!class_exists($pluginClass)) {
1030 1
            throw new InvalidConfigException(sprintf(
1031 1
                'The plugin class %s does not exist.',
1032
                $pluginClass
1033
            ));
1034
        }
1035
1036 24
        if (!in_array('Puli\Manager\Api\PuliPlugin', class_implements($pluginClass))) {
1037 1
            throw new InvalidConfigException(sprintf(
1038 1
                'The plugin class %s must implement PuliPlugin.',
1039
                $pluginClass
1040
            ));
1041
        }
1042 23
    }
1043
1044 49
    private function loadConfigFile($homeDir, Config $baseConfig)
1045
    {
1046 49
        if (null === $homeDir) {
1047 2
            return null;
1048
        }
1049
1050 47
        Assert::fileExists($homeDir, 'Could not load Puli context: The home directory %s does not exist.');
1051 47
        Assert::directory($homeDir, 'Could not load Puli context: The home directory %s is a file. Expected a directory.');
1052
1053
        // Create a storage without the factory manager
1054 47
        $configStorage = new ConfigFileStorage($this->getStorage(), $this->getConfigFileSerializer());
1055 47
        $configPath = Path::canonicalize($homeDir).'/config.json';
1056
1057
        try {
1058 47
            return $configStorage->loadConfigFile($configPath, $baseConfig);
1059 1
        } catch (FileNotFoundException $e) {
1060
            // It's ok if no config.json exists. We'll work with
1061
            // DefaultConfig instead
1062 1
            return null;
1063
        }
1064
    }
1065
}
1066