Passed
Push — master ( abdf29...71665b )
by
unknown
03:29 queued 43s
created

Conversions   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 156
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0
wmc 25
lcom 0
cbo 1

3 Methods

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