Completed
Pull Request — dev (#24)
by Ben
02:00
created

Config::push()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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