Registry::getServices()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 4
nc 2
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;
8
use Puli\Discovery\Binding\ClassBinding;
9
10
/**
11
 * A class that holds the list of service providers of a project.
12
 * This class is designed so that service provider do not need to be instantiated each time the registry is filled.
13
 * They can be lazily instantiated if needed.
14
 */
15
class Registry implements RegistryInterface
16
{
17
    /**
18
     * The array with lazy values.
19
     *
20
     * @var array
21
     */
22
    private $lazyArray;
23
24
    /**
25
     * The array with constructed values.
26
     *
27
     * @var array
28
     */
29
    private $constructedArray = [];
30
31
    /**
32
     * An array of service factories (the result of the call to 'getServices'),
33
     * indexed by service provider.
34
     *
35
     * @var array An array<key, array<servicename, callable>>
36
     */
37
    private $serviceFactories = [];
38
39
    private $position = 0;
40
41
    /**
42
     * Initializes the registry from a list of service providers.
43
     * This list of service providers can be passed as ServiceProvider instances, or simply class name,
44
     * or an array of [class name, [constructor params]].
45
     * If a Puli $discovery object is passed, the registry is automatically populated with ServiceProviders from Puli.
46
     *
47
     * @param array          $lazyArray The array with lazy values
48
     * @param Discovery|null $discovery
49
     */
50
    public function __construct(array $lazyArray = [], Discovery $discovery = null)
51
    {
52
        if ($discovery !== null) {
53
            $this->lazyArray = array_merge($this->discover($discovery), $lazyArray);
54
        } else {
55
            $this->lazyArray = $lazyArray;
56
        }
57
    }
58
59
    /**
60
     * Discovers service provider class names using Puli.
61
     *
62
     * @param Discovery $discovery
63
     *
64
     * @return string[] Returns an array of service providers.
65
     */
66
    private function discover(Discovery $discovery) /*: array*/
67
    {
68
        $bindings = $discovery->findBindings('container-interop/service-provider');
69
        $serviceProviders = [];
70
71
        foreach ($bindings as $binding) {
72
            if ($binding instanceof ClassBinding) {
73
                $serviceProviders[] = $binding->getClassName();
74
            }
75
        }
76
77
        return $serviceProviders;
78
    }
79
80
    /**
81
     * @param string|object $className The FQCN or the instance to put in the array
82
     * @param array ...$params The parameters passed to the constructor.
83
     *
84
     * @return int The key in the array
85
     *
86
     * @throws ServiceProviderRegistryInvalidArgumentException
87
     */
88
    public function push($className, ...$params)
89
    {
90
        if ($className instanceof ServiceProvider) {
91
            $this->lazyArray[] = $className;
92
        } elseif (is_string($className)) {
93
            $this->lazyArray[] = [$className, $params];
94
        } else {
95
            throw new InvalidArgumentException('Push expects first parameter to be a fully qualified class name or an instance of Interop\\Container\\ServiceProvider');
96
        }
97
        end($this->lazyArray);
98
99
        return key($this->lazyArray);
100
    }
101
102
    /**
103
     * Whether a offset exists.
104
     *
105
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
106
     *
107
     * @param mixed $offset <p>
108
     *                      An offset to check for.
109
     *                      </p>
110
     *
111
     * @return bool true on success or false on failure.
112
     *              </p>
113
     *              <p>
114
     *              The return value will be casted to boolean if non-boolean was returned.
115
     *
116
     * @since 5.0.0
117
     */
118
    public function offsetExists($offset)
119
    {
120
        return isset($this->lazyArray[$offset]);
121
    }
122
123
    /**
124
     * Offset to retrieve.
125
     *
126
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
127
     *
128
     * @param mixed $offset <p>
129
     *                      The offset to retrieve.
130
     *                      </p>
131
     *
132
     * @return mixed Can return all value types.
133
     *
134
     * @since 5.0.0
135
     */
136
    public function offsetGet($offset)
137
    {
138
        if (isset($this->constructedArray[$offset])) {
139
            return $this->constructedArray[$offset];
140
        } else {
141
            $item = $this->lazyArray[$offset];
142
            if ($item instanceof ServiceProvider) {
143
                $this->constructedArray[$offset] = $item;
144
145
                return $item;
146
            } elseif (is_array($item)) {
147
                $className = $item[0];
148
                $params = isset($item[1]) ? $item[1] : [];
149
            } elseif (is_string($item)) {
150
                $className = $item;
151
                $params = [];
152
            }
153
            $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...
154
155
            return $this->constructedArray[$offset];
156
        }
157
    }
158
159
    /**
160
     * Offset to set.
161
     *
162
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
163
     *
164
     * @param mixed $offset <p>
165
     *                      The offset to assign the value to.
166
     *                      </p>
167
     * @param mixed $value  <p>
168
     *                      The value to set.
169
     *                      </p>
170
     *
171
     * @since 5.0.0
172
     */
173
    public function offsetSet($offset, $value)
174
    {
175
        throw new \LogicException('Not implemented');
176
    }
177
    /**
178
     * Offset to unset.
179
     *
180
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
181
     *
182
     * @param mixed $offset <p>
183
     *                      The offset to unset.
184
     *                      </p>
185
     *
186
     * @since 5.0.0
187
     */
188
    public function offsetUnset($offset)
189
    {
190
        unset($this->lazyArray[$offset]);
191
        unset($this->constructedArray[$offset]);
192
    }
193
194
    /**
195
     * Returns the result of the getServices call on service provider whose key in the registry is $offset.
196
     * The result is cached in the registry so 2 successive calls will trigger `getServices` only once.
197
     *
198
     * @param string $offset Key of the service provider in the registry
199
     *
200
     * @return array
201
     */
202
    public function getServices($offset)
203
    {
204
        if (!isset($this->serviceFactories[$offset])) {
205
            $this->serviceFactories[$offset] = $this->offsetGet($offset)->getServices();
206
        }
207
208
        return $this->serviceFactories[$offset];
209
    }
210
211
    /**
212
     * @param string             $offset      Key of the service provider in the registry
213
     * @param string             $serviceName Name of the service to fetch
214
     * @param ContainerInterface $container
215
     * @param callable|null      $previous
216
     *
217
     * @return mixed
218
     */
219
    public function createService($offset, $serviceName, ContainerInterface $container, callable $previous = null)
220
    {
221
        return call_user_func($this->getServices($offset)[$serviceName], $container, $previous);
222
    }
223
224
    /**
225
     * Return the current element.
226
     *
227
     * @link http://php.net/manual/en/iterator.current.php
228
     *
229
     * @return mixed Can return any type.
230
     *
231
     * @since 5.0.0
232
     */
233
    public function current()
234
    {
235
        return $this->offsetGet($this->position);
236
    }
237
238
    /**
239
     * Move forward to next element.
240
     *
241
     * @link http://php.net/manual/en/iterator.next.php
242
     * @since 5.0.0
243
     */
244
    public function next()
245
    {
246
        ++$this->position;
247
    }
248
249
    /**
250
     * Return the key of the current element.
251
     *
252
     * @link http://php.net/manual/en/iterator.key.php
253
     *
254
     * @return mixed scalar on success, or null on failure.
255
     *
256
     * @since 5.0.0
257
     */
258
    public function key()
259
    {
260
        return $this->position;
261
    }
262
263
    /**
264
     * Checks if current position is valid.
265
     *
266
     * @link http://php.net/manual/en/iterator.valid.php
267
     *
268
     * @return bool The return value will be casted to boolean and then evaluated.
269
     *              Returns true on success or false on failure.
270
     *
271
     * @since 5.0.0
272
     */
273
    public function valid()
274
    {
275
        return $this->offsetExists($this->position);
276
    }
277
278
    /**
279
     * Rewind the Iterator to the first element.
280
     *
281
     * @link http://php.net/manual/en/iterator.rewind.php
282
     * @since 5.0.0
283
     */
284
    public function rewind()
285
    {
286
        $this->position = 0;
287
    }
288
}
289