CallableResolver::resolveCallable()   B
last analyzed

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
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