Completed
Push — master ( 699a19...b2f933 )
by Michael
05:34 queued 03:05
created

ManagesItemsTrait::isEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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