Passed
Push — master ( df4ff7...e740a4 )
by Divine Niiquaye
01:54
created

CallableResolver::isStaticCallToNonStaticMethod()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 3
rs 10
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
use ReflectionMethod;
24
use Throwable;
25
26
/**
27
 * Resolves a callable from a container.
28
 *
29
 * @author Matthieu Napoli <[email protected]>
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
class CallableResolver
33
{
34
    public const CALLABLE_PATTERN = '#^([^\:]+)(\:|\@)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$#';
35
36
    /**
37
     * @var null|ContainerInterface
38
     */
39
    private $container;
40
41 79
    public function __construct(?ContainerInterface $container = null)
42
    {
43 79
        $this->container = $container;
44 79
    }
45
46
    /**
47
     * Resolve the given callable into a real PHP callable.
48
     *
49
     * @param array<mixed,string>|callable|object|string $callable
50
     *
51
     * @throws NotCallableException
52
     *
53
     * @return callable real PHP callable
54
     */
55 58
    public function resolve($callable)
56
    {
57 58
        $isStaticCallToNonStaticMethod = false;
58
59
        // If it's already a callable there is nothing to do
60 58
        if (\is_callable($callable)) {
61 22
            $isStaticCallToNonStaticMethod = $this->isStaticCallToNonStaticMethod($callable);
62
63 22
            if (!$isStaticCallToNonStaticMethod) {
64 22
                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...
65
            }
66 36
        } elseif (\is_string($callable) && 1 === \preg_match(self::CALLABLE_PATTERN, $callable, $matches)) {
67
            // check for callable as "class:method", and "class@method"
68 6
            $callable = [$matches[1], $matches[3]];
69
        }
70
71
        // The callable is a container entry name
72 36
        if (\is_string($callable) && null !== $this->container) {
73
            try {
74 6
                $callable = $this->container->get($callable);
75 2
            } catch (NotFoundExceptionInterface $e) {
76 2
                throw NotCallableException::fromInvalidCallable($callable, true, $e);
77
            }
78
        }
79
80 34
        return $this->resolveCallable($callable, $isStaticCallToNonStaticMethod);
81
    }
82
83
    /**
84
     * @param array<mixed,string>|callable|object|string $callable
85
     * @param bool                                       $isStaticCallToNonStaticMethod
86
     *
87
     * @throws NotCallableException
88
     *
89
     * @return callable
90
     */
91 34
    private function resolveCallable($callable, bool $isStaticCallToNonStaticMethod)
92
    {
93
        // Callable object or string (i.e. implementing __invoke())
94 34
        if ((\is_string($callable) || \is_object($callable)) && \method_exists($callable, '__invoke')) {
95 6
            $callable = [$callable, '__invoke'];
96
        }
97
98
        // The callable is an array whose first item is a container entry name
99
        // e.g. ['some-container-entry', 'methodToCall']
100 34
        if (\is_array($callable) && \is_string($callable[0])) {
101 24
            list($class, $method) = $callable;
102
103
            try {
104 24
                if (null !== $this->container) {
105
                    // Replace the container entry name by the actual object
106 13
                    $class = $this->container->get($class);
107 11
                } elseif (\class_exists($class)) {
108 11
                    $class = new $class();
109
                }
110
111 15
                $callable = [$class, $method];
112 9
            } catch (Throwable $e) {
113 9
                if ($isStaticCallToNonStaticMethod) {
114
                    throw new NotCallableException(\sprintf(
115
                        'Cannot call %s::%s() because %2$s() is not a static method and "%1$s" is not a valid',
116
                        $class,
117
                        $method
118
                    ), 0, $e);
119
                }
120
121 9
                throw NotCallableException::fromInvalidCallable($callable, null !== $this->container, $e);
122
            }
123
        }
124
125 25
        if (!\is_callable($callable)) {
126 10
            throw NotCallableException::fromInvalidCallable($callable, null !== $this->container);
127
        }
128
129
        // Unrecognized stuff, we let it fail later
130 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...
131
    }
132
133
    /**
134
     * Check if the callable represents a static call to a non-static method.
135
     *
136
     * @param mixed $callable
137
     *
138
     * @return bool
139
     */
140 22
    private function isStaticCallToNonStaticMethod($callable)
141
    {
142 22
        if (\is_array($callable) && \is_string($callable[0])) {
143 2
            list($class, $method) = $callable;
144 2
            $reflection           = new ReflectionMethod($class, $method);
145
146 2
            return !$reflection->isStatic();
147
        }
148
149 21
        return false;
150
    }
151
}
152