1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /* |
||
6 | * This file is part of PHP Invoker. |
||
7 | * |
||
8 | * PHP version 7.1 and above required |
||
9 | * |
||
10 | * @author Divine Niiquaye Ibok <[email protected]> |
||
11 | * @copyright 2019 Biurad Group (https://biurad.com/) |
||
12 | * @license https://opensource.org/licenses/BSD-3-Clause License |
||
13 | * |
||
14 | * For the full copyright and license information, please view the LICENSE |
||
15 | * file that was distributed with this source code. |
||
16 | */ |
||
17 | |||
18 | namespace DivineNii\Invoker; |
||
19 | |||
20 | use DivineNii\Invoker\Exceptions\NotCallableException; |
||
21 | use Psr\Container\ContainerInterface; |
||
22 | use Psr\Container\NotFoundExceptionInterface; |
||
23 | |||
24 | /** |
||
25 | * Resolves a callable from a container. |
||
26 | * |
||
27 | * @author Matthieu Napoli <[email protected]> |
||
28 | * @author Divine Niiquaye Ibok <[email protected]> |
||
29 | */ |
||
30 | class CallableResolver |
||
31 | { |
||
32 | public const CALLABLE_PATTERN = '#^([^\:]+)(\:|\@)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$#'; |
||
33 | |||
34 | /** |
||
35 | * @var null|ContainerInterface |
||
36 | */ |
||
37 | private $container; |
||
38 | |||
39 | 82 | public function __construct(?ContainerInterface $container = null) |
|
40 | { |
||
41 | 82 | $this->container = $container; |
|
42 | 82 | } |
|
43 | |||
44 | /** |
||
45 | * Resolve the given callable into a real PHP callable. |
||
46 | * |
||
47 | * @param array<mixed,string>|callable|object|string $callable |
||
48 | * |
||
49 | * @throws NotCallableException |
||
50 | * |
||
51 | * @return callable real PHP callable |
||
52 | */ |
||
53 | 61 | public function resolve($callable) |
|
54 | { |
||
55 | 61 | $isStaticCallToNonStaticMethod = false; |
|
56 | |||
57 | // If it's already a callable there is nothing to do |
||
58 | 61 | if (\is_callable($callable)) { |
|
59 | 25 | $isStaticCallToNonStaticMethod = $this->isStaticCallToNonStaticMethod($callable); |
|
60 | |||
61 | 25 | if (!$isStaticCallToNonStaticMethod) { |
|
62 | 25 | return $callable; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
63 | } |
||
64 | 36 | } elseif (\is_string($callable) && 1 === \preg_match(self::CALLABLE_PATTERN, $callable, $matches)) { |
|
65 | // check for callable as "class:method", and "class@method" |
||
66 | 6 | $callable = [$matches[1], $matches[3]]; |
|
67 | } |
||
68 | |||
69 | // The callable is a container entry name |
||
70 | 36 | if (\is_string($callable) && null !== $this->container) { |
|
71 | try { |
||
72 | 6 | $callable = $this->container->get($callable); |
|
73 | 2 | } catch (NotFoundExceptionInterface $e) { |
|
74 | 2 | throw NotCallableException::fromInvalidCallable($callable, true, $e); |
|
75 | } |
||
76 | } |
||
77 | |||
78 | 34 | return $this->resolveCallable($callable, $isStaticCallToNonStaticMethod); |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * @param array<mixed,string>|callable|object|string $callable |
||
83 | * @param bool $isStaticCallToNonStaticMethod |
||
84 | * |
||
85 | * @throws NotCallableException |
||
86 | * |
||
87 | * @return callable |
||
88 | */ |
||
89 | 34 | private function resolveCallable($callable, bool $isStaticCallToNonStaticMethod) |
|
90 | { |
||
91 | // Callable object or string (i.e. implementing __invoke()) |
||
92 | 34 | if ((\is_string($callable) || \is_object($callable)) && \method_exists($callable, '__invoke')) { |
|
93 | 6 | $callable = [$callable, '__invoke']; |
|
94 | } |
||
95 | |||
96 | // The callable is an array whose first item is a container entry name |
||
97 | // e.g. ['some-container-entry', 'methodToCall'] |
||
98 | 34 | if (\is_array($callable) && \is_string($callable[0])) { |
|
99 | 24 | list($class, $method) = $callable; |
|
100 | |||
101 | try { |
||
102 | 24 | if (null !== $this->container) { |
|
103 | // Replace the container entry name by the actual object |
||
104 | 13 | $class = $this->container->get($class); |
|
105 | 11 | } elseif (\class_exists($class)) { |
|
106 | 11 | $class = new $class(); |
|
107 | } |
||
108 | |||
109 | 15 | $callable = [$class, $method]; |
|
110 | 9 | } catch (\Throwable $e) { |
|
111 | 9 | if ($isStaticCallToNonStaticMethod) { |
|
112 | throw new NotCallableException(\sprintf( |
||
113 | 'Cannot call %s::%s() because %2$s() is not a static method and "%1$s" is not a valid', |
||
114 | $class, |
||
115 | $method |
||
116 | ), 0, $e); |
||
117 | } |
||
118 | |||
119 | 9 | throw NotCallableException::fromInvalidCallable($callable, null !== $this->container, $e); |
|
120 | } |
||
121 | } |
||
122 | |||
123 | 25 | if (!\is_callable($callable)) { |
|
124 | 10 | throw NotCallableException::fromInvalidCallable($callable, null !== $this->container); |
|
125 | } |
||
126 | |||
127 | // Unrecognized stuff, we let it fail later |
||
128 | 15 | return $callable; |
|
0 ignored issues
–
show
|
|||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Check if the callable represents a static call to a non-static method. |
||
133 | * |
||
134 | * @param mixed $callable |
||
135 | * |
||
136 | * @return bool |
||
137 | */ |
||
138 | 25 | private function isStaticCallToNonStaticMethod($callable) |
|
139 | { |
||
140 | 25 | if (\is_array($callable) && \is_string($callable[0])) { |
|
141 | 2 | list($class, $method) = $callable; |
|
142 | 2 | $reflection = new \ReflectionMethod($class, $method); |
|
143 | |||
144 | 2 | return !$reflection->isStatic(); |
|
145 | } |
||
146 | |||
147 | 24 | return false; |
|
148 | } |
||
149 | } |
||
150 |