Failure conditions met
Push97d950...17c62d
failed — Build
created

PluginsIgniter::gatherLoadedPackagesHandles()   A

↳ Parent: PluginsIgniter

Complexity

Conditions 4
Paths 2

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 18
Code Lines 11

Code Coverage

Tests 4
CRAP Score 9.3088

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 18
ccs 4
cts 13
cp 0.3076
rs 9.2
c 1
b 0
f 0
cc 4
eloc 11
nc 2
nop 1
crap 9.3088
1
<?php
2
3
/*
4
 * This file is part of Rocketeer
5
 *
6
 * (c) Maxime Fabre <[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
13
namespace Rocketeer\Services\Ignition;
14
15
use ReflectionClass;
16
use Rocketeer\Traits\ContainerAwareTrait;
17
18
/**
19
 * Publishes the plugin's configurations in user-land.
20
 */
21
class PluginsIgniter
22
{
23
    use ContainerAwareTrait;
24
25
    /**
26
     * @var bool
27
     */
28
    protected $force = false;
29
30
    /**
31
     * @param bool $force
32
     */
33 2
    public function setForce($force)
34
    {
35 2
        $this->force = $force;
36 2
    }
37
38
    ////////////////////////////////////////////////////////////////////////////////
39
    ////////////////////////////////// PUBLISHING //////////////////////////////////
40
    ////////////////////////////////////////////////////////////////////////////////
41
42
    /**
43
     * Publishes a package's configuration.
44
     *
45
     * @param string|string[] $plugins
0 ignored issues
show
Documentation introduced by Maxime Fabre
Should the type for parameter $plugins not be string|string[]|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
46
     *
47
     * @return bool|string|null
48
     */
49 3
    public function publish($plugins = null)
50
    {
51 3
        $plugins = $this->gatherLoadedPackagesHandles($plugins);
0 ignored issues
show
Bug introduced by Maxime Fabre
It seems like $plugins can also be of type null; however, Rocketeer\Services\Ignit...LoadedPackagesHandles() does only seem to accept string|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
52 3
        foreach ($plugins as $plugin) {
53
            // Find the plugin's configuration
54 3
            $paths = $this->findPackageConfiguration($plugin);
55
56
            // Cancel if no valid paths
57 3
            $paths = array_filter($paths, [$this->files, 'isDirectory']);
58 3
            $paths = array_values($paths);
59 3
            if (empty($paths)) {
60 2
                $this->explainer->comment('No configuration found for '.$plugin);
61 2
                continue;
62
            }
63
64 1
            $this->publishConfiguration($paths[0]);
65 3
        }
66 3
    }
67
68
    /**
69
     * Publishes a configuration within a classic application.
70
     *
71
     * @param string $path
72
     *
73
     * @return bool
0 ignored issues
show
Documentation introduced by Maxime Fabre
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
74
     */
75 1
    protected function publishConfiguration($path)
76
    {
77
        // Compute and create the destination folder
78 1
        $destination = $this->paths->getConfigurationPath().'/plugins';
79
80
        // Export configuration
81 1
        $files = $this->files->listFiles($path, true);
82 1
        foreach ($files as $file) {
83 1
            $fileDestination = $destination.DS.$file['basename'];
84 1
            if ($this->files->has($destination) && !$this->force) {
85
                continue;
86
            }
87
88 1
            $this->files->forceCopy($file['path'], $fileDestination);
89 1
            $this->explainer->success('Published <comment>'.str_replace($this->paths->getBasePath(), null, $fileDestination).'</comment>');
90 1
        }
91 1
    }
92
93
    ////////////////////////////////////////////////////////////////////////////////
94
    /////////////////////////////////// HELPERS ////////////////////////////////////
95
    ////////////////////////////////////////////////////////////////////////////////
96
97
    /**
98
     * Find all the possible locations for a package's configuration.
99
     *
100
     * @param string $package
101
     *
102
     * @return string[]
103
     */
104 4
    public function findPackageConfiguration($package)
105
    {
106
        $paths = [
107 4
            $this->paths->getBasePath().'vendor/%s/src/config',
108 4
            $this->paths->getBasePath().'vendor/%s/config',
109 4
            $this->paths->getRocketeerPath().'/vendor/%s/src/config',
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getRocketeerPath does not exist on object<Rocketeer\Service...Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
110 4
            $this->paths->getRocketeerPath().'/vendor/%s/config',
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getRocketeerPath does not exist on object<Rocketeer\Service...Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
111 4
            $this->paths->getUserHomeFolder().'/.composer/vendor/%s/src/config',
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getUserHomeFolder does not exist on object<Rocketeer\Service...Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
112 4
            $this->paths->getUserHomeFolder().'/.composer/vendor/%s/config',
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getUserHomeFolder does not exist on object<Rocketeer\Service...Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
113 4
        ];
114
115
        // Check for the first configuration path that exists
116 4
        $paths = array_map(function ($path) use ($package) {
117 4
            return sprintf($path, $package);
118 4
        }, $paths);
119
120 4
        return $paths;
121
    }
122
123
    /**
124
     * Infer the name of the loaded packages
125
     * from their service provider.
126
     *
127
     * @param string|string[] $packages
128
     *
129
     * @return string[]
130
     */
131 3
    protected function gatherLoadedPackagesHandles($packages)
132
    {
133 3
        $packages = (array) $packages;
134 3
        if (!$packages) {
135
            $plugins = $this->container->getPlugins();
0 ignored issues
show
Bug introduced by Maxime Fabre
It seems like you code against a concrete implementation and not the interface League\Container\ContainerInterface as the method getPlugins() does only exist in the following implementations of said interface: Rocketeer\Services\Container\Container, Rocketeer\Services\Container\Container.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
136
            foreach ($plugins as $plugin) {
137
                $path = (new ReflectionClass($plugin))->getFileName();
138
                preg_match('/vendor\/([^\/]+)\/([^\/]+)/', $path, $handle);
139
                if (count($handle) !== 3) {
140
                    continue;
141
                }
142
143
                $packages[] = $handle[1].'/'.$handle[2];
144
            }
145
        }
146
147 3
        return $packages;
148
    }
149
}
150