Issues (3)

src/CallableResolver.php (2 issues)

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
The expression return $callable also could return the type object which is incompatible with the documented return type callable.
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
Bug Best Practice introduced by
The expression return $callable also could return the type object which is incompatible with the documented return type callable.
Loading history...
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