Passed
Push — 1.x ( 82cd9a...b759cf )
by Kevin
01:14
created

Callback::__toString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
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
    public function __toString(): string
22
    {
23
        if ($class = $this->function->getClosureScopeClass()) {
24
            return "{$class->getName()}:{$this->function->getStartLine()}";
25
        }
26
27
        return $this->function->getName();
28
    }
29
30
    /**
31
     * @param callable|\ReflectionFunction $value
32
     */
33
    public static function createFor($value): self
34
    {
35
        if (\is_callable($value)) {
36
            $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

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