DescendDotNotationPath   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 22
c 5
b 0
f 1
lcom 1
cbo 10
dl 0
loc 250
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A intoArray() 0 8 1
A into() 0 13 3
A intoMixed() 0 4 1
A __invoke() 0 4 1
B getPathFromRoot() 0 24 3
A getChildFromPart() 0 10 2
A getPartFromObject() 0 15 3
A intoObject() 0 12 2
A getPartFromArray() 0 14 3
A getExtension() 0 14 3
1
<?php
2
3
/**
4
 * Copyright (c) 2015-present Ganbaro Digital Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   DataContainers/ValueBuilders
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2011-present Mediasift Ltd www.datasift.com
40
 * @copyright 2015-present Ganbaro Digital Ltd www.ganbarodigital.com
41
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
42
 * @link      http://code.ganbarodigital.com/php-data-containers
43
 */
44
45
namespace GanbaroDigital\DataContainers\ValueBuilders;
46
47
use GanbaroDigital\DataContainers\Checks\IsReadableContainer;
48
use GanbaroDigital\DataContainers\Exceptions\E4xx_CannotDescendPath;
49
use GanbaroDigital\DataContainers\Exceptions\E4xx_NoSuchIndex;
50
use GanbaroDigital\DataContainers\Exceptions\E4xx_NoSuchProperty;
51
use GanbaroDigital\DataContainers\Exceptions\E4xx_UnsupportedType;
52
use GanbaroDigital\Reflection\Checks\IsAssignable;
53
use GanbaroDigital\Reflection\Checks\IsIndexable;
54
use GanbaroDigital\Reflection\Checks\IsTraversable;
55
use GanbaroDigital\Reflection\Requirements\RequireAssignable;
56
use GanbaroDigital\Reflection\Requirements\RequireIndexable;
57
use GanbaroDigital\Reflection\Requirements\RequireStringy;
58
use GanbaroDigital\Reflection\ValueBuilders\FirstMethodMatchingType;
59
use GanbaroDigital\Reflection\ValueBuilders\SimpleType;
60
61
class DescendDotNotationPath
62
{
63
    /**
64
     * descend inside an array, using dot.notation.support, and optionally
65
     * extending the array if the end of the dot.notation.path is missing
66
     *
67
     * @param  array &$arr
68
     *         the array to dig into
69
     * @param  string $index
70
     *         the dot.notation.support path to descend
71
     * @param  array|callable|string|null $extendingItem
72
     *         if we need to extend, what data type do we extend using?
73
     * @return mixed
74
     */
75
    public static function &intoArray(&$arr, $index, $extendingItem = null)
76
    {
77
        // robustness!
78
        RequireIndexable::check($arr, E4xx_UnsupportedType::class);
79
80
        $retval =& self::getPathFromRoot($arr, $index, $extendingItem);
81
        return $retval;
82
    }
83
84
    /**
85
     * descend inside an object, using dot.notation.support, and optionally
86
     * extending the object if the end of the dot.notation.path is missing
87
     *
88
     * @param  object $obj
89
     *         the object to dig into
90
     * @param  string $property
91
     *         the dot.notation.support path to descend
92
     * @param  array|callable|string|null $extendingItem
93
     *         if we need to extend, what data type do we extend using?
94
     * @return mixed
95
     */
96
    public static function &intoObject($obj, $property, $extendingItem = null)
97
    {
98
        // robustness!
99
        RequireAssignable::check($obj, E4xx_UnsupportedType::class);
100
        RequireStringy::check($property, E4xx_UnsupportedType::class);
101
        if (strlen($property) === 0) {
102
            throw new \InvalidArgumentException("'\$property' cannot be empty string");
103
        }
104
105
        $retval =& self::getPathFromRoot($obj, $property, $extendingItem);
106
        return $retval;
107
    }
108
109
    /**
110
     * descend inside a container, using dot.notation.support, and optionally
111
     * extending the container if the end of the dot.notation.path is missing
112
     *
113
     * @param  mixed &$item
114
     *         the container to dig into
115
     * @param  string $property
116
     *         the dot.notation.support path to descend
117
     * @param  array|callable|string|null $extendingItem
118
     *         if we need to extend, what data type do we extend using?
119
     * @return mixed
120
     */
121
    public static function &into(&$item, $property, $extendingItem = null)
122
    {
123
        if (IsAssignable::check($item)) {
124
            $retval =& self::intoObject($item, $property, $extendingItem);
125
            return $retval;
126
        }
127
        if (IsTraversable::check($item)) {
128
            $retval =& self::intoArray($item, $property, $extendingItem);
129
            return $retval;
130
        }
131
132
        throw new E4xx_UnsupportedType(SimpleType::from($item));
133
    }
134
135
    /**
136
     * descend inside a variable, using dot.notation.support, and optionally
137
     * extending the variable if the end of the dot.notation.path is missing
138
     *
139
     * @deprecated since 2.2.0
140
     * @codeCoverageIgnore
141
     *
142
     * @param  object|array &$item
143
     *         the variable to dig into
144
     * @param  string $path
145
     *         the dot.notation.support path to descend
146
     * @param  array|callable|string|null $extendingItem
147
     *         if we need to extend, what data type do we extend using?
148
     * @return mixed
149
     */
150
    public static function &intoMixed($item, $path, $extendingItem = null)
151
    {
152
        return self::into($item, $path, $extendingItem);
153
    }
154
155
    /**
156
     * descend inside a variable, using dot.notation.support, and optionally
157
     * extending the variable if the end of the dot.notation.path is missing
158
     *
159
     * @param  object|array &$item
160
     *         the variable to dig into
161
     * @param  string $path
162
     *         the dot.notation.support path to descend
163
     * @param  array|callable|string|null $extendingItem
164
     *         if we need to extend, what data type do we extend using?
165
     * @return mixed
166
     */
167
    public function &__invoke($item, $path, $extendingItem = null)
168
    {
169
        return self::into($item, $path, $extendingItem);
170
    }
171
172
    /**
173
     * get whatever is at the end of the given dot.notation.support path,
174
     * optionally extending the path as required
175
     *
176
     * @param  array|object &$root
177
     *         where we start from
178
     * @param  string $path
179
     *         the dot.notation.support path to descend
180
     * @param  array|callable|string|null $extendingItem
181
     *         if we need to extend, what data type do we extend using?
182
     * @return mixed
183
     */
184
    private static function &getPathFromRoot(&$root, $path, $extendingItem)
185
    {
186
        // to avoid recursion, this will track where we are in the tree
187
        $retval =& $root;
188
189
        // this will track where we have been, in case we need to report on
190
        // an error
191
        $visitedPath = [];
192
193
        // explore the path
194
        $parts = explode(".", $path);
195
        foreach ($parts as $part) {
196
            // make sure we have a valid container
197
            if (!IsReadableContainer::check($retval)) {
198
                throw new E4xx_CannotDescendPath($retval, implode('.', $visitedPath));
199
            }
200
201
            $retval =& self::getChildFromPart($retval, $part, $extendingItem);
202
            $visitedPath[] = $part;
203
        }
204
205
        // if we get here, then we have found what they are looking for
206
        return $retval;
207
    }
208
209
    /**
210
     * get a child from part of our container tree, optionally extending
211
     * the container if needed
212
     *
213
     * @param  array|object &$container
214
     *         the container we want to get the child from
215
     * @param  string $part
216
     *         the name of the child that we want
217
     * @param  array|callable|string|null $extendingItem
218
     *         if we need to extend the container, this is what we use to
219
     *         do the extension
220
     * @return mixed
221
     */
222
    private static function &getChildFromPart(&$container, $part, $extendingItem = null)
223
    {
224
        if (IsAssignable::check($container)) {
225
            return self::getPartFromObject($container, $part, $extendingItem);
226
        }
227
228
        // if we get here, this must be an array
229
        $retval =& self::getPartFromArray($container, $part, $extendingItem);
230
        return $retval;
231
    }
232
233
    /**
234
     * get a property from an object, extending the object if the property
235
     * does not exist
236
     *
237
     * @param  object $obj
238
     *         the object to look inside
239
     * @param  string $part
240
     *         the name of the property to extract
241
     * @param  array|callable|string|null $extendingItem
242
     *         if we need to extend, what data type do we extend using?
243
     * @return mixed
244
     */
245
    private static function &getPartFromObject($obj, $part, $extendingItem = null)
246
    {
247
        // general case
248
        if (isset($obj->$part)) {
249
            return $obj->$part;
250
        }
251
252
        // can we extend?
253
        if ($extendingItem === null) {
254
            throw new E4xx_NoSuchProperty($obj, $part);
255
        }
256
257
        $obj->$part = self::getExtension($extendingItem);
258
        return $obj->$part;
259
    }
260
261
    /**
262
     * get an index from an array, extending the array if the index does
263
     * not exist
264
     *
265
     * @param  array &$arr
266
     *         the array to look inside
267
     * @param  string $part
268
     *         the index to look up
269
     * @param  array|callable|string|null $extendingItem
270
     *         if we need to extend, what data type do we extend using?
271
     * @return mixed
272
     */
273
    private static function &getPartFromArray(&$arr, $part, $extendingItem = null)
274
    {
275
        if (array_key_exists($part, $arr)) {
276
            return $arr[$part];
277
        }
278
279
        // can we extend?
280
        if ($extendingItem === null) {
281
            throw new E4xx_NoSuchIndex('array', $part);
282
        }
283
284
        $arr[$part] = self::getExtension($extendingItem);
285
        return $arr[$part];
286
    }
287
288
    /**
289
     * create new extension
290
     *
291
     * @param  array|callable|string|null $extendingItem
292
     *         what data type do we extend using?
293
     * @return array|object
294
     *         the extension that has been created
295
     */
296
    private static function getExtension($extendingItem)
297
    {
298
        if (is_array($extendingItem)) {
299
            return $extendingItem;
300
        }
301
302
        // if they don't return anything useful, that is their problem!
303
        if (is_callable($extendingItem)) {
304
            return $extendingItem();
305
        }
306
307
        // assume that it is a class name
308
        return new $extendingItem;
309
    }
310
}
311