Completed
Pull Request — master (#19)
by Rasmus
01:31
created

Reflection::getFirstParameter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 6
cts 7
cp 0.8571
rs 9.7998
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3.0261
1
<?php
2
3
namespace mindplay\unbox;
4
5
use Closure;
6
use ReflectionFunction;
7
use ReflectionFunctionAbstract;
8
use ReflectionMethod;
9
use ReflectionParameter;
10
use ReflectionException;
11
12
/**
13
 * Pseudo-namespace for some common reflection helper-functions.
14
 */
15
abstract class Reflection
16
{
17
    /**
18
     * @type string pattern for parsing an argument type from a ReflectionParameter string
19
     *
20
     * @see Reflection::getParameterType()
21
     */
22
    const ARG_PATTERN = '/(?:\<required\>|\<optional\>)\\s+([\\w\\\\]+)/';
23
24
    /**
25
     * Create a Reflection of the function references by any type of callable (or object implementing `__invoke()`)
26
     *
27
     * @param callable|object $callback
28
     *
29
     * @return ReflectionFunctionAbstract
30
     *
31
     * @throws InvalidArgumentException
32
     */
33 1
    public static function createFromCallable($callback)
34
    {
35 1
        if (is_object($callback)) {
36 1
            if ($callback instanceof Closure) {
37 1
                return new ReflectionFunction($callback);
38 1
            } elseif (method_exists($callback, '__invoke')) {
39 1
                return new ReflectionMethod($callback, '__invoke');
40
            }
41
42 1
            throw new InvalidArgumentException("class " . get_class($callback) . " does not implement __invoke()");
43 1
        } elseif (is_array($callback)) {
44 1
            if (is_callable($callback)) {
45 1
                return new ReflectionMethod($callback[0], $callback[1]);
46
            }
47
48 1
            throw new InvalidArgumentException("expected callable");
49 1
        } elseif (is_callable($callback)) {
50 1
            return new ReflectionFunction($callback);
51
        }
52
53 1
        throw new InvalidArgumentException("unexpected value: " . var_export($callback, true) . " - expected callable");
54
    }
55
56
    /**
57
     * Obtain the type-hint of a `ReflectionParameter`, but avoid triggering autoload (as a performance optimization)
58
     *
59
     * @param ReflectionParameter $param
60
     *
61
     * @return string|null fully-qualified type-name (or NULL, if no type-hint was available)
62
     */
63 1
    public static function getParameterType(ReflectionParameter $param)
64
    {
65 1
        if (method_exists($param, "getType")) {
66
            $type = $param->getType();
67
68
            return $type === null || $type->isBuiltin()
69
                ? null // ignore scalar type-hints
70
                : $type->__toString();
71
        }
72
73 1
        if (preg_match(self::ARG_PATTERN, $param->__toString(), $matches) === 1) {
74 1
            return $matches[1];
75
        }
76
77 1
        return null; // no type-hint is available
78
    }
79
80
    /**
81
     * Obtain the `ReflectionParameter` instance from the first parameter of
82
     * any type of callable (or object implementing `__invoke()`)
83
     *
84
     * @param callable|object $callback
85
     *
86
     * @return ReflectionParameter
87
     *
88
     * @throws ReflectionException
89
     */
90 1
    public static function getFirstParameter($callback)
91
    {
92 1
        if ($callback instanceof Closure) {
93 1
            return new ReflectionParameter($callback, 0); // shortcut reflection for closures (as an optimization)
94
        }
95
96 1
        $params = Reflection::createFromCallable($callback)->getParameters();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
97
98 1
        if (! isset($params[0])) {
99
            throw new ReflectionException("Parameter 0 is not defined");
100
        }
101
102 1
        return $params[0];
103
    }
104
}
105