Passed
Pull Request — master (#17)
by Robbie
02:47
created

MemoryConfigCollection   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 67.67%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 45
c 1
b 0
f 0
lcom 1
cbo 3
dl 0
loc 302
ccs 90
cts 133
cp 0.6767
rs 8.3673

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A create() 0 4 1
A transform() 0 7 2
B set() 0 23 5
B get() 0 16 5
B getClassConfig() 0 34 6
A exists() 0 11 3
A remove() 0 12 2
A removeAll() 0 7 1
A getAll() 0 4 1
A update() 0 9 1
A merge() 0 12 3
A getMetadata() 0 8 3
A getHistory() 0 8 3
A serialize() 0 12 1
A unserialize() 0 11 1
A nest() 0 4 1
B saveMetadata() 0 21 5

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace SilverStripe\Config\Collections;
4
5
use SilverStripe\Config\MergeStrategy\Priority;
6
use SilverStripe\Config\Middleware\MiddlewareAware;
7
use SilverStripe\Config\Transformer\TransformerInterface;
8
use Serializable;
9
10
/**
11
 * Basic mutable config collection stored in memory
12
 */
13
class MemoryConfigCollection implements MutableConfigCollectionInterface, Serializable
14
{
15
    use MiddlewareAware;
16
17
    /**
18
     * Stores a list of key/value config prior to middleware being applied
19
     *
20
     * @var array
21
     */
22
    protected $config = [];
23
24
    /**
25
     * Call cache for non-trivial config calls including middleware
26
     *
27
     * @var array
28
     */
29
    protected $callCache = [];
30
31
    /**
32
     * @var array
33
     */
34
    protected $metadata = [];
35
36
    /**
37
     * @var array
38
     */
39
    protected $history = [];
40
41
    /**
42
     * @var boolean
43
     */
44
    protected $trackMetadata = false;
45
46
    /**
47
     * ConfigCollection constructor.
48
     *
49
     * @param bool $trackMetadata
50
     */
51 25
    public function __construct($trackMetadata = false)
52
    {
53 25
        $this->trackMetadata = $trackMetadata;
54 25
    }
55
56
    /**
57
     * @return static
58
     */
59
    public static function create()
60
    {
61
        return new static();
62
    }
63
64
    /**
65
     * Trigger transformers to load into this store
66
     *
67
     * @param  TransformerInterface[] $transformers
68
     * @return $this
69
     */
70 16
    public function transform($transformers)
71
    {
72 16
        foreach ($transformers as $transformer) {
73 16
            $transformer->transform($this);
74 15
        }
75 15
        return $this;
76
    }
77
78 22
    public function set($class, $name, $data, $metadata = [])
79
    {
80 22
        $class = strtolower($class);
81 22
        $this->saveMetadata($class, $metadata);
82
83 22
        if ($name) {
84 6
            if (!isset($this->config[$class])) {
85 6
                $this->config[$class] = [];
86 6
            }
87 6
            $this->config[$class][$name] = $data;
88 6
        } else {
89 22
            $this->config[$class] = $data;
90
        }
91
92
        // Flush call cache for this class, and any subclasses
93 22
        unset($this->callCache[$class]);
94 22
        foreach ($this->callCache as $nextClass => $data) {
95 1
            if (is_subclass_of($nextClass, $class, true)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
96
                unset($this->callCache[$nextClass]);
97
            }
98 22
        }
99 22
        return $this;
100
    }
101
102 23
    public function get($class, $name = null, $excludeMiddleware = 0)
103
    {
104 23
        if (!is_int($excludeMiddleware) && $excludeMiddleware !== true) {
105
            throw new \InvalidArgumentException("Invalid middleware flags");
106
        }
107
108
        // Get config for complete class
109 23
        $class = strtolower($class);
110 23
        $config = $this->getClassConfig($class, $excludeMiddleware);
111
112
        // Return either name, or whole-class config
113 23
        if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
114 6
            return isset($config[$name]) ? $config[$name] : null;
115
        }
116 23
        return $config;
117
    }
118
119
    /**
120
     * Retrieve config for an entire class
121
     *
122
     * @param string $class Name of class
123
     * @param int|true $excludeMiddleware Optional flag of middleware to disable.
124
     * Passing in `true` disables all middleware.
125
     * Can also pass in int flags to specify specific middlewares.
126
     * @return array|null
127
     */
128 23
    protected function getClassConfig($class, $excludeMiddleware = 0)
129
    {
130 23
        $class = strtolower($class);
131
132
        // Can't apply middleware to config on non-existant class
133 23
        if (!isset($this->config[$class])) {
134 13
            return null;
135
        }
136
137
        // `true` excludes all middleware, so bypass call cache
138 22
        if ($excludeMiddleware === true) {
139 12
            return $this->config[$class];
140
        }
141
142
        // Check cache
143 22
        if (isset($this->callCache[$class][$excludeMiddleware])) {
144 1
            return $this->callCache[$class][$excludeMiddleware];
145
        }
146
147
        // Build middleware
148 22
        $result = $this->callMiddleware(
149 22
            $class, $excludeMiddleware, function ($class, $excludeMiddleware) {
0 ignored issues
show
Unused Code introduced by
The parameter $excludeMiddleware is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150 22
                $class = strtolower($class);
151 22
                return isset($this->config[$class]) ? $this->config[$class] : [];
152
            }
153 22
        );
154
155
        // Save cache
156 22
        if (!isset($this->callCache[$class])) {
157 22
            $this->callCache[$class] = [];
158 22
        }
159 22
        $this->callCache[$class][$excludeMiddleware] = $result;
160 22
        return $result;
161
    }
162
163 2
    public function exists($class, $name = null, $excludeMiddleware = 0)
164
    {
165 2
        $config = $this->get($class, null, $excludeMiddleware);
166 2
        if (!isset($config)) {
167 2
            return false;
168
        }
169 1
        if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
170
            return array_key_exists($name, $config);
171
        }
172 1
        return true;
173
    }
174
175 1
    public function remove($class, $name = null)
176
    {
177 1
        $class = strtolower($class);
178 1
        if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
179
            unset($this->config[$class][$name]);
180
        } else {
181 1
            unset($this->config[$class]);
182
        }
183
        // Discard call cache
184 1
        unset($this->callCache[$class]);
185 1
        return $this;
186
    }
187
188 1
    public function removeAll()
189
    {
190 1
        $this->config = [];
191 1
        $this->metadata = [];
192 1
        $this->history = [];
193 1
        $this->callCache = [];
194 1
    }
195
196
    /**
197
     * Get complete config (excludes middleware-applied config)
198
     *
199
     * @return array
200
     */
201
    public function getAll()
202
    {
203
        return $this->config;
204
    }
205
206
    /**
207
     * @deprecated 4.0...5.0
208
     *
209
     * Synonym for merge()
210
     *
211
     * @param string $class
212
     * @param string $name
213
     * @param mixed  $value
214
     * @return $this
215
     */
216
    public function update($class, $name, $value)
217
    {
218
        user_error(
219
            'Config::inst()->update() is deprecated. Please use YAML configuration, Config::modify()->merge() or ->set()',
220
            E_USER_WARNING
221
        );
222
        $this->merge($class, $name, $value);
223
        return $this;
224
    }
225
226
    public function merge($class, $name, $value)
227
    {
228
        // Detect mergeable config
229
        $existing = $this->get($class, $name, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a integer.

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...
230
        if (is_array($value) && is_array($existing)) {
231
            $value = Priority::mergeArray($value, $existing);
232
        }
233
234
        // Apply
235
        $this->set($class, $name, $value);
236
        return $this;
237
    }
238
239 8
    public function getMetadata()
240
    {
241 8
        if (!$this->trackMetadata || !is_array($this->metadata)) {
242 7
            return [];
243
        }
244
245 1
        return $this->metadata;
246
    }
247
248 2
    public function getHistory()
249
    {
250 2
        if (!$this->trackMetadata || !is_array($this->history)) {
251 1
            return [];
252
        }
253
254 1
        return $this->history;
255
    }
256
257
    public function serialize()
258
    {
259
        return serialize([
260
            $this->config,
261
            $this->history,
262
            $this->metadata,
263
            $this->trackMetadata,
264
            $this->middlewares,
265
            $this->callCache
266
        ]);
267
268
    }
269
270
    public function unserialize($serialized)
271
    {
272
        list(
273
            $this->config,
274
            $this->history,
275
            $this->metadata,
276
            $this->trackMetadata,
277
            $this->middlewares,
278
            $this->callCache
279
        ) = unserialize($serialized);
280
    }
281
282
    public function nest()
283
    {
284
        return clone $this;
285
    }
286
287
    /**
288
     * Save metadata for the given class
289
     *
290
     * @param string $class
291
     * @param array  $metadata
292
     */
293 22
    protected function saveMetadata($class, $metadata)
294
    {
295 22
        if (!$this->trackMetadata) {
296 21
            return;
297
        }
298
299 1
        if (isset($this->metadata[$class]) && isset($this->config[$class])) {
300 1
            if (!isset($this->history[$class])) {
301 1
                $this->history[$class] = [];
302 1
            }
303
304 1
            array_unshift(
305 1
                $this->history[$class], [
306 1
                'value' => $this->config[$class],
307 1
                'metadata' => $this->metadata[$class]
308 1
                ]
309 1
            );
310 1
        }
311
312 1
        $this->metadata[$class] = $metadata;
313 1
    }
314
}
315