Passed
Push — master ( 31cc9c...3a06f0 )
by Oleg
03:53
created

Container   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 45
c 2
b 1
f 0
lcom 1
cbo 0
dl 0
loc 250
rs 8.3673

8 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 9 2
A __isset() 0 14 4
A __get() 0 12 4
A __set() 0 4 1
C configure() 0 26 7
C loadComponent() 0 51 15
B buildCalls() 0 28 6
B buildParams() 0 18 6

How to fix   Complexity   

Complex Class

Complex classes like Container 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 Container, and based on these observations, apply Extract Interface, too.

1
<?php /** MicroContainer */
2
3
namespace Micro\Base;
4
5
/**
6
 * Container class file.
7
 *
8
 * @author Oleg Lunegov <[email protected]>
9
 * @link https://github.com/lugnsk/micro
10
 * @copyright Copyright &copy; 2013 Oleg Lunegov
11
 * @license /LICENSE
12
 * @package Micro
13
 * @subpackage Base
14
 * @version 1.0
15
 * @since 1.0
16
 */
17
class Container extends \stdClass implements IContainer
18
{
19
    /** @var array $data data */
20
    protected $data = [];
21
    /** @var array $config Configs */
22
    protected $config = [];
23
    /** @var array $components Components config */
24
    protected $components = [];
25
26
27
    /**
28
     * Load more configs from file
29
     *
30
     * @access public
31
     *
32
     * @param string $filename
33
     *
34
     * @return void
35
     */
36
    public function load($filename)
37
    {
38
        if (file_exists($filename)) {
39
            /** @noinspection PhpIncludeInspection */
40
            $this->config = array_merge_recursive($this->config, require $filename);
41
            $this->components = array_merge_recursive($this->components, $this->config['components']);
42
            unset($this->config['components']);
43
        }
44
    }
45
46
    /**
47
     * Is set component or option name into Container
48
     *
49
     * @access public
50
     *
51
     * @param string $name Name attribute
52
     *
53
     * @return bool
54
     */
55
    public function __isset($name)
56
    {
57
        if (array_key_exists($name, $this->config)) {
58
            return true;
59
        }
60
        if (array_key_exists($name, $this->data)) {
61
            return true;
62
        }
63
        if (array_key_exists($name, $this->components)) {
64
            return true;
65
        }
66
67
        return false;
68
    }
69
70
    /**
71
     * Get Container value
72
     *
73
     * @access public
74
     *
75
     * @param string $name element name
76
     *
77
     * @return mixed
78
     */
79
    public function __get($name = '')
80
    {
81
        if (!empty($this->config[$name])) {
82
            return $this->config[$name];
83
        }
84
85
        if (empty($this->data[$name]) && !$this->configure($name)) {
86
            return false;
87
        }
88
89
        return $this->data[$name];
90
    }
91
92
    /**
93
     * Set attribute
94
     *
95
     * @access public
96
     *
97
     * @param string $name Name attribute
98
     * @param mixed $component Component or option
99
     *
100
     * @return void
101
     */
102
    public function __set($name, $component)
103
    {
104
        $this->data[$name] = $component;
105
    }
106
107
    /**
108
     * Get component's
109
     *
110
     * @access public
111
     *
112
     * @param string|null $name name element to initialize
113
     *
114
     * @return bool
115
     */
116
    public function configure($name = null)
117
    {
118
        if (0 === count($this->components)) {
119
            return false;
120
        }
121
122
        if ($name === null) {
123
            foreach ($this->components AS $key => $options) {
124
                if (!$this->loadComponent($key, $options)) {
125
                    return false;
126
                }
127
            }
128
129
            return true;
130
        }
131
132
        if (empty($this->components[$name])) {
133
            return false;
134
        }
135
136
        if (!$this->loadComponent($name, $this->components[$name])) {
137
            return false;
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Load component
145
     *
146
     * @access public
147
     *
148
     * @param string $name component name
149
     * @param array $options component configs
150
     *
151
     * @return bool
152
     */
153
    public function loadComponent($name, $options)
154
    {
155
        if (empty($options['class']) || !class_exists($options['class'])) {
156
            return false;
157
        }
158
159
        $className = $options['class'];
160
        $this->data[$name] = null;
161
162
        $options['arguments'] = !empty($options['arguments']) ? $this->buildParams($options['arguments']) : null;
163
        $options['property'] = !empty($options['property']) ? $this->buildParams($options['property']) : null;
164
        $options['calls'] = !empty($options['calls']) ? $this->buildCalls($options['calls']) : null;
165
166
        try { // create object
167
            $reflection = new \ReflectionClass($className);
168
            $reflectionMethod = new \ReflectionMethod($className, '__construct');
169
170
            if ($reflectionMethod->getNumberOfParameters() === 0) {
171
                $this->data[$name] = new $className;
172
            } else {
173
                $this->data[$name] = $reflection->newInstanceArgs($options['arguments']);
174
            }
175
176
            unset($reflection, $reflectionMethod);
177
        } catch (Exception $e) {
178
            return false;
179
        }
180
181
        if (!empty($options['property'])) { // load properties
182
            foreach ($options['property'] as $key => $val) {
183
                if (property_exists($this->data[$name], $key)) {
184
                    $this->data[$name]->$key = $val;
185
                }
186
            }
187
        }
188
189
        if (!empty($options['calls'])) { // run methods
190
            foreach ($options['calls'] as $key => $val) {
191
                if (method_exists($this->data['name'], $key)) {
192
                    $reflectionMethod = new \ReflectionMethod($className, $key);
193
                    if ($reflectionMethod->getNumberOfParameters() === 0) {
194
                        $this->data['name']->$key();
195
                    } else {
196
                        call_user_func_array([$this->data['name'], $key], $val);
197
                    }
198
                }
199
            }
200
        }
201
202
        return true;
203
    }
204
205
    /**
206
     * Build calls arguments
207
     *
208
     * @access private
209
     * @param array $params
210
     * @return array
211
     */
212
    private function buildCalls(array $params)
213
    {
214
        $calls = [];
215
216
        if (!is_array($params[0])) {
217
            $call[] = $params[0];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$call was never initialized. Although not strictly required by PHP, it is generally a good practice to add $call = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
218
            unset($params[0]);
219
220
            if (!empty($params[1])) {
221
                $call[] = $params[1];
222
                unset($params[1]);
223
            }
224
225
            $params[] = $call;
226
        }
227
228
        foreach ($params as $arguments) {
229
            if (is_string($arguments[0])) {
230
                if (!empty($arguments[1])) {
231
                    $calls[$arguments[0]] = $this->buildParams($arguments[1]);
0 ignored issues
show
Documentation introduced by
$arguments[1] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
232
                } else {
233
                    $calls[$arguments[0]] = null;
234
                }
235
            }
236
        }
237
238
        return $calls;
239
    }
240
241
    /**
242
     * Build params from array
243
     *
244
     * @access private
245
     * @param array $params
246
     * @return array
247
     */
248
    private function buildParams(array $params)
249
    {
250
        /** @noinspection AlterInForeachInspection */
251
        foreach ($params AS $key => &$val) { // IoC Constructor
252
            if (is_string($params[$key]) && (0 === strpos($val, '@'))) {
253
                if ($val === '@this') {
254
                    $val = $this;
255
                } else {
256
                    if (null === $this->{substr($val, 1)}) {
257
                        return false;
258
                    }
259
                    $val = $this->{substr($val, 1)};
260
                }
261
            }
262
        }
263
264
        return $params;
265
    }
266
}
267