Completed
Push — master ( 58d828...11135c )
by Michael
05:24
created

ManagesItemsTrait::getRaw()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
1
<?php
2
namespace Michaels\Manager\Traits;
3
4
use Michaels\Manager\Exceptions\ItemNotFoundException;
5
use Michaels\Manager\Exceptions\ModifyingProtectedValueException;
6
use Michaels\Manager\Exceptions\NestingUnderNonArrayException;
7
use Michaels\Manager\Helpers;
8
use Michaels\Manager\Messages\NoItemFoundMessage;
9
10
/**
11
 * Manages complex, nested data
12
 *
13
 * @implements Michaels\Manager\Contracts\ManagesItemsInterface
14
 * @package Michaels\Manager
15
 */
16
trait ManagesItemsTrait
17
{
18
    /**
19
     * The items stored in the manager
20
     * @var array $items Items governed by manager
21
     */
22
    protected $_items = [];
23
24
    /**
25
     * Name of the property to hold the data items. Internal use only
26
     * @var string
27
     */
28
    protected $nameOfItemsRepository = '_items';
29
30
    /** @var array Array of protected aliases */
31
    protected $protectedItems = [];
32
33
    /* The user may also set $dataItemsName */
34
35
    /**
36
     * Initializes a new manager instance.
37
     *
38
     * This is useful for implementations that have their own __construct method
39
     * This is an alias for reset()
40
     *
41
     * @param array $items
42
     * @return $this
43
     */
44
    public function initManager($items = null)
45
    {
46
        if (property_exists($this, 'dataItemsName')) {
47
            $this->setItemsName($this->dataItemsName);
48
        }
49
50
        $repo = $this->getItemsName();
51
52
        if (!isset($this->$repo)) {
53
            $this->$repo = [];
54
        }
55
56
        if (is_null($items)) {
57
            return $this;
58
        }
59
60
        $this->$repo = is_array($items) ? $items : Helpers::getArrayableItems($items);
61
62
        return $this;
63
    }
64
65
    /**
66
     * Hydrate with external data, optionally append
67
     *
68
     * @param $data array     The data to be hydrated into the manager
69
     * @param bool $append When true, data will be appended to the current set
70
     * @return $this
71
     */
72
    public function hydrate(array $data, $append = false)
73
    {
74
        if ($append) {
75
            $this->addItem($data);
0 ignored issues
show
Documentation introduced by
$data is of type array, but the function expects a string.

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...
76
        } else {
77
            $this->reset($data);
78
        }
79
80
        return $this;
81
    }
82
83
    /**
84
     * Adds a single item.
85
     *
86
     * Allow for dot notation (one.two.three) and item nesting.
87
     *
88
     * @param string $alias Key to be stored
89
     * @param mixed $item Value to be stored
90
     * @param array $options THIS IS NOT USED HERE
91
     * @return $this
0 ignored issues
show
Documentation introduced by
Should the return type not be ManagesItemsTrait|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
92
     */
93
    public function add($alias, $item = null, array $options = null)
94
    {
95
        $this->addItem($alias, $item, $options);
96
    }
97
98
    /**
99
     * Adds a single item.
100
     *
101
     * Allow for dot notation (one.two.three) and item nesting.
102
     *
103
     * @param string $alias Key to be stored
104
     * @param mixed $item Value to be stored
105
     * @param array $options THIS IS NOT USED HERE
106
     * @return $this
107
     */
108
    protected function addItem($alias, $item = null, array $options = null)
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
109
    {
110
        $this->checkIfProtected($alias);
111
112
        // Are we adding multiple items?
113
        if (is_array($alias)) {
114
            foreach ($alias as $key => $value) {
115
                $this->addItem($key, $value);
116
            }
117
            return $this;
118
        }
119
120
        // No, we are adding a single item
121
        $repo = $this->getItemsName();
122
        $loc = &$this->$repo;
123
124
        $pieces = explode('.', $alias);
125
        $currentLevel = 1;
126
        $nestLevels = count($pieces) - 1;
127
128
        foreach ($pieces as $step) {
129
            // Make sure we are not trying to nest under a non-array. This is gross
130
            // https://github.com/chrismichaels84/data-manager/issues/6
131
132
            // 1. Not at the last level (the one with the desired value),
133
            // 2. The nest level is already set,
134
            // 3. and is not an array
135
            if ($nestLevels > $currentLevel && isset($loc[$step]) && !is_array($loc[$step])) {
136
                throw new NestingUnderNonArrayException();
137
            }
138
139
            $loc = &$loc[$step];
140
            $currentLevel++;
141
        }
142
        $loc = $item;
143
144
        return $this;
145
    }
146
147
    /**
148
     * Updates an item
149
     *
150
     * @param string $alias
151
     * @param null $item
152
     *
153
     * @return $this
154
     */
155
    public function set($alias, $item = null)
156
    {
157
        return $this->addItem($alias, $item);
158
    }
159
160
    /**
161
     * Push a value or values onto the end of an array inside manager
162
     * @param string $alias The level of nested data
163
     * @param mixed $value The first value to append
164
     * @param null|mixed $other Optional other values to apend
165
     * @return int Number of items pushed
166
     * @throws ItemNotFoundException If pushing to unset array
167
     */
168
    public function push($alias, $value, $other = null)
169
    {
170
        if (isset($other)) {
171
            $values = func_get_args();
172
            array_shift($values);
173
        } else {
174
            $values = [$value];
175
        }
176
177
        $array = $this->get($alias);
178
179
        if (!is_array($array)) {
180
            throw new NestingUnderNonArrayException("You may only push items onto an array");
181
        }
182
183
        foreach ($values as $value) {
184
            array_push($array, $value);
185
        }
186
        $this->set($alias, $array);
187
188
        return count($values);
189
    }
190
191
    /**
192
     * Get a single item
193
     *
194
     * Note: When editing, update ManagesIocTrait::getRaw()
195
     *
196
     * @param string $alias
197
     * @param string $fallback Defaults to '_michaels_no_fallback' so null can be a fallback
198
     * @throws \Michaels\Manager\Exceptions\ItemNotFoundException If item not found
199
     * @return mixed
200
     */
201
    public function get($alias, $fallback = '_michaels_no_fallback')
202
    {
203
        return $this->getRaw($alias, $fallback);
204
    }
205
206
    /**
207
     * Get a single item
208
     *
209
     * @param string $alias
210
     * @param string $fallback Defaults to '_michaels_no_fallback' so null can be a fallback
211
     * @throws \Michaels\Manager\Exceptions\ItemNotFoundException If item not found
212
     * @return mixed
213
     */
214
    public function getRaw($alias, $fallback = '_michaels_no_fallback')
215
    {
216
        $item = $this->getIfExists($alias);
217
218
        // The item was not found
219
        if ($item instanceof NoItemFoundMessage) {
220
            if ($fallback !== '_michaels_no_fallback') {
221
                $item = $fallback;
222
            } else {
223
                throw new ItemNotFoundException("$alias not found");
224
            }
225
        }
226
227
        return $this->prepareReturnedValue($item);
228
    }
229
230
    /**
231
     * Return an item if it exist
232
     * @param $alias
233
     * @return NoItemFoundMessage
234
     */
235
    public function getIfExists($alias)
236
    {
237
        $repo = $this->getItemsName();
238
        $loc = &$this->$repo;
239 View Code Duplication
        foreach (explode('.', $alias) as $step) {
240
            if (array_key_exists($step, $loc)) {
241
                $loc = &$loc[$step];
242
            } else {
243
                return new NoItemFoundMessage($alias);
244
            }
245
        }
246
        return $loc;
247
    }
248
249
    /**
250
     * Return an item if it exist
251
     * Alias of getIfExists()
252
     *
253
     * @param $alias
254
     * @return NoItemFoundMessage
255
     */
256
    public function getIfHas($alias)
257
    {
258
        return $this->getIfExists($alias);
259
    }
260
261
    /**
262
     * Return all items as array
263
     *
264
     * @return array
265
     */
266
    public function getAll()
267
    {
268
        $repo = $this->getItemsName();
269
        return $this->prepareReturnedValue($this->$repo);
270
    }
271
272
    /**
273
     * Return all items as array
274
     * Alias of getAll()
275
     * @return array
276
     */
277
    public function all()
278
    {
279
        return $this->getAll();
280
    }
281
282
    /**
283
     * Confirm or deny that an item exists
284
     *
285
     * @param $alias
286
     * @return bool
287
     */
288
    public function exists($alias)
289
    {
290
        $repo = $this->getItemsName();
291
        $loc = &$this->$repo;
292 View Code Duplication
        foreach (explode('.', $alias) as $step) {
293
            if (!isset($loc[$step])) {
294
                return false;
295
            } else {
296
                $loc = &$loc[$step];
297
            }
298
        }
299
        return true;
300
    }
301
302
    /**
303
     * Confirm or deny that an item exists
304
     * Alias of exists()
305
     *
306
     * @param $alias
307
     * @return bool
308
     */
309
    public function has($alias)
310
    {
311
        return $this->exists($alias);
312
    }
313
314
315
    /**
316
     * Confirm that manager has no items
317
     * @return boolean
318
     */
319
    public function isEmpty()
320
    {
321
        $repo = $this->getItemsName();
322
        return empty($this->$repo);
323
    }
324
325
    /**
326
     * Deletes an item
327
     *
328
     * @param $alias
329
     * @return $this
330
     */
331
    public function remove($alias)
332
    {
333
        $repo = $this->getItemsName();
334
        $loc = &$this->$repo;
335
        $parts = explode('.', $alias);
336
337
        while (count($parts) > 1) {
338
            $step = array_shift($parts);
339
            if (isset($loc[$step]) && is_array($loc[$step])) {
340
                $loc = &$loc[$step];
341
            }
342
        }
343
344
        unset($loc[array_shift($parts)]);
345
        return $this;
346
    }
347
348
    /**
349
     * Clear the manager
350
     * @return $this
351
     */
352
    public function clear()
353
    {
354
        $repo = $this->getItemsName();
355
        $this->$repo = [];
356
        return $this;
357
    }
358
359
    /**
360
     * Reset the manager with an array of items
361
     * Alias of initManager()
362
     *
363
     * @param array $items
364
     * @return mixed
365
     */
366
    public function reset($items)
367
    {
368
        $this->initManager($items);
369
    }
370
371
    /**
372
     * Guard an alias from being modified
373
     * @param $item
374
     * @return $this
375
     */
376
    public function protect($item)
377
    {
378
        array_push($this->protectedItems, $item);
379
        return $this;
380
    }
381
382
    /**
383
     * Merge a set of defaults with the current items
384
     * @param array $defaults
385
     * @return $this
386
     */
387
    public function loadDefaults(array $defaults)
388
    {
389
        $this->mergeDefaults($defaults);
390
        return $this;
391
    }
392
393
    /**
394
     * Returns the name of the property that holds data items
395
     * @return string
396
     */
397
    public function getItemsName()
398
    {
399
        return $this->nameOfItemsRepository;
400
    }
401
402
    /**
403
     * Sets the name of the property that holds data items
404
     * @param $nameOfItemsRepository
405
     * @return $this
406
     */
407
    public function setItemsName($nameOfItemsRepository)
408
    {
409
        $this->nameOfItemsRepository = $nameOfItemsRepository;
410
        return $this;
411
    }
412
413
    /**
414
     * Get the collection of items as JSON.
415
     *
416
     * @param  int $options
417
     * @return string
418
     */
419
    public function toJson($options = 0)
420
    {
421
        return json_encode($this->getAll(), $options);
422
    }
423
424
    /**
425
     * When manager instance is used as a string, return json of items
426
     * @return string
427
     */
428
    public function __toString()
429
    {
430
        return $this->toJson();
431
    }
432
433
    /**
434
     * Prepare the returned value
435
     * @param $value
436
     * @return mixed
437
     */
438
    protected function prepareReturnedValue($value)
439
    {
440
        // Are we looking for Collections?
441
        if (method_exists($this, 'toCollection')) {
442
            return $this->toCollection($value);
443
        }
444
445
        // No? Just return the value
446
        return $value;
447
    }
448
449
    /**
450
     * Recursively merge defaults array and items array
451
     * @param array $defaults
452
     * @param string $level
453
     */
454
    protected function mergeDefaults(array $defaults, $level = '')
455
    {
456
        foreach ($defaults as $key => $value) {
457
            if (is_array($value)) {
458
                $original = $this->getIfExists(ltrim("$level.$key", "."));
459
                if (is_array($original)) {
460
                    $this->mergeDefaults($value, "$level.$key");
461
                } elseif ($original instanceof NoItemFoundMessage) {
462
                    $this->set(ltrim("$level.$key", "."), $value);
463
                }
464
            } else {
465
                if (!$this->exists(ltrim("$level.$key", "."))) {
466
                    $this->set(ltrim("$level.$key", "."), $value);
467
                }
468
            }
469
        }
470
    }
471
472
    /**
473
     * Cycle through the nests to see if an item is protected
474
     * @param $item
475
     */
476
    protected function checkIfProtected($item)
477
    {
478
        $this->performProtectedCheck($item);
479
480
        if (!is_string($item)) {
481
            return;
482
        }
483
484
        $prefix = $item;
485
        while (false !== $pos = strrpos($prefix, '.')) {
486
            $prefix = substr($item, 0, $pos);
487
            $this->performProtectedCheck($prefix);
488
            $prefix = rtrim($prefix, '.');
489
        }
490
    }
491
492
    /**
493
     * Throws an exception if item is protected
494
     * @param $item
495
     */
496
    protected function performProtectedCheck($item)
497
    {
498
        if (in_array($item, $this->protectedItems)) {
499
            throw new ModifyingProtectedValueException("Cannot access $item because it is protected");
500
        }
501
    }
502
}
503