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

valkyrjaio / valkyrja / 15738627666

18 Jun 2025 04:39PM UTC coverage: 46.073% (-1.2%) from 47.32%
15738627666

push

github

MelechMizrachi
Http/Routing: Updated to use Dispatcher2. Deprecating old way of doing things. Forcing attributes.

63 of 449 new or added lines in 18 files covered. (14.03%)

30 existing lines in 8 files now uncovered.

5109 of 11089 relevant lines covered (46.07%)

15.32 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 Valkyrja\Dispatcher\Data\Contract\CallableDispatch;
18
use Valkyrja\Dispatcher\Data\Contract\ClassDispatch;
19
use Valkyrja\Http\Routing\Constant\Regex;
20
use Valkyrja\Http\Routing\Data\Contract\Parameter;
21
use Valkyrja\Http\Routing\Data\Contract\Route;
22
use Valkyrja\Http\Routing\Exception\InvalidParameterRegexException;
23
use Valkyrja\Http\Routing\Exception\InvalidRoutePathException;
24
use Valkyrja\Http\Routing\Processor\Contract\Processor as Contract;
25
use Valkyrja\Http\Routing\Support\Helpers;
26
use Valkyrja\Orm\Data\EntityCast;
27
use Valkyrja\Orm\Entity\Contract\Entity;
28

29
use function assert;
30
use function preg_match;
31

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

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

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

NEW
65
        return $route;
×
66
    }
67

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

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

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

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

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

111
            // Validate the parameter
NEW
112
            $route     = $this->processParameterEntity($route, $parameter);
×
NEW
113
            $parameter = $this->processParameterInRegex($parameter, $regex);
×
114

115
            $regex = $this->replaceParameterNameInRegex($route, $parameter, $regex);
×
116
        }
117

118
        $regex = Regex::START . $regex . Regex::END;
×
119

NEW
120
        return $route->withRegex($regex);
×
121
    }
122

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

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

139
            if (! $cast instanceof EntityCast) {
×
NEW
140
                return $route;
×
141
            }
142

143
            $entityColumn = $cast->column;
×
144

145
            if ($entityColumn !== null) {
×
146
                assert(property_exists($entity, $entityColumn));
×
147
            }
148
        }
149

NEW
150
        return $route;
×
151
    }
152

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

NEW
165
        if (! $dispatch instanceof ClassDispatch && ! $dispatch instanceof CallableDispatch) {
×
NEW
166
            return $route;
×
167
        }
168

NEW
169
        $dependencies = $dispatch->getDependencies();
×
170

171
        if ($dependencies === null || $dependencies === []) {
×
NEW
172
            return $route;
×
173
        }
174

175
        $updatedDependencies = [];
×
176

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

NEW
183
        return $route->withDispatch($dispatch->withDependencies($updatedDependencies));
×
184
    }
185

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

NEW
202
        return $parameter;
×
203
    }
204

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

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

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

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

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