• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valkyrjaio / valkyrja / 16185661246

10 Jul 2025 03:48AM UTC coverage: 43.558% (-0.2%) from 43.747%
16185661246

push

github

MelechMizrachi
Http Routing: Updating ListCommand.

0 of 21 new or added lines in 1 file covered. (0.0%)

2913 existing lines in 212 files now uncovered.

3925 of 9011 relevant lines covered (43.56%)

11.07 hits per line

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

0.0
/src/Valkyrja/Http/Routing/Processor/Processor.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <melechmizrachi@gmail.com>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace Valkyrja\Http\Routing\Processor;
15

16
use InvalidArgumentException;
17
use Override;
18
use Valkyrja\Dispatcher\Data\Contract\CallableDispatch;
19
use Valkyrja\Dispatcher\Data\Contract\ClassDispatch;
20
use Valkyrja\Http\Routing\Constant\Regex;
21
use Valkyrja\Http\Routing\Data\Contract\Parameter;
22
use Valkyrja\Http\Routing\Data\Contract\Route;
23
use Valkyrja\Http\Routing\Exception\InvalidParameterRegexException;
24
use Valkyrja\Http\Routing\Exception\InvalidRoutePathException;
25
use Valkyrja\Http\Routing\Processor\Contract\Processor as Contract;
26
use Valkyrja\Http\Routing\Support\Helpers;
27
use Valkyrja\Orm\Data\EntityCast;
28
use Valkyrja\Orm\Entity\Contract\Entity;
29

30
use function assert;
31
use function preg_match;
32

33
/**
34
 * Class Processor.
35
 *
36
 * @author Melech Mizrachi
37
 */
38
class Processor implements Contract
39
{
40
    /**
41
     * Process a route.
42
     *
43
     * @param Route $route The route
44
     *
45
     * @throws InvalidRoutePathException
46
     *
47
     * @return Route
48
     */
49
    #[Override]
50
    public function route(Route $route): Route
51
    {
52
        // Verify the route
UNCOV
53
        $this->verifyRoute($route);
×
54

55
        // Set the id to the spl_object_id of the route
56
        // $route->setId((string) spl_object_id($route));
57
        // Set the id to an md5 hash of the route
58
        // $route->setId(md5(Arr::toString($route->asArray())));
59
        // Set the path to the validated cleaned path (/some/path)
UNCOV
60
        $route = $route->withPath(Helpers::trimPath($route->getPath()));
×
61

62
        // If this is a dynamic route
UNCOV
63
        if (str_contains($route->getPath(), '{')) {
×
UNCOV
64
            $route = $this->modifyRegex($route);
×
65
        }
66

UNCOV
67
        return $route;
×
68
    }
69

70
    /**
71
     * Verify a route.
72
     *
73
     * @param Route $route The route
74
     *
75
     * @return void
76
     */
77
    protected function verifyRoute(Route $route): void
78
    {
UNCOV
79
        if (! $route->getPath()) {
×
UNCOV
80
            throw new InvalidArgumentException('Invalid path defined in route.');
×
81
        }
82
    }
83

84
    /**
85
     * Create the regex for a route.
86
     *
87
     * @param Route $route The route
88
     *
89
     * @throws InvalidRoutePathException
90
     *
91
     * @return Route
92
     */
93
    protected function modifyRegex(Route $route): Route
94
    {
95
        // If the regex has already been set then don't do anything
UNCOV
96
        if (($regex = $route->getRegex()) !== null) {
×
UNCOV
97
            return $route;
×
98
        }
99

100
        // Replace all slashes with \/
UNCOV
101
        $regex = str_replace('/', Regex::PATH, $route->getPath());
×
102

103
        // Iterate through the route's parameters
UNCOV
104
        foreach ($route->getParameters() as $parameter) {
×
105
            $parameterRegex = $parameter->getRegex();
×
106

107
            if (@preg_match(Regex::START . $parameterRegex . Regex::END, '') === false) {
×
108
                throw new InvalidParameterRegexException(
×
UNCOV
109
                    message: "Invalid parameter regex of `$parameterRegex` provided for " . $parameter->getName()
×
UNCOV
110
                );
×
111
            }
112

113
            // Validate the parameter
UNCOV
114
            $route     = $this->processParameterEntity($route, $parameter);
×
115
            $parameter = $this->processParameterInRegex($parameter, $regex);
×
116

UNCOV
117
            $regex = $this->replaceParameterNameInRegex($route, $parameter, $regex);
×
118
        }
119

120
        $regex = Regex::START . $regex . Regex::END;
×
121

UNCOV
122
        return $route->withRegex($regex);
×
123
    }
124

125
    /**
126
     * Validate the parameter entity.
127
     *
128
     * @param Route     $route     The route
129
     * @param Parameter $parameter The parameter
130
     *
131
     * @return Route
132
     */
133
    protected function processParameterEntity(Route $route, Parameter $parameter): Route
134
    {
UNCOV
135
        $cast   = $parameter->getCast();
×
136
        $entity = $cast->type ?? null;
×
137

UNCOV
138
        if ($entity !== null && is_a($entity, Entity::class, true)) {
×
139
            $route = $this->removeEntityFromDependencies($route, $entity);
×
140

UNCOV
141
            if (! $cast instanceof EntityCast) {
×
UNCOV
142
                return $route;
×
143
            }
144

145
            $entityColumn = $cast->column;
×
146

UNCOV
147
            if ($entityColumn !== null) {
×
UNCOV
148
                assert(property_exists($entity, $entityColumn));
×
149
            }
150
        }
151

UNCOV
152
        return $route;
×
153
    }
154

155
    /**
156
     * Remove the entity from the route's dependencies list.
157
     *
158
     * @param Route                $route      The route
159
     * @param class-string<Entity> $entityName The entity class name
160
     *
161
     * @return Route
162
     */
163
    protected function removeEntityFromDependencies(Route $route, string $entityName): Route
164
    {
165
        $dispatch = $route->getDispatch();
×
166

UNCOV
167
        if (! $dispatch instanceof ClassDispatch && ! $dispatch instanceof CallableDispatch) {
×
UNCOV
168
            return $route;
×
169
        }
170

171
        $dependencies = $dispatch->getDependencies();
×
172

UNCOV
173
        if ($dependencies === null || $dependencies === []) {
×
UNCOV
174
            return $route;
×
175
        }
176

177
        $updatedDependencies = [];
×
178

179
        foreach ($dependencies as $dependency) {
×
UNCOV
180
            if ($dependency !== $entityName) {
×
UNCOV
181
                $updatedDependencies[] = $dependency;
×
182
            }
183
        }
184

UNCOV
185
        return $route->withDispatch($dispatch->withDependencies($updatedDependencies));
×
186
    }
187

188
    /**
189
     * Validate the parameter name exists in the regex.
190
     *
191
     * @param Parameter $parameter The parameter
192
     * @param string    $regex     The regex
193
     *
194
     * @return Parameter
195
     */
196
    protected function processParameterInRegex(Parameter $parameter, string $regex): Parameter
197
    {
198
        // If the parameter is optional or the name has a ? affixed to it
199
        if ($parameter->isOptional() || str_contains($regex, $parameter->getName() . '?')) {
×
200
            // Ensure the parameter is set to optional
UNCOV
201
            return $parameter->withIsOptional(true);
×
202
        }
203

UNCOV
204
        return $parameter;
×
205
    }
206

207
    /**
208
     * Replace the parameter name in the route's regex.
209
     *
210
     * @param Route     $route     The route
211
     * @param Parameter $parameter The parameter
212
     * @param string    $regex     The regex
213
     *
214
     * @throws InvalidRoutePathException
215
     *
216
     * @return string
217
     */
218
    protected function replaceParameterNameInRegex(Route $route, Parameter $parameter, string $regex): string
219
    {
220
        // Get whether this parameter is optional
UNCOV
221
        $isOptional = $parameter->isOptional();
×
222

223
        // Get the replacement for this parameter's name (something like {name} or {name?}
224
        // Prepend \/ if it optional so we can replace the path slash and set it in the
225
        // regex below as a non-capture-optional group
UNCOV
226
        $nameReplacement = ($isOptional ? Regex::PATH : '')
×
UNCOV
227
            . '{' . $parameter->getName() . ($isOptional ? '?' : '') . '}';
×
228

229
        // Check if the path doesn't contain the parameter's name replacement
UNCOV
230
        if (! str_contains($regex, $nameReplacement)) {
×
UNCOV
231
            throw new InvalidRoutePathException("{$route->getPath()} is missing $nameReplacement");
×
232
        }
233

234
        // If optional we don't want to capture the / before the value
235
        $parameterRegex = ($isOptional ? Regex::START_OPTIONAL_CAPTURE_GROUP : '')
×
236
            // Start the actual value's capture group
×
237
            . (! $parameter->shouldCapture() ? Regex::START_NON_CAPTURE_GROUP : Regex::START_CAPTURE_GROUP)
×
238
            // Set the parameter's regex to match the value
×
239
            . $parameter->getRegex()
×
UNCOV
240
            // End the capture group
×
UNCOV
241
            . ($isOptional ? Regex::END_OPTIONAL_CAPTURE_GROUP : Regex::END_CAPTURE_GROUP);
×
242

243
        // Replace the {name} or \/{name?} with the finished regex
UNCOV
244
        return str_replace($nameReplacement, $parameterRegex, $regex);
×
245
    }
246
}
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