Passed
Pull Request — master (#64)
by
unknown
03:08
created

Conversions::mixedToValueGetter()   B

Complexity

Conditions 11
Paths 2

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11

Importance

Changes 0
Metric Value
cc 11
nc 2
nop 1
dl 0
loc 47
rs 7.3166
c 0
b 0
f 0
ccs 28
cts 28
cp 1
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Zicht Online <http://zicht.nl>
4
 */
5
6
namespace Zicht\Itertools\util;
7
8
use Doctrine\Common\Collections\Collection;
9
use Zicht\Itertools\lib\StringIterator;
10
use function Zicht\Itertools\conversions\mixed_to_closure;
11
12
class Conversions
13
{
14
    /**
15
     * Transforms anything into an Iterator or throws an InvalidArgumentException
16
     *
17
     * > mixedToIterator([1, 2, 3])
18
     * 1 2 3
19
     *
20
     * > mixedToIterator('foo')
21
     * f o o
22
     *
23
     * @param array|string|\Iterator $iterable
24
     * @return \Iterator
25
     */
26 451
    public static function mixedToIterator($iterable)
27
    {
28
        // NULL is often used to indicate that nothing is there,
29
        // for robustness we will deal with NULL as it is an empty array
30 451
        if (is_null($iterable)) {
31 6
            $iterable = new \ArrayIterator([]);
32
        }
33
34
        // an array is *not* an instance of Traversable (as it is not an
35
        // object and hence can not 'implement Traversable')
36 451
        if (is_array($iterable)) {
37 373
            $iterable = new \ArrayIterator($iterable);
38
        }
39
40
        // a string is considered iterable in Python
41 451
        if (is_string($iterable)) {
42 8
            $iterable = new StringIterator($iterable);
43
        }
44
45
        // a doctrine Collection (i.e. Array or Persistent) is also an iterator
46 451
        if ($iterable instanceof Collection) {
47 3
            $iterable = $iterable->getIterator();
48
        }
49
50 451
        if ($iterable instanceof \Traversable and !($iterable instanceof \Iterator)) {
51 1
            $iterable = new \IteratorIterator($iterable);
52
        }
53
54
        // by now it should be an Iterator, otherwise throw an exception
55 451
        if (!($iterable instanceof \Iterator)) {
56 53
            throw new \InvalidArgumentException('Argument $ITERABLE must be a Traversable');
57
        }
58
59 398
        return $iterable;
60
    }
61
62
    /**
63
     * Try to transforms something into a Closure.
64
     *
65
     * When $closure is null the returned Closure behaves like an identity function,
66
     * i.e. it will return the value that it is given.
67
     *
68
     * @param null|\Closure $closure
69
     * @return \Closure
70
     */
71 353
    public static function mixedToClosure($closure)
72
    {
73 353
        if (is_null($closure)) {
74
            return function ($value) {
75 140
                return $value;
76 160
            };
77
        }
78
79 220
        if (!($closure instanceof \Closure)) {
80
            // A \Closure is always callable, but a callable is not always a \Closure.
81
            // Checking within this if statement is a slight optimization, preventing an unnecessary function wrap
82 16
            if (is_callable($closure)) {
83
                $closure = function () use ($closure) {
84 2
                    return call_user_func_array($closure, func_get_args());
85 2
                };
86
            } else {
87 14
                throw new \InvalidArgumentException('Argument $CLOSURE must be a Closure');
88
            }
89
        }
90
91 206
        return $closure;
92
    }
93
94
    /**
95
     * Try to transforms something into a Closure that gets a value from $strategy.
96
     *
97
     * When $strategy is null the returned Closure behaves like an identity function,
98
     * i.e. it will return the value that it is given.
99
     *
100
     * When $strategy is callable it is converted into a Closure (see mixedToClosure).
101
     *
102
     * When $strategy is a string the returned Closure tries to find properties,
103
     * methods, or array indexes named by the string.  Multiple property, method,
104
     * or index names can be separated by a dot.  The same behavior as Twig is
105
     * used, see http://twig.sensiolabs.org/doc/2.x/templates.html#variables
106
     *
107
     * @param null|string|\Closure $strategy
108
     * @return \Closure
109
     */
110 345
    public static function mixedToValueGetter($strategy)
111
    {
112 345
        if (is_string($strategy)) {
113 88
            $keyParts = explode('.', $strategy);
114 88
            $strategy = function ($value) use ($keyParts) {
115 88
                foreach ($keyParts as $keyPart) {
116 88
                    if (is_array($value) && array_key_exists($keyPart, $value)) {
117 28
                        $value = $value[$keyPart];
118 28
                        continue;
119
                    }
120
121 67
                    if (is_object($value)) {
122
                        // property_exists does not distinguish between public, protected, or private properties, hence we need to use reflection
123 57
                        $reflection = new \ReflectionObject($value);
124 57
                        if ($reflection->hasProperty($keyPart)) {
125 30
                            $property = $reflection->getProperty($keyPart);
126 30
                            if ($property->isPublic()) {
127 15
                                $value = $property->getValue($value);
128 15
                                continue;
129
                            }
130
                        }
131
132 44
                        foreach (['', 'get', 'is', 'has'] as $prefix) {
133 44
                            $method = sprintf('%s%s', $prefix, $keyPart);
134 44
                            if (is_callable([$value, $method])) {
135 21
                                $value = call_user_func([$value, $method]);
136 44
                                continue 2;
137
                            }
138
                        }
139
140 28
                        if (method_exists($value, '__get')) {
141 9
                            $value = $value->$keyPart;
142 9
                            continue;
143
                        }
144
                    }
145
146
                    // no match found
147 31
                    $value = null;
148 31
                    break;
149
                }
150
151 88
                return $value;
152 88
            };
153
        }
154
155 345
        return self::mixedToClosure($strategy);
156
    }
157
}
158