Completed
Push — 2.0 ( a0895a )
by David
9s
created

Registry::discoverPuli()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
1
<?php
2
3
namespace TheCodingMachine\ServiceProvider;
4
5
use Interop\Container\ContainerInterface;
6
use Interop\Container\ServiceProvider;
7
use Puli\Discovery\Api\Discovery as PuliDiscovery;
8
use TheCodingMachine\Discovery\DiscoveryInterface as TcmDiscovery;
9
use Puli\Discovery\Binding\ClassBinding;
10
11
/**
12
 * A class that holds the list of service providers of a project.
13
 * This class is designed so that service provider do not need to be instantiated each time the registry is filled.
14
 * They can be lazily instantiated if needed.
15
 */
16
class Registry implements RegistryInterface
17
{
18
    /**
19
     * The array with lazy values.
20
     *
21
     * @var array
22
     */
23
    private $lazyArray;
24
25
    /**
26
     * The array with constructed values.
27
     *
28
     * @var array
29
     */
30
    private $constructedArray = [];
31
32
    /**
33
     * An array of service factories (the result of the call to 'getServices'),
34
     * indexed by service provider.
35
     *
36
     * @var array An array<key, array<servicename, callable>>
37
     */
38
    private $serviceFactories = [];
39
40
    private $position = 0;
41
42
    /**
43
     * Initializes the registry from a list of service providers.
44
     * This list of service providers can be passed as ServiceProvider instances, or simply class name,
45
     * or an array of [class name, [constructor params]].
46
     * If a Puli $discovery object is passed, the registry is automatically populated with ServiceProviders from Puli.
47
     *
48
     * @param array          $lazyArray The array with lazy values
49
     * @param PuliDiscovery|null $puliDiscovery
50
     */
51
    public function __construct(array $lazyArray = [], PuliDiscovery $puliDiscovery = null, TcmDiscovery $tcmDiscovery = null)
52
    {
53
        if ($puliDiscovery !== null) {
54
            $this->lazyArray = $this->discoverPuli($puliDiscovery);
55
        } else {
56
            $this->lazyArray = [];
57
        }
58
59
        if ($tcmDiscovery !== null) {
60
            $this->lazyArray = array_merge($this->lazyArray, $this->discoverTcm($tcmDiscovery));
61
        }
62
63
        $this->lazyArray = array_merge($this->lazyArray, $lazyArray);
64
    }
65
66
    /**
67
     * Discovers service provider class names using Puli.
68
     *
69
     * @param PuliDiscovery $discovery
70
     *
71
     * @return string[] Returns an array of service providers.
72
     */
73
    private function discoverPuli(PuliDiscovery $discovery) /*: array*/
74
    {
75
        $bindings = $discovery->findBindings('container-interop/service-provider');
76
        $serviceProviders = [];
77
78
        foreach ($bindings as $binding) {
79
            if ($binding instanceof ClassBinding) {
80
                $serviceProviders[] = $binding->getClassName();
81
            }
82
        }
83
84
        return $serviceProviders;
85
    }
86
87
    /**
88
     * Discovers service provider class names using thecodingmachine/discovery.
89
     *
90
     * @param TcmDiscovery $discovery
91
     *
92
     * @return string[] Returns an array of service providers.
93
     */
94
    private function discoverTcm(TcmDiscovery $discovery) /*: array*/
95
    {
96
        return $discovery->get(ServiceProvider::class);
97
    }
98
99
    /**
100
     * @param string|object $className The FQCN or the instance to put in the array
101
     * @param array ...$params The parameters passed to the constructor.
102
     *
103
     * @return int The key in the array
104
     *
105
     * @throws ServiceProviderRegistryInvalidArgumentException
106
     */
107
    public function push($className, ...$params)
108
    {
109
        if ($className instanceof ServiceProvider) {
110
            $this->lazyArray[] = $className;
111
        } elseif (is_string($className)) {
112
            $this->lazyArray[] = [$className, $params];
113
        } else {
114
            throw new InvalidArgumentException('Push expects first parameter to be a fully qualified class name or an instance of Interop\\Container\\ServiceProvider');
115
        }
116
        end($this->lazyArray);
117
118
        return key($this->lazyArray);
119
    }
120
121
    /**
122
     * Whether a offset exists.
123
     *
124
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
125
     *
126
     * @param mixed $offset <p>
127
     *                      An offset to check for.
128
     *                      </p>
129
     *
130
     * @return bool true on success or false on failure.
131
     *              </p>
132
     *              <p>
133
     *              The return value will be casted to boolean if non-boolean was returned.
134
     *
135
     * @since 5.0.0
136
     */
137
    public function offsetExists($offset)
138
    {
139
        return isset($this->lazyArray[$offset]);
140
    }
141
142
    /**
143
     * Offset to retrieve.
144
     *
145
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
146
     *
147
     * @param mixed $offset <p>
148
     *                      The offset to retrieve.
149
     *                      </p>
150
     *
151
     * @return mixed Can return all value types.
152
     *
153
     * @since 5.0.0
154
     */
155
    public function offsetGet($offset)
156
    {
157
        if (isset($this->constructedArray[$offset])) {
158
            return $this->constructedArray[$offset];
159
        } else {
160
            $item = $this->lazyArray[$offset];
161
            if ($item instanceof ServiceProvider) {
162
                $this->constructedArray[$offset] = $item;
163
164
                return $item;
165
            } elseif (is_array($item)) {
166
                $className = $item[0];
167
                $params = isset($item[1]) ? $item[1] : [];
168
            } elseif (is_string($item)) {
169
                $className = $item;
170
                $params = [];
171
            }
172
            $this->constructedArray[$offset] = new $className(...$params);
0 ignored issues
show
Bug introduced by
The variable $className does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
173
174
            return $this->constructedArray[$offset];
175
        }
176
    }
177
178
    /**
179
     * Offset to set.
180
     *
181
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
182
     *
183
     * @param mixed $offset <p>
184
     *                      The offset to assign the value to.
185
     *                      </p>
186
     * @param mixed $value  <p>
187
     *                      The value to set.
188
     *                      </p>
189
     *
190
     * @since 5.0.0
191
     */
192
    public function offsetSet($offset, $value)
193
    {
194
        throw new \LogicException('Not implemented');
195
    }
196
    /**
197
     * Offset to unset.
198
     *
199
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
200
     *
201
     * @param mixed $offset <p>
202
     *                      The offset to unset.
203
     *                      </p>
204
     *
205
     * @since 5.0.0
206
     */
207
    public function offsetUnset($offset)
208
    {
209
        unset($this->lazyArray[$offset]);
210
        unset($this->constructedArray[$offset]);
211
    }
212
213
    /**
214
     * Returns the result of the getServices call on service provider whose key in the registry is $offset.
215
     * The result is cached in the registry so 2 successive calls will trigger `getServices` only once.
216
     *
217
     * @param string $offset Key of the service provider in the registry
218
     *
219
     * @return array
220
     */
221
    public function getServices($offset)
222
    {
223
        if (!isset($this->serviceFactories[$offset])) {
224
            $this->serviceFactories[$offset] = $this->offsetGet($offset)->getServices();
225
        }
226
227
        return $this->serviceFactories[$offset];
228
    }
229
230
    /**
231
     * @param string             $offset      Key of the service provider in the registry
232
     * @param string             $serviceName Name of the service to fetch
233
     * @param ContainerInterface $container
234
     * @param callable|null      $previous
235
     *
236
     * @return mixed
237
     */
238
    public function createService($offset, $serviceName, ContainerInterface $container, callable $previous = null)
239
    {
240
        return call_user_func($this->getServices($offset)[$serviceName], $container, $previous);
241
    }
242
243
    /**
244
     * Return the current element.
245
     *
246
     * @link http://php.net/manual/en/iterator.current.php
247
     *
248
     * @return mixed Can return any type.
249
     *
250
     * @since 5.0.0
251
     */
252
    public function current()
253
    {
254
        return $this->offsetGet($this->position);
255
    }
256
257
    /**
258
     * Move forward to next element.
259
     *
260
     * @link http://php.net/manual/en/iterator.next.php
261
     * @since 5.0.0
262
     */
263
    public function next()
264
    {
265
        ++$this->position;
266
    }
267
268
    /**
269
     * Return the key of the current element.
270
     *
271
     * @link http://php.net/manual/en/iterator.key.php
272
     *
273
     * @return mixed scalar on success, or null on failure.
274
     *
275
     * @since 5.0.0
276
     */
277
    public function key()
278
    {
279
        return $this->position;
280
    }
281
282
    /**
283
     * Checks if current position is valid.
284
     *
285
     * @link http://php.net/manual/en/iterator.valid.php
286
     *
287
     * @return bool The return value will be casted to boolean and then evaluated.
288
     *              Returns true on success or false on failure.
289
     *
290
     * @since 5.0.0
291
     */
292
    public function valid()
293
    {
294
        return $this->offsetExists($this->position);
295
    }
296
297
    /**
298
     * Rewind the Iterator to the first element.
299
     *
300
     * @link http://php.net/manual/en/iterator.rewind.php
301
     * @since 5.0.0
302
     */
303
    public function rewind()
304
    {
305
        $this->position = 0;
306
    }
307
}
308