Matcher::compareUri()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 3
rs 10
1
<?php
2
3
namespace MiladRahimi\PhpRouter\Dispatching;
4
5
use MiladRahimi\PhpRouter\Exceptions\RouteNotFoundException;
6
use MiladRahimi\PhpRouter\Routing\Route;
7
use MiladRahimi\PhpRouter\Routing\Repository;
8
use Psr\Http\Message\ServerRequestInterface;
9
10
/**
11
 * It finds an appropriate route for HTTP requests
12
 */
13
class Matcher
14
{
15
    private Repository $repository;
16
17
    public function __construct(Repository $repository)
18
    {
19
        $this->repository = $repository;
20
    }
21
22
    /**
23
     * Find the right route for the given request and defined patterns
24
     *
25
     * @param ServerRequestInterface $request
26
     * @param string[] $patterns
27
     * @return Route
28
     * @throws RouteNotFoundException
29
     */
30
    public function find(ServerRequestInterface $request, array $patterns): Route
31
    {
32
        foreach ($this->repository->findByMethod($request->getMethod()) as $route) {
33
            $parameters = [];
34
35
            if ($this->compare($route, $request, $parameters, $patterns)) {
36
                $route->setUri($request->getUri()->getPath());
37
                $route->setParameters($this->pruneRouteParameters($parameters));
38
39
                return $route;
40
            }
41
        }
42
43
        throw new RouteNotFoundException();
44
    }
45
46
    /**
47
     * Prune route parameters (remove unnecessary parameters)
48
     *
49
     * @param string[] $parameters
50
     * @return string[]
51
     */
52
    private function pruneRouteParameters(array $parameters): array
53
    {
54
        return array_filter($parameters, function ($value, $name) {
55
            return is_numeric($name) === false;
56
        }, ARRAY_FILTER_USE_BOTH);
57
    }
58
59
    /**
60
     * Compare the route with the given HTTP request
61
     *
62
     * @param Route $route
63
     * @param ServerRequestInterface $request
64
     * @param string[] $parameters
65
     * @param string[] $patterns
66
     * @return bool
67
     */
68
    private function compare(Route $route, ServerRequestInterface $request, array &$parameters, array $patterns): bool
69
    {
70
        return (
71
            $this->compareDomain($route->getDomain(), $request->getUri()->getHost()) &&
72
            $this->compareUri($route->getPath(), $request->getUri()->getPath(), $parameters, $patterns)
73
        );
74
    }
75
76
    /**
77
     * Check if given request domain matches given route domain
78
     *
79
     * @param string|null $routeDomain
80
     * @param string $requestDomain
81
     * @return bool
82
     */
83
    private function compareDomain(?string $routeDomain, string $requestDomain): bool
84
    {
85
        return !$routeDomain || preg_match('@^' . $routeDomain . '$@', $requestDomain);
86
    }
87
88
    /**
89
     * Check if given request uri matches given uri method
90
     *
91
     * @param string $path
92
     * @param string $uri
93
     * @param string[] $parameters
94
     * @param string[] $patterns
95
     * @return bool
96
     */
97
    private function compareUri(string $path, string $uri, array &$parameters, array $patterns): bool
98
    {
99
        return preg_match('@^' . $this->regexUri($path, $patterns) . '$@', $uri, $parameters);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('@^' ....$@', $uri, $parameters) returns the type integer which is incompatible with the type-hinted return boolean.
Loading history...
100
    }
101
102
    /**
103
     * Convert route to regex
104
     *
105
     * @param string $path
106
     * @param string[] $patterns
107
     * @return string
108
     */
109
    private function regexUri(string $path, array $patterns): string
110
    {
111
        return preg_replace_callback('@{([^}]+)}@', function (array $match) use ($patterns) {
112
            return $this->regexParameter($match[1], $patterns);
113
        }, $path);
114
    }
115
116
    /**
117
     * Convert route parameter to regex
118
     *
119
     * @param string $name
120
     * @param array $patterns
121
     * @return string
122
     */
123
    private function regexParameter(string $name, array $patterns): string
124
    {
125
        if ($name[-1] == '?') {
126
            $name = substr($name, 0, -1);
127
            $suffix = '?';
128
        } else {
129
            $suffix = '';
130
        }
131
132
        $pattern = $patterns[$name] ?? '[^/]+';
133
134
        return '(?<' . $name . '>' . $pattern . ')' . $suffix;
135
    }
136
}
137