ServiceLocator   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 41.24%

Importance

Changes 0
Metric Value
dl 0
loc 299
rs 9.76
c 0
b 0
f 0
ccs 40
cts 97
cp 0.4124
wmc 33
lcom 1
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
A setConfig() 0 19 3
A getConfig() 0 8 3
A config() 0 16 4
B get() 0 48 11
A _getClassName() 0 21 5
A _create() 0 4 1
A exists() 0 4 1
A set() 0 4 1
A clear() 0 6 1
A genericInstances() 0 4 1
A remove() 0 8 1
A _compareOptions() 0 8 1
1
<?php
2
/**
3
 * Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
4
 *
5
 * Licensed under The MIT License
6
 * Redistributions of files must retain the above copyright notice.
7
 *
8
 * @copyright Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
9
 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10
 */
11
12
namespace CakeDC\Api\Service\Locator;
13
14
use CakeDC\Api\Service\Service;
15
use Cake\Core\App;
16
use Cake\Core\Configure;
17
use Cake\Utility\Inflector;
18
use RuntimeException;
19
20
/**
21
 * Provides a default registry/factory for Service objects.
22
 */
23
class ServiceLocator implements LocatorInterface
24
{
25
26
    /**
27
     * Configuration for aliases.
28
     *
29
     * @var array
30
     */
31
    protected $_config = [];
32
33
    /**
34
     * Instances that belong to the registry.
35
     *
36
     * @var array
37
     */
38
    protected $_instances = [];
39
40
    /**
41
     * Contains a list of Method objects that were created out of the
42
     * built-in Method class. The list is indexed by method names
43
     *
44
     * @var array
45
     */
46
    protected $_fallbacked = [];
47
48
    /**
49
     * Contains a list of options that were passed to get() method.
50
     *
51
     * @var array
52
     */
53
    protected $_options = [];
54
55
    /**
56
     * Stores a list of options to be used when instantiating an object
57
     * with a matching alias.
58
     *
59
     * @param string|array $alias Name of the alias or array to completely overwrite current config.
60
     * @param array|null $options list of options for the alias
61
     * @return $this
62
     * @throws \RuntimeException When you attempt to configure an existing table instance.
63
     */
64
    public function setConfig($alias, $options = null)
65
    {
66
        if (!is_string($alias)) {
67
            $this->_config = $alias;
68
69
            return $this;
70
        }
71
72
        if (isset($this->_instances[$alias])) {
73
            throw new RuntimeException(sprintf(
74
                'You cannot configure "%s", service has already been constructed.',
75
                $alias
76
            ));
77
        }
78
79
        $this->_config[$alias] = $options;
80
81
        return $this;
82
    }
83
84
    /**
85
     * Returns configuration for an alias or the full configuration array for all aliases.
86
     *
87
     * @param string|null $alias Alias to get config for, null for complete config.
88
     * @return array The config data.
89
     */
90
    public function getConfig($alias = null)
91
    {
92
        if ($alias === null) {
93
            return $this->_config;
94
        }
95
96
        return isset($this->_config[$alias]) ? $this->_config[$alias] : [];
97
    }
98
99
    /**
100
     * Stores a list of options to be used when instantiating an object
101
     * with a matching alias.
102
     *
103
     * The options that can be stored are those that are recognized by `get()`
104
     * If second argument is omitted, it will return the current settings
105
     * for $alias.
106
     *
107
     * If no arguments are passed it will return the full configuration array for
108
     * all aliases
109
     *
110
     * @param string|null $alias Name of the alias
111
     * @param array|null $options list of options for the alias
112
     * @return array The config data.
113
     * @throws RuntimeException When you attempt to configure an existing method instance.
114
     * @deprecated 3.6.0 Use setConfig()/getConfig() instead.
115
     */
116
    public function config($alias = null, $options = null)
117
    {
118
        deprecationWarning(
119
            'ServiceLocator::config() is deprecated. ' .
120
            'Use getConfig()/setConfig() instead.'
121
        );
122
        if ($alias !== null) {
123
            if (is_string($alias) && $options === null) {
124
                return $this->getConfig($alias);
125
            }
126
127
            $this->setConfig($alias, $options);
128
        }
129
130
        return $this->getConfig($alias);
131
    }
132
133
    /**
134
     * Get a method instance from the registry.
135
     *
136
     * Methods are only created once until the registry is flushed.
137
     * This means that aliases must be unique across your application.
138
     * This is important because method associations are resolved at runtime
139
     * and cyclic references need to be handled correctly.
140
     *
141
     * The options that can be passed are the same as in `Method::__construct()`, but the
142
     * key `className` is also recognized.
143
     *
144
     * If $options does not contain `className` CakePHP will attempt to construct the
145
     * class name based on the alias. If this class does not exist,
146
     * then the default `CakeDC\OracleDriver\ORM\Method` class will be used. By setting the `className`
147
     * option you can define the specific class to use. This className can
148
     * use a plugin short class reference.
149
     *
150
     * If you use a `$name` that uses plugin syntax only the name part will be used as
151
     * key in the registry. This means that if two plugins, or a plugin and app provide
152
     * the same alias, the registry will only store the first instance.
153
     *
154
     * If no `method` option is passed, the method name will be the underscored version
155
     * of the provided $alias.
156
     *
157
     * If no `connection` option is passed the method's defaultConnectionName() method
158
     * will be called to get the default connection name to use.
159
     *
160
     * @param string $alias The alias name you want to get.
161
     * @param array $options The options you want to build the method with.
162
     *   If a method has already been loaded the options will be ignored.
163
     * @return \CakeDC\Api\Service\Service
164
     * @throws \RuntimeException When you try to configure an alias that already exists.
165
     */
166 91
    public function get($alias, array $options = [])
167
    {
168 91
        $alias = Inflector::camelize($alias);
169
170 91
        if (isset($this->_instances[$alias]) && empty($options['refresh'])) {
171
            if (!empty($options) && !$this->_compareOptions($alias, $options)) {
172
                throw new RuntimeException(sprintf(
173
                    'You cannot configure "%s", it already exists in the registry.',
174
                    $alias
175
                ));
176
            }
177
178
            return $this->_instances[$alias];
179
        }
180
181 91
        $this->_options[$alias] = $options;
182 91
        list(, $classAlias) = pluginSplit($alias);
183 91
        $options = ['alias' => $classAlias] + $options;
184
185 91
        if (isset($this->_config[$alias])) {
186
            $options += $this->_config[$alias];
187
        }
188
189 91
        if (empty($options['className'])) {
190 91
            $options['className'] = Inflector::camelize($alias);
191 91
        }
192
193 91
        $className = $this->_getClassName($alias, $options);
194 91
        if (!$className && strpos($options['className'], '.') === false) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false 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...
195 34
            $_options = $options;
196 34
            $_options['className'] = 'CakeDC/Api.' . $_options['className'];
197 34
            $className = $this->_getClassName($alias, $_options);
198 34
        }
199 91
        if ($className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
200 77
            $options['className'] = $className;
201 77
            $options['service'] = Inflector::underscore($alias);
202 77
        } else {
203 23
            $fallbackClass = Configure::read('Api.ServiceFallback');
204 23
            if ($fallbackClass) {
205 23
                $options['className'] = $fallbackClass;
206 23
                $options['service'] = Inflector::underscore($alias);
207 23
            }
208
        }
209
210 91
        $this->_instances[$alias] = $this->_create($options);
211
212 91
        return $this->_instances[$alias];
213
    }
214
215
    /**
216
     * Gets the method class name.
217
     *
218
     * @param string $alias The alias name you want to get.
219
     * @param array $options Method options array.
220
     * @return string
221
     */
222 91
    protected function _getClassName($alias, array $options = [])
223
    {
224 91
        $useVersions = Configure::read('Api.useVersioning');
225 91
        if ($useVersions) {
226
            $versionPrefix = Configure::read('Api.versionPrefix');
227
            if (empty($versionPrefix)) {
228
                $versionPrefix = 'v';
229
            }
230
            if (empty($options['version'])) {
231
                $options['version'] = $versionPrefix . Configure::read('Api.defaultVersion');
232
            }
233
            $version = '/' . $options['version'];
234
        } else {
235 91
            $version = '';
236
        }
237 91
        if (empty($options['className'])) {
238
            $options['className'] = Inflector::camelize($alias);
239
        }
240
241 91
        return App::className($options['className'], 'Service' . $version, 'Service');
242
    }
243
244
    /**
245
     * Wrapper for creating method instances
246
     *
247
     * @param array $options The alias to check for.
248
     * @return \CakeDC\Api\Service\Service
249
     */
250 91
    protected function _create(array $options)
251
    {
252 91
        return new $options['className']($options);
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258
    public function exists($alias)
259
    {
260
        return isset($this->_instances[$alias]);
261
    }
262
263
    /**
264
     * {@inheritDoc}
265
     */
266
    public function set($alias, Service $object)
267
    {
268
        return $this->_instances[$alias] = $object;
269
    }
270
271
    /**
272
     * {@inheritDoc}
273
     */
274 108
    public function clear()
275
    {
276 108
        $this->_instances = [];
277 108
        $this->_config = [];
278 108
        $this->_fallbacked = [];
279 108
    }
280
281
    /**
282
     * Returns the list of methods that were created by this registry that could
283
     * not be instantiated from a specific subclass. This method is useful for
284
     * debugging common mistakes when setting up associations or created new method
285
     * classes.
286
     *
287
     * @return array
288
     */
289
    public function genericInstances()
290
    {
291
        return $this->_fallbacked;
292
    }
293
294
    /**
295
     * {@inheritDoc}
296
     */
297
    public function remove($alias)
298
    {
299
        unset(
300
            $this->_instances[$alias],
301
            $this->_config[$alias],
302
            $this->_fallbacked[$alias]
303
        );
304
    }
305
306
    /**
307
     * Compare services options.
308
     *
309
     * @param string $alias Service alias.
310
     * @param array $options Options.
311
     * @return bool
312
     */
313
    protected function _compareOptions($alias, array $options)
314
    {
315
        $currentOptions = $this->_options[$alias];
316
        unset($currentOptions['controller']);
317
        unset($options['controller']);
318
319
        return $currentOptions == $options;
320
    }
321
}
322