Passed
Push — 1.x ( 44281e...e2dae5 )
by Kevin
02:12
created

Callback::invoke()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 21
rs 9.6111
1
<?php
2
3
namespace Zenstruck;
4
5
use Zenstruck\Callback\Exception\UnresolveableArgument;
6
use Zenstruck\Callback\Parameter;
7
8
/**
9
 * @author Kevin Bond <[email protected]>
10
 */
11
final class Callback
12
{
13
    /** @var \ReflectionFunction */
14
    private $function;
15
16
    private function __construct(\ReflectionFunction $function)
17
    {
18
        $this->function = $function;
19
    }
20
21
    /**
22
     * @param callable|\ReflectionFunction $value
23
     */
24
    public static function createFor($value): self
25
    {
26
        if (\is_callable($value)) {
27
            $value = new \ReflectionFunction(\Closure::fromCallable($value));
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type ReflectionFunction; however, parameter $callback of Closure::fromCallable() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

27
            $value = new \ReflectionFunction(\Closure::fromCallable(/** @scrutinizer ignore-type */ $value));
Loading history...
28
        }
29
30
        if (!$value instanceof \ReflectionFunction) {
31
            throw new \InvalidArgumentException('$value must be callable.');
32
        }
33
34
        return new self($value);
35
    }
36
37
    /**
38
     * Invoke the callable with the passed arguments. Arguments of type
39
     * Zenstruck\Callback\Parameter are resolved before invoking.
40
     *
41
     * @param mixed ...$arguments
42
     *
43
     * @return mixed
44
     *
45
     * @throws UnresolveableArgument
46
     */
47
    public function invoke(...$arguments)
48
    {
49
        $parameters = $this->function->getParameters();
50
51
        foreach ($arguments as $key => $argument) {
52
            if (!$argument instanceof Parameter) {
53
                continue;
54
            }
55
56
            if (!\array_key_exists($key, $parameters)) {
57
                throw new \ArgumentCountError(\sprintf('No argument %d for callable. Expected type: "%s".', $key + 1, $argument->type()));
58
            }
59
60
            try {
61
                $arguments[$key] = $argument->resolve($parameters[$key]);
62
            } catch (UnresolveableArgument $e) {
63
                throw new UnresolveableArgument(\sprintf('Unable to resolve argument %d for callback. Expected type: "%s".', $key + 1, $argument->type()), $e);
64
            }
65
        }
66
67
        return $this->function->invoke(...$arguments);
68
    }
69
70
    /**
71
     * Invoke the callable using the passed Parameter to resolve all callable
72
     * arguments.
73
     *
74
     * @param int $min Enforce a minimum number of arguments the callable must have
75
     *
76
     * @return mixed
77
     *
78
     * @throws UnresolveableArgument
79
     */
80
    public function invokeAll(Parameter $parameter, int $min = 0)
81
    {
82
        $arguments = $this->function->getParameters();
83
84
        if (\count($arguments) < $min) {
85
            throw new \ArgumentCountError("{$min} argument(s) of type \"{$parameter->type()}\" required.");
86
        }
87
88
        foreach ($arguments as $key => $argument) {
89
            try {
90
                $arguments[$key] = $parameter->resolve($argument);
91
            } catch (UnresolveableArgument $e) {
92
                throw new UnresolveableArgument(\sprintf('Unable to resolve argument %d for callback. Expected type: "%s"', $key + 1, $parameter->type()), $e);
93
            }
94
        }
95
96
        return $this->function->invoke(...$arguments);
97
    }
98
}
99