divineniiquaye /
php-invoker
| 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
Loading history...
|
|||
| 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 |