Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 33f97a3

Browse filesBrowse files
committed
[HttpFoundation] Extract request matchers for better reusability
1 parent 58117d7 commit 33f97a3
Copy full SHA for 33f97a3

18 files changed

+725
-67
lines changed

‎src/Symfony/Component/HttpFoundation/RequestMatcher.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/RequestMatcher.php
+71-65Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,25 @@
1111

1212
namespace Symfony\Component\HttpFoundation;
1313

14+
use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher;
15+
use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
16+
use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher;
17+
use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher;
18+
use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher;
19+
use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher;
20+
use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher;
21+
1422
/**
1523
* RequestMatcher compares a pre-defined set of checks against a Request instance.
1624
*
1725
* @author Fabien Potencier <fabien@symfony.com>
1826
*/
1927
class RequestMatcher implements RequestMatcherInterface
2028
{
21-
private ?string $path = null;
22-
private ?string $host = null;
23-
private ?int $port = null;
24-
2529
/**
26-
* @var string[]
30+
* @var RequestMatcherInterface[]
2731
*/
28-
private array $methods = [];
29-
30-
/**
31-
* @var string[]
32-
*/
33-
private array $ips = [];
34-
35-
/**
36-
* @var string[]
37-
*/
38-
private array $attributes = [];
39-
40-
/**
41-
* @var string[]
42-
*/
43-
private array $schemes = [];
32+
private array $matchers = [];
4433

4534
/**
4635
* @param string|string[]|null $methods
@@ -61,22 +50,49 @@ public function __construct(string $path = null, string $host = null, string|arr
6150
}
6251
}
6352

53+
/**
54+
* @return $this
55+
*/
56+
public function add(RequestMatcherInterface $matcher): static
57+
{
58+
$this->matchers[$matcher::class] = $matcher;
59+
60+
return $this;
61+
}
62+
63+
public function remove(string $class)
64+
{
65+
unset($this->matchers[$class]);
66+
}
67+
6468
/**
6569
* Adds a check for the HTTP scheme.
6670
*
6771
* @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
6872
*/
6973
public function matchScheme(string|array|null $scheme)
7074
{
71-
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
75+
if (null === $scheme || !count($scheme)) {
76+
$this->remove(SchemeRequestMatcher::class);
77+
78+
return;
79+
}
80+
81+
$this->add(new SchemeRequestMatcher($scheme));
7282
}
7383

7484
/**
7585
* Adds a check for the URL host name.
7686
*/
7787
public function matchHost(?string $regexp)
7888
{
79-
$this->host = $regexp;
89+
if (null === $regexp) {
90+
$this->remove(HostRequestMatcher::class);
91+
92+
return;
93+
}
94+
95+
$this->add(new HostRequestMatcher($regexp));
8096
}
8197

8298
/**
@@ -86,15 +102,27 @@ public function matchHost(?string $regexp)
86102
*/
87103
public function matchPort(?int $port)
88104
{
89-
$this->port = $port;
105+
if (null === $port) {
106+
$this->remove(PortRequestMatcher::class);
107+
108+
return;
109+
}
110+
111+
$this->add(new PortRequestMatcher($port));
90112
}
91113

92114
/**
93115
* Adds a check for the URL path info.
94116
*/
95117
public function matchPath(?string $regexp)
96118
{
97-
$this->path = $regexp;
119+
if (null === $regexp) {
120+
$this->remove(PathRequestMatcher::class);
121+
122+
return;
123+
}
124+
125+
$this->add(new PathRequestMatcher($regexp));
98126
}
99127

100128
/**
@@ -114,69 +142,47 @@ public function matchIp(string $ip)
114142
*/
115143
public function matchIps(string|array|null $ips)
116144
{
117-
$ips = null !== $ips ? (array) $ips : [];
145+
if (null === $ips || !count($ips)) {
146+
$this->remove(IpsRequestMatcher::class);
118147

119-
$this->ips = array_reduce($ips, static function (array $ips, string $ip) {
120-
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
121-
}, []);
148+
return;
149+
}
150+
151+
$this->add(new IpsRequestMatcher($ips));
122152
}
123153

124154
/**
125155
* Adds a check for the HTTP method.
126156
*
127-
* @param string|string[]|null $method An HTTP method or an array of HTTP methods
157+
* @param string|string[]|null $method
128158
*/
129159
public function matchMethod(string|array|null $method)
130160
{
131-
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
161+
if (null === $method || !count($method)) {
162+
$this->remove(MethodRequestMatcher::class);
163+
164+
return;
165+
}
166+
167+
$this->add(new MethodRequestMatcher($method));
132168
}
133169

134170
/**
135171
* Adds a check for request attribute.
136172
*/
137173
public function matchAttribute(string $key, string $regexp)
138174
{
139-
$this->attributes[$key] = $regexp;
175+
($this->matchers[AttributesRequestMatcher::class] ??= new AttributesRequestMatcher([]))->addAttributeCheck($key, $regexp);
140176
}
141177

142178
public function matches(Request $request): bool
143179
{
144-
if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) {
145-
return false;
146-
}
147-
148-
if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) {
149-
return false;
150-
}
151-
152-
foreach ($this->attributes as $key => $pattern) {
153-
$requestAttribute = $request->attributes->get($key);
154-
if (!\is_string($requestAttribute)) {
155-
return false;
156-
}
157-
if (!preg_match('{'.$pattern.'}', $requestAttribute)) {
180+
foreach ($this->matchers as $matcher) {
181+
if (!$matcher->matches($request)) {
158182
return false;
159183
}
160184
}
161185

162-
if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
163-
return false;
164-
}
165-
166-
if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
167-
return false;
168-
}
169-
170-
if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) {
171-
return false;
172-
}
173-
174-
if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) {
175-
return true;
176-
}
177-
178-
// Note to future implementors: add additional checks above the
179-
// foreach above or else your check might not be run!
180-
return 0 === \count($this->ips);
186+
return true;
181187
}
182188
}
+55Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
/**
18+
* Checks the Request attributes matches all regular expressions.
19+
*
20+
* @author Fabien Potencier <fabien@symfony.com>
21+
*/
22+
class AttributesRequestMatcher implements RequestMatcherInterface
23+
{
24+
/**
25+
* @param array<string, string> $regexps
26+
*/
27+
public function __construct(private array $regexps)
28+
{
29+
}
30+
31+
/**
32+
* @return $this
33+
*/
34+
public function addAttributeCheck(string $key, string $regexp): static
35+
{
36+
$this->regexps[$key] = $regexp;
37+
38+
return $this;
39+
}
40+
41+
public function matches(Request $request): bool
42+
{
43+
foreach ($this->regexps as $key => $regexp) {
44+
$attribute = $request->attributes->get($key);
45+
if (!\is_string($attribute)) {
46+
return false;
47+
}
48+
if (!preg_match('{'.$regexp.'}', $attribute)) {
49+
return false;
50+
}
51+
}
52+
53+
return true;
54+
}
55+
}
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
/**
18+
* Checks the Request URL host name matches a regular expression.
19+
*
20+
* @author Fabien Potencier <fabien@symfony.com>
21+
*/
22+
class HostRequestMatcher implements RequestMatcherInterface
23+
{
24+
public function __construct(private string $regexp)
25+
{
26+
}
27+
28+
public function matches(Request $request): bool
29+
{
30+
return preg_match('{'.$this->regexp.'}i', $request->getHost());
31+
}
32+
}
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\IpUtils;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
17+
18+
/**
19+
* Checks the client IP of a Request.
20+
*
21+
* @author Fabien Potencier <fabien@symfony.com>
22+
*/
23+
class IpsRequestMatcher implements RequestMatcherInterface
24+
{
25+
private array $ips;
26+
27+
/**
28+
* @param string[]|string $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
29+
*/
30+
public function __construct(array|string $ips)
31+
{
32+
$this->ips = array_reduce((array) $ips, static function (array $ips, string $ip) {
33+
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
34+
}, []);
35+
}
36+
37+
public function matches(Request $request): bool
38+
{
39+
if (!$this->ips) {
40+
return true;
41+
}
42+
43+
return IpUtils::checkIp($request->getClientIp() ?? '', $this->ips);
44+
}
45+
}
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Exception\JsonException;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
17+
18+
/**
19+
* Checks the Request content is valid JSON.
20+
*
21+
* @author Fabien Potencier <fabien@symfony.com>
22+
*/
23+
class IsJsonRequestMatcher implements RequestMatcherInterface
24+
{
25+
public function matches(Request $request): bool
26+
{
27+
try {
28+
$request->toArray();
29+
} catch (JsonException) {
30+
return false;
31+
}
32+
33+
return true;
34+
}
35+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.