Completed
Push — master ( 044c50...7232aa )
by Ben
02:18
created

Config   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 21
Bugs 2 Features 9
Metric Value
wmc 38
c 21
b 2
f 9
lcom 1
cbo 3
dl 0
loc 322
rs 8.3999

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 4
A setArrHelper() 0 7 2
A flatten() 0 4 1
A set() 0 17 3
A get() 0 7 1
A all() 0 4 1
A forget() 0 8 2
A clear() 0 9 3
A has() 0 4 1
A prepend() 0 6 1
A push() 0 6 1
A getAsArray() 0 8 3
A dataDecode() 0 14 2
A unpackArray() 0 15 3
A dataEncode() 0 15 3
A encodeArray() 0 11 3
A isValidValue() 0 10 4
1
<?php
2
3
namespace Benrowe\Laravel\Config;
4
5
use Benrowe\Laravel\Config\Modifiers\Collection;
6
use Benrowe\Laravel\Config\Modifiers\Modifier;
7
use Benrowe\Laravel\Config\Storage\StorageInterface;
8
use Illuminate\Contracts\Config\Repository;
9
use Illuminate\Support\Arr;
10
use InvalidArgumentException;
11
12
/**
13
 * Config class
14
 * Transforms a flattened key/value array configuration into a multi-dimensional
15
 * config handler
16
 *
17
 * @package Benrowe\Laravel\Config
18
 */
19
class Config implements Repository
20
{
21
    /**
22
     * @var string The delimiter used in the array keys to specify the heirachy
23
     */
24
    const KEY_DELIMITER = '.';
25
26
    /**
27
     * @var string the pattern to match array keys
28
     */
29
    const ARRAY_PATTERN = "/\[([0-9]+)\]$/";
30
31
    /**
32
     * The configuration data
33
     * @var array
34
     */
35
    private $data;
36
37
    public $modifiers;
38
39
    /**
40
     * @var \Illuminate\Support\Arr
41
     */
42
    private $arrHelper;
43
44
    private $storage;
45
46
    /**
47
     * constructor
48
     * The initial data
49
     *
50
     * @param array|StorageInterface $data the flattened data
51
     * @param Arr|null $arrHelper the array helper
52
     */
53
    public function __construct($data = [], Arr $arrHelper = null)
54
    {
55
        $this->setArrHelper($arrHelper);
56
57
        // test $data is valid
58
        if (!is_array($data) && !($data instanceof StorageInterface)) {
59
            $msg = '$data must be either an array or an implementation of ';
60
            $msg .= StorageInterface::class;
61
            throw new \InvalidArgumentException($msg);
62
        }
63
64
        if ($data instanceof StorageInterface) {
65
            $this->storage = $data;
66
            $data = $this->storage->load();
67
        }
68
69
        $this->data = $this->dataDecode($data);
70
        $this->modifiers = new Collection;
71
    }
72
73
    /**
74
     * Set the array helper
75
     *
76
     * @param Arr|null $arrHelper
77
     */
78
    private function setArrHelper(Arr $arrHelper = null)
79
    {
80
        if ($arrHelper === null) {
81
            $arrHelper = new Arr;
82
        }
83
        $this->arrHelper = $arrHelper;
84
    }
85
86
    /**
87
     * Reduce the configuration to a simple key/value array, despite the
88
     * heirachy of information
89
     *
90
     * @return array
91
     */
92
    public function flatten()
93
    {
94
        return $this->dataEncode($this->data);
95
    }
96
97
    /**
98
     * Create/Update a configuration value
99
     *
100
     * @param string $key
101
     * @param mixed $value
102
     */
103
    public function set($key, $value = null)
104
    {
105
        $value = $this->modifiers->convert(
106
            $key,
107
            $value,
108
            Modifier::DIRECTION_FROM
109
        );
110
        // verify the array
111
        if (!$this->isValidValue($value)) {
112
            $msg = 'Value for "'.$key.'" is invalid. ';
113
            $msg .= 'Must be scalar or an array of scalar values';
114
            throw new InvalidArgumentException($msg);
115
        }
116
117
        $this->arrHelper->set($this->data, $key, $value);
118
        $this->storage && $this->storage->save($key, $value);
119
    }
120
121
    /**
122
     * Get the configuration value based on it's key
123
     *
124
     * @param  string $key
125
     * @param  mixed $default
126
     * @return mixed
127
     */
128
    public function get($key, $default = null)
129
    {
130
        return $this->modifiers->convert(
131
            $key,
132
            $this->arrHelper->get($this->data, $key, $default)
133
        );
134
    }
135
136
    /***
137
      * Get all of the configuration data in it's hierarchical state
138
      */
139
    public function all()
140
    {
141
        return $this->data;
142
    }
143
144
    /**
145
     * Remove an item from the configuration
146
     *
147
     * @param  string $key
148
     * @return boolean
149
     */
150
    public function forget($key)
151
    {
152
        if ($this->has($key)) {
153
            $this->arrHelper->forget($this->data, $key);
154
            return true;
155
        }
156
        return false;
157
    }
158
159
    /**
160
     * Clear all of the settings from the configuration
161
     *
162
     * @return boolean
163
     */
164
    public function clear()
165
    {
166
        if (!empty($this->data)) {
167
            $this->data = [];
168
            $this->storage && $this->storage->clear();
169
            return true;
170
        }
171
        return false;
172
    }
173
174
    /**
175
     * Check if a configuration setting exists
176
     *
177
     * @param  string $key
178
     * @return boolean
179
     */
180
    public function has($key)
181
    {
182
        return $this->arrHelper->has($this->data, $key);
183
    }
184
185
    /**
186
     * Prepend a value onto the key.
187
     *
188
     * If that existing key is not  an array it will be converted into an array
189
     * and the the value will be the first element of the array
190
     *
191
     * @param  string $key
192
     * @param  mixed $value
193
     * @return void
194
     */
195
    public function prepend($key, $value)
196
    {
197
        $existing = $this->getAsArray($key);
198
        array_unshift($existing, $value);
199
        $this->set($key, $existing);
200
    }
201
202
    /**
203
     * Push a value onto the key
204
     *
205
     * If that existing key is not  an array it will be converted into an array
206
     * and the the value will be the first element of the array
207
     *
208
     * @param  string $key
209
     * @param  mixed $value
210
     * @return void
211
     */
212
    public function push($key, $value)
213
    {
214
        $existing = $this->getAsArray($key);
215
        array_push($existing, $value);
216
        $this->set($key, $existing);
217
    }
218
219
    /**
220
     * Get the value, as an array
221
     *
222
     * @param  string $key
223
     * @return array any existing value will be converted to the first element
224
     *               of the array
225
     */
226
    private function getAsArray($key)
227
    {
228
        $value = $this->get($key);
229
        if (!is_array($value)) {
230
            $value = !is_null($value) ? [$value] : [];
231
        }
232
        return $value;
233
    }
234
235
    /**
236
     * Converts the flat key/value from the storage engine
237
     * to a heirachy structure based on the key sytax
238
     *
239
     * @param  array $data
240
     * @return array
241
     */
242
    private function dataDecode($data)
243
    {
244
        // preprocess the keys into a unique list where the array values are
245
        // stored against the same key
246
247
        $data = $this->unpackArray($data);
248
249
        $newData = [];
250
        foreach ($data as $key => $value) {
251
            $this->arrHelper->set($newData, $key, $value);
252
        }
253
254
        return $newData;
255
    }
256
257
    /**
258
     * unpack the keys that are structured for arrays so that they no
259
     * longer have the [] syntax at the end. Rather they're now a proper
260
     * array.
261
     *
262
     * @param  array $data [description]
263
     * @return array
264
     */
265
    private function unpackArray($data)
266
    {
267
        $arrKeys = array_filter($data, function ($val) {
268
            return preg_match(self::ARRAY_PATTERN, $val);
269
        });
270
        foreach ($arrKeys as $key => $value) {
271
            $newKey = preg_replace(self::ARRAY_PATTERN, '', $key);
272
            if (!isset($data[$newKey])) {
273
                $data[$newKey] = [];
274
            }
275
            $data[$newKey][] = $value;
276
            unset($data[$key]);
277
        }
278
        return $data;
279
    }
280
281
    /**
282
     * Flatten a multi-dimensional array into a linear key/value list
283
     *
284
     * @param  array $data
285
     * @param string|null $prefix
286
     * @return array
287
     */
288
    private function dataEncode($data, $prefix = null)
289
    {
290
        $newData = [];
291
        foreach ($data as $key => $value) {
292
            if (is_array($value)) {
293
                $newData = array_merge(
294
                    $newData,
295
                    $this->encodeArray($key, $value, $prefix)
296
                );
297
                continue;
298
            }
299
            $newData[$prefix.$key] = $value;
300
        }
301
        return $newData;
302
    }
303
304
    /**
305
     * Encode the array of values against the provided key
306
     *
307
     * @param  string $key
308
     * @param  array  $value  either an associative or keyed array
309
     * @param  string|null $prefix
310
     * @return array
311
     */
312
    private function encodeArray($key, array $value, $prefix = null)
313
    {
314
        $data = [];
315
        if (!$this->arrHelper->isAssoc($value)) {
316
            foreach ($value as $index => $val) {
317
                $data[$prefix.$key.'['.$index.']'] = $val;
318
            }
319
            return $data;
320
        }
321
        return $this->dataEncode($value, $prefix.$key.self::KEY_DELIMITER);
322
    }
323
324
    /**
325
     * Validate the value as safe for this object
326
     *
327
     * @param  mixed  $value the value to test
328
     * @return boolean
329
     */
330
    private function isValidValue($value)
331
    {
332
        return
333
            is_scalar($value) ||
334
            (
335
                is_array($value) &&
336
                !$this->arrHelper->isAssoc($value) &&
337
                count($value) === count(array_filter($value, 'is_scalar'))
338
            );
339
    }
340
}
341