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

phpolar / phpolar / 14632358803

24 Apr 2025 02:51AM UTC coverage: 91.411%. First build
14632358803

Pull #411

github

web-flow
Merge 2fe2feed2 into 8d98ea610
Pull Request #411: feat: add delete and put request methods

6 of 11 new or added lines in 1 file covered. (54.55%)

149 of 163 relevant lines covered (91.41%)

9.55 hits per line

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

89.36
/src/Http/RouteMap.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Phpolar\Phpolar\Http;
6

7
use Psr\Http\Message\ServerRequestInterface;
8
use Phpolar\Phpolar\Core\Routing\RouteNotRegistered;
9
use Phpolar\Phpolar\Core\Routing\RouteParamMap;
10
use Phpolar\PropertyInjectorContract\PropertyInjectorInterface;
11
use Phpolar\Routable\RoutableInterface;
12
use Phpolar\RoutableFactory\RoutableFactoryInterface;
13

14
use const Phpolar\Phpolar\Core\Routing\ROUTE_PARAM_PATTERN;
15

16
/**
17
 * Contains route paths associated with target objects.
18
 * The target objects define what should happen when a
19
 * request is mapped to the given route.
20
 */
21
class RouteMap
22
{
23
    public function __construct(private PropertyInjectorInterface $propertyInjector)
24
    {
25
    }
51✔
26

27
    /**
28
     * @var array<string,RoutableInterface|RoutableFactoryInterface> Stores actions for `DELETE` requests.
29
     */
30
    private array $registryForDelete = [];
31

32
    /**
33
     * @var array<string,RoutableInterface|RoutableFactoryInterface> Stores actions for `GET` requests.
34
     */
35
    private array $registryForGet = [];
36

37
    /**
38
     * @var array<string,RoutableInterface|RoutableFactoryInterface> Stores actions for `POST` requests.
39
     */
40
    private array $registryForPost = [];
41

42
    /**
43
     * @var array<string,RoutableInterface|RoutableFactoryInterface> Stores actions for `PUT` requests.
44
     */
45
    private array $registryForPut = [];
46

47
    private bool $containsParamRoutes = false;
48

49
    /**
50
     * Associates a request method, route and a target object.
51
     */
52
    public function add(RequestMethods $method, string $route, RoutableInterface | RoutableFactoryInterface $entry): void
53
    {
54
        $this->containsParamRoutes = $this->containsParamRoutes || preg_match(ROUTE_PARAM_PATTERN, $route) === 1;
37✔
55
        match ($method) {
56
            RequestMethods::DELETE => $this->registryForDelete[$route] = $entry,
37✔
57
            RequestMethods::GET => $this->registryForGet[$route] = $entry,
37✔
58
            RequestMethods::POST => $this->registryForPost[$route] = $entry,
18✔
NEW
59
            RequestMethods::PUT => $this->registryForPut[$route] = $entry,
×
60
        };
61
    }
62

63
    /**
64
     * Attempts to locate an object associated with a given route.
65
     *
66
     * The object defines an action that will be executed for
67
     * HTTP requests that match the associated route.
68
     *
69
     * ### Result matrix
70
     *
71
     * 1. Not Parameterized => Target Object
72
     * 1. Parameterized => Target Object w/ metadata
73
     * 1. Not Located => `RouteNotRegistered`
74
     */
75
    public function match(ServerRequestInterface $request): RoutableInterface | ResolvedRoute | RouteNotRegistered
76
    {
77
        $method = $request->getMethod();
51✔
78
        $route = $request->getUri()->getPath();
51✔
79
        return match(strtoupper($method)) {
51✔
NEW
80
            "DELETE" => $this->matchDeleteRoute($route),
×
81
            "GET" => $this->matchGetRoute($route),
26✔
82
            "POST" => $this->matchPostRoute($route),
25✔
NEW
83
            "PUT" => $this->matchPutRoute($route),
×
84
            default => new RouteNotRegistered(),
51✔
85
        };
51✔
86
    }
87

88
    private function matchDeleteRoute(string $route): RoutableInterface | ResolvedRoute | RouteNotRegistered
89
    {
NEW
90
        return $this->getInstanceFromRegistry($this->registryForDelete, $route);
×
91
    }
92

93
    private function matchGetRoute(string $route): RoutableInterface | ResolvedRoute | RouteNotRegistered
94
    {
95
        return $this->getInstanceFromRegistry($this->registryForGet, $route);
26✔
96
    }
97

98
    private function matchPostRoute(string $route): RoutableInterface | ResolvedRoute | RouteNotRegistered
99
    {
100
        return $this->getInstanceFromRegistry($this->registryForPost, $route);
25✔
101
    }
102

103
    private function matchPutRoute(string $route): RoutableInterface | ResolvedRoute | RouteNotRegistered
104
    {
NEW
105
        return $this->getInstanceFromRegistry($this->registryForPut, $route);
×
106
    }
107

108
    /**
109
     * @param array<string,RoutableInterface|RoutableFactoryInterface> $registry
110
     */
111
    private function getInstanceFromRegistry(array $registry, string $route): RoutableInterface | ResolvedRoute | RouteNotRegistered
112
    {
113
        $key = $this->containsParamRoutes === true ? $this->getMatchedParameterizedRoute($registry, $route) : $route;
51✔
114
        if ($key === false) {
51✔
115
            return new RouteNotRegistered();
8✔
116
        }
117
        if (isset($registry[$key]) === false) {
43✔
118
            return new RouteNotRegistered();
24✔
119
        }
120
        $isParamRoute = preg_match("/\{([[:alpha:]]+)\}/", $key) === 1;
19✔
121
        $targetOrFactory = $registry[$key];
19✔
122
        $target = $targetOrFactory instanceof RoutableFactoryInterface ? $targetOrFactory->createInstance() : $targetOrFactory;
19✔
123
        $this->propertyInjector->inject($target);
19✔
124
        return $isParamRoute === true ? new ResolvedRoute($target, new RouteParamMap($key, $route)) : $target;
19✔
125
    }
126

127
    /**
128
     * @param array<string,RoutableInterface|RoutableFactoryInterface> $registry
129
     * @param string $path
130
     */
131
    private function getMatchedParameterizedRoute(array $registry, string $path): string|false
132
    {
133
        // Reindex the result. See https://www.php.net/manual/en/function.array-filter.php
134
        return current(array_values(
20✔
135
            array_filter(
20✔
136
                array_keys($registry),
20✔
137
                static fn (string $registeredRoute) => self::partsMatch($registeredRoute, $path),
20✔
138
            )
20✔
139
        ));
20✔
140
    }
141

142
    private static function partsMatch(string $registeredRoute, string $path): bool
143
    {
144
        $pathParts = explode("/", ltrim($path, "/"));
20✔
145
        $routeParts = explode("/", ltrim($registeredRoute, "/"));
20✔
146
        $routePartsCnt = count($routeParts);
20✔
147
        if ($routePartsCnt !== count($pathParts)) {
20✔
148
            return false;
6✔
149
        }
150
        return count(
16✔
151
            array_filter(
16✔
152
                array_combine($routeParts, $pathParts),
16✔
153
                static fn (string $pathPart, string $routePart) => $routePart === $pathPart || preg_match(ROUTE_PARAM_PATTERN, $routePart) === 1,
16✔
154
                ARRAY_FILTER_USE_BOTH,
16✔
155
            )
16✔
156
        ) === count($routeParts);
16✔
157
    }
158
}
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