Test Failed
Push — master ( 4cb417...df4ff7 )
by Divine Niiquaye
10:45
created

CallableResolver::resolveCallable()   B

Complexity

Conditions 11
Paths 28

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 11

Importance

Changes 0
Metric Value
cc 11
eloc 21
c 0
b 0
f 0
nc 28
nop 2
dl 0
loc 40
ccs 16
cts 16
cp 1
crap 11
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    public function __construct(?ContainerInterface $container = null)
42
    {
43 72
        $this->container = $container;
44
    }
45 72
46 72
    /**
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
    public function resolve($callable)
56
    {
57 52
        $isStaticCallToNonStaticMethod = false;
58
59
        // If it's already a callable there is nothing to do
60 52
        if (\is_callable($callable)) {
61 11
            $isStaticCallToNonStaticMethod = $this->isStaticCallToNonStaticMethod($callable);
62
63
            if (!$isStaticCallToNonStaticMethod) {
64 41
                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 4
        } elseif (\is_string($callable) && 1 === \preg_match(self::CALLABLE_PATTERN, $callable, $matches)) {
67
            // check for callable as "class:method", and "class@method"
68
            $callable = [$matches[1], $matches[3]];
69
        }
70 41
71
        // The callable is a container entry name
72 6
        if (\is_string($callable) && null !== $this->container) {
73 3
            try {
74 2
                $callable = $this->container->get($callable);
75 1
            } catch (NotFoundExceptionInterface $e) {
76
                throw NotCallableException::fromInvalidCallable($callable, true, $e);
77
            }
78
        }
79
80 40
        return $this->resolveCallable($callable, $isStaticCallToNonStaticMethod);
81
    }
82
83 35
    /**
84 8
     * @param array<mixed,string>|callable|object|string $callable
85
     * @param bool                                       $isStaticCallToNonStaticMethod
86
     *
87 34
     * @throws NotCallableException
88 11
     *
89
     * @return callable
90
     */
91 23
    private function resolveCallable($callable, bool $isStaticCallToNonStaticMethod)
92
    {
93
        // Callable object or string (i.e. implementing __invoke())
94
        if ((\is_string($callable) || \is_object($callable)) && \method_exists($callable, '__invoke')) {
95
            $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
        if (\is_array($callable) && \is_string($callable[0])) {
101 40
            list($class, $method) = $callable;
102
103 40
            try {
104
                if (null !== $this->container) {
105
                    // Replace the container entry name by the actual object
106 40
                    $class = $this->container->get($class);
107 21
                } elseif (\class_exists($class)) {
108
                    $class = new $class();
109 21
                }
110 13
111
                $callable = [$class, $method];
112
            } catch (Throwable $e) {
113
                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 28
                        $class,
117
                        $method
118 19
                    ), 0, $e);
119
                }
120 10
121
                throw NotCallableException::fromInvalidCallable($callable, null !== $this->container, $e);
122
            }
123 14
        }
124 9
125
        if (!\is_callable($callable)) {
126
            throw NotCallableException::fromInvalidCallable($callable, null !== $this->container);
127 13
        }
128 6
129 6
        // Unrecognized stuff, we let it fail later
130 1
        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 5
    /**
134 2
     * Check if the callable represents a static call to a non-static method.
135 1
     *
136 1
     * @param mixed $callable
137 1
     *
138 1
     * @return bool
139
     */
140
    private function isStaticCallToNonStaticMethod($callable)
141
    {
142 1
        if (\is_array($callable) && \is_string($callable[0])) {
143 1
            list($class, $method) = $callable;
144 1
            $reflection           = new ReflectionMethod($class, $method);
145 1
146
            return !$reflection->isStatic();
147
        }
148
149 3
        return false;
150
    }
151
}
152