Passed
Push — master ( df4ff7...e740a4 )
by Divine Niiquaye
01:54
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 12.6332

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 21
cp 0.7619
crap 12.6332
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 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