• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

IlyasDeckers / ody-core / 13532154862

25 Feb 2025 10:24PM UTC coverage: 30.374% (+1.7%) from 28.706%
13532154862

push

github

web-flow
Update php.yml

544 of 1791 relevant lines covered (30.37%)

9.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.11
/src/CallableResolver.php
1
<?php
2
declare(strict_types=1);
3

4
namespace Ody\Core;
5

6
use Closure;
7
use Psr\Container\ContainerInterface;
8
use Psr\Http\Server\MiddlewareInterface;
9
use Psr\Http\Server\RequestHandlerInterface;
10
use RuntimeException;
11
use Ody\Core\Interfaces\AdvancedCallableResolverInterface;
12

13
use function class_exists;
14
use function is_array;
15
use function is_callable;
16
use function is_object;
17
use function is_string;
18
use function json_encode;
19
use function preg_match;
20
use function sprintf;
21

22
/**
23
 * @template TContainerInterface of (ContainerInterface|null)
24
 */
25
final class CallableResolver implements AdvancedCallableResolverInterface
26
{
27
    public static string $callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';
28

29
    /** @var TContainerInterface $container */
30
    private ?ContainerInterface $container;
31

32
    /**
33
     * @param TContainerInterface $container
34
     */
35
    public function __construct(?ContainerInterface $container = null)
97✔
36
    {
37
        $this->container = $container;
97✔
38
    }
39

40
    /**
41
     * {@inheritdoc}
42
     */
43
    public function resolve($toResolve): callable
×
44
    {
45
        var_dump('resolve');
×
46
        $toResolve = $this->prepareToResolve($toResolve);
×
47
        if (is_callable($toResolve)) {
×
48
            return $this->bindToContainer($toResolve);
×
49
        }
50
        $resolved = $toResolve;
×
51
        if (is_string($toResolve)) {
×
52
            $resolved = $this->resolveSlimNotation($toResolve);
×
53
            $resolved[1] ??= '__invoke';
×
54
        }
55
        $callable = $this->assertCallable($resolved, $toResolve);
×
56
        return $this->bindToContainer($callable);
×
57
    }
58

59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function resolveRoute($toResolve): callable
78✔
63
    {
64
        return $this->resolveByPredicate($toResolve, [$this, 'isRoute'], 'handle');
78✔
65
    }
66

67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function resolveMiddleware($toResolve): callable
×
71
    {
72
        return $this->resolveByPredicate($toResolve, [$this, 'isMiddleware'], 'process');
×
73
    }
74

75
    /**
76
     * @param string|callable $toResolve
77
     *
78
     * @throws RuntimeException
79
     */
80
    private function resolveByPredicate($toResolve, callable $predicate, string $defaultMethod): callable
78✔
81
    {
82
        $toResolve = $this->prepareToResolve($toResolve);
78✔
83
        if (is_callable($toResolve)) {
78✔
84
            return $this->bindToContainer($toResolve);
77✔
85
        }
86
        $resolved = $toResolve;
1✔
87
        if ($predicate($toResolve)) {
1✔
88
            $resolved = [$toResolve, $defaultMethod];
×
89
        }
90
        if (is_string($toResolve)) {
1✔
91
            [$instance, $method] = $this->resolveSlimNotation($toResolve);
1✔
92
            if ($method === null && $predicate($instance)) {
×
93
                $method = $defaultMethod;
×
94
            }
95
            $resolved = [$instance, $method ?? '__invoke'];
×
96
        }
97
        $callable = $this->assertCallable($resolved, $toResolve);
×
98
        return $this->bindToContainer($callable);
×
99
    }
100

101
    /**
102
     * @param mixed $toResolve
103
     */
104
    private function isRoute($toResolve): bool
1✔
105
    {
106
        return $toResolve instanceof RequestHandlerInterface;
1✔
107
    }
108

109
    /**
110
     * @param mixed $toResolve
111
     */
112
    private function isMiddleware($toResolve): bool
×
113
    {
114
        return $toResolve instanceof MiddlewareInterface;
×
115
    }
116

117
    /**
118
     * @throws RuntimeException
119
     *
120
     * @return array{object, string|null} [Instance, Method Name]
121
     */
122
    private function resolveSlimNotation(string $toResolve): array
1✔
123
    {
124
        /** @psalm-suppress ArgumentTypeCoercion */
125
        preg_match(CallableResolver::$callablePattern, $toResolve, $matches);
1✔
126
        [$class, $method] = $matches ? [$matches[1], $matches[2]] : [$toResolve, null];
1✔
127

128
        if ($this->container && $this->container->has($class)) {
1✔
129
            $instance = $this->container->get($class);
1✔
130
            if (!is_object($instance)) {
1✔
131
                throw new RuntimeException(sprintf('%s container entry is not an object', $class));
×
132
            }
133
        } else {
134
            if (!class_exists($class)) {
×
135
                if ($method) {
×
136
                    $class .= '::' . $method . '()';
×
137
                }
138
                throw new RuntimeException(sprintf('Callable %s does not exist', $class));
×
139
            }
140
            $instance = new $class($this->container);
×
141
        }
142

143
        if (!class_exists($class)) {
1✔
144
            if ($method) {
1✔
145
                $class .= '::' . $method . '()';
1✔
146
            }
147
            throw new RuntimeException(sprintf('Callable %s does not exist', $class));
1✔
148
        }
149
        $instance = new $class($this->container);
×
150

151
        var_dump('here');
×
152

153
        return [$instance, $method];
×
154
    }
155

156
    /**
157
     * @param mixed $resolved
158
     * @param mixed $toResolve
159
     *
160
     * @throws RuntimeException
161
     */
162
    private function assertCallable($resolved, $toResolve): callable
×
163
    {
164
        if (!is_callable($resolved)) {
×
165
            if (is_callable($toResolve) || is_object($toResolve) || is_array($toResolve)) {
×
166
                $formatedToResolve = ($toResolveJson = json_encode($toResolve)) !== false ? $toResolveJson : '';
×
167
            } else {
168
                $formatedToResolve = is_string($toResolve) ? $toResolve : '';
×
169
            }
170
            throw new RuntimeException(sprintf('%s is not resolvable', $formatedToResolve));
×
171
        }
172
        return $resolved;
×
173
    }
174

175
    private function bindToContainer(callable $callable): callable
77✔
176
    {
177
        if (is_array($callable) && $callable[0] instanceof Closure) {
77✔
178
            $callable = $callable[0];
×
179
        }
180
        if ($callable instanceof Closure) {
77✔
181
            /** @var Closure $callable */
182
            $callable = $callable->bindTo($this->container);
76✔
183
        }
184
        return $callable;
77✔
185
    }
186

187
    /**
188
     * @param string|callable $toResolve
189
     * @return string|callable
190
     */
191
    private function prepareToResolve($toResolve)
78✔
192
    {
193
        if (!is_array($toResolve)) {
78✔
194
            return $toResolve;
78✔
195
        }
196
        $candidate = $toResolve;
×
197
        $class = array_shift($candidate);
×
198
        $method = array_shift($candidate);
×
199
        if (is_string($class) && is_string($method)) {
×
200
            return $class . ':' . $method;
×
201
        }
202
        return $toResolve;
×
203
    }
204
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc