Completed
Push — develop ( a379bf )
by Nate
01:48
created

ArgumentHelper::closure()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 14
cp 0
rs 9.7
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 12
1
<?php
2
3
/**
4
 * @author    Flipbox Factory
5
 * @copyright Copyright (c) 2017, Flipbox Digital
6
 * @link      https://github.com/flipbox/transform/releases/latest
7
 * @license   https://github.com/flipbox/transform/blob/master/LICENSE
8
 */
9
10
namespace Flipbox\Transform\Helpers;
11
12
/**
13
 * @author Flipbox Factory <[email protected]>
14
 * @since 3.0.0
15
 */
16
class ArgumentHelper
17
{
18
    /**
19
     * Extracts all of the valid arguments for a provided Closure.
20
     *
21
     * @param $transformer
22
     * @param array $params
23
     * @return array
24
     */
25
    public static function closure(\Closure $transformer, array $params): array
26
    {
27
        if (empty($params)) {
28
            return $params;
29
        }
30
31
        try {
32
            return self::interpretFunction(
33
                new \ReflectionFunction($transformer),
34
                $params
35
            );
36
        } catch (\ReflectionException $e) {
37
            // Sorry
38
        }
39
40
        return [];
41
    }
42
43
    /**
44
     * Extracts all of the valid arguments for a provided callable.
45
     *
46
     * @param callable $transformer
47
     * @param array $params
48
     * @param string $method
49
     * @return array
50
     */
51
    public static function callable(callable $transformer, array $params, string $method = 'transform'): array
52
    {
53
        if (TransformerHelper::isClosure($transformer)) {
54
            return static::closure($transformer, $params);
1 ignored issue
show
Documentation introduced by
$transformer is of type callable, but the function expects a object<Closure>.

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...
55
        }
56
57
        if (empty($params)) {
58
            return $params;
59
        }
60
61
        try {
62
            return self::interpretFunction(
63
                new \ReflectionMethod($transformer, $method),
64
                $params
65
            );
66
        } catch (\ReflectionException $e) {
67
            // Sorry
68
        }
69
70
        return [];
71
    }
72
73
    /**
74
     * Merges an indexed array of arguments values with their name.  Note, the orders MUST match.
75
     *
76
     * @param callable $transformer
77
     * @param array $params
78
     * @param string $method
79
     * @return array
80
     * @throws \ReflectionException
81
     */
82
    public static function mergeCallable(callable $transformer, array $params, string $method = 'transform'): array
83
    {
84
        if (TransformerHelper::isClosure($transformer)) {
85
            return static::mergeClosure($transformer, $params);
86
        }
87
88
        return self::mergeParameters(
89
            new \ReflectionMethod($transformer, $method),
90
            $params
91
        );
92
    }
93
94
    /**
95
     * Merges an indexed array of arguments values with their name.  Note, the orders MUST match.
96
     *
97
     * @param callable $transformer
98
     * @param array $params
99
     * @return array
100
     * @throws \ReflectionException
101
     */
102
    public static function mergeClosure(callable $transformer, array $params): array
103
    {
104
        return self::mergeParameters(
105
            new \ReflectionFunction($transformer),
106
            $params
107
        );
108
    }
109
110
111
    /**
112
     * @param \ReflectionFunctionAbstract $function
113
     * @param array $params
114
     * @return array
115
     */
116
    private static function mergeParameters(\ReflectionFunctionAbstract $function, array $params): array
117
    {
118
        $args = [];
119
120
        foreach ($function->getParameters() as $key => $param) {
121
            $name = $param->name;
122
            $args[$name] = $params[$key] ?? null;
123
        }
124
125
        return $args;
126
    }
127
128
    /**
129
     * @param \ReflectionFunctionAbstract $function
130
     * @param array $params
131
     * @return array
132
     * @throws \InvalidArgumentException
133
     */
134
    private static function interpretFunction(\ReflectionFunctionAbstract $function, array $params): array
135
    {
136
        $args = $missing = [];
137
        foreach ($function->getParameters() as $param) {
138
            $name = $param->name;
139
            if (true === in_array($name, ['data'], true)) {
140
                continue;
141
            }
142
143
            if (array_key_exists($name, $params)) {
144
                $args[$name] = static::argType($param, $params[$name]);
0 ignored issues
show
Bug introduced by
Since argType() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of argType() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
145
            } elseif ($param->isDefaultValueAvailable()) {
146
                $args[$name] = $param->getDefaultValue();
147
            } elseif ($param->isVariadic()) {
148
                $args = array_merge(
149
                    $args,
150
                    ${$param->name}
151
                );
152
            } else {
153
                $missing[$name] = $name;
154
            }
155
        }
156
157
        if (!empty($missing)) {
158
            throw new \InvalidArgumentException(sprintf(
159
                'Missing required parameters "%s".',
160
                implode(', ', $missing)
161
            ));
162
        }
163
164
        return $args;
165
    }
166
167
    /**
168
     * @param \ReflectionParameter $param
169
     * @param $value
170
     * @return mixed
171
     */
172
    private static function argType(
173
        \ReflectionParameter $param,
174
        $value
175
    ) {
176
        if (!$param->hasType()) {
177
            return $value;
178
        }
179
180
        if ($param->isArray()) {
181
            return (array)$value;
182
        }
183
184
        if ($param->isCallable() && is_callable($value)) {
185
            return $value;
186
        }
187
188
        if (!is_array($value)) {
189
            return $value;
190
        }
191
192
        throw new \InvalidArgumentException(sprintf(
193
            'Invalid data received for parameter "%s".',
194
            $param->name
195
        ));
196
    }
197
}
198