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 b582774

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

18 files changed

+722
-69
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/RequestMatcher.php
+80-67Lines changed: 80 additions & 67 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-
25-
/**
26-
* @var string[]
27-
*/
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-
4029
/**
41-
* @var string[]
30+
* @var RequestMatcherInterface[]
4231
*/
43-
private array $schemes = [];
32+
private array $matchers = [];
4433

4534
/**
4635
* @param string|string[]|null $methods
@@ -56,27 +45,56 @@ public function __construct(string $path = null, string $host = null, string|arr
5645
$this->matchScheme($schemes);
5746
$this->matchPort($port);
5847

59-
foreach ($attributes as $k => $v) {
60-
$this->matchAttribute($k, $v);
48+
if ($attributes) {
49+
foreach ($attributes as $k => $v) {
50+
$this->matchAttribute($k, $v);
51+
}
6152
}
6253
}
6354

55+
/**
56+
* @return $this
57+
*/
58+
public function add(RequestMatcherInterface $matcher): static
59+
{
60+
$this->matchers[$matcher::class] = $matcher;
61+
62+
return $this;
63+
}
64+
65+
public function remove(string $class)
66+
{
67+
unset($this->matchers[$class]);
68+
}
69+
6470
/**
6571
* Adds a check for the HTTP scheme.
6672
*
6773
* @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
6874
*/
6975
public function matchScheme(string|array|null $scheme)
7076
{
71-
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
77+
if (null === $scheme) {
78+
$this->remove(SchemeRequestMatcher::class);
79+
80+
return;
81+
}
82+
83+
$this->add(new SchemeRequestMatcher($scheme));
7284
}
7385

7486
/**
7587
* Adds a check for the URL host name.
7688
*/
7789
public function matchHost(?string $regexp)
7890
{
79-
$this->host = $regexp;
91+
if (null === $regexp) {
92+
$this->remove(HostRequestMatcher::class);
93+
94+
return;
95+
}
96+
97+
$this->add(new HostRequestMatcher($regexp));
8098
}
8199

82100
/**
@@ -86,15 +104,27 @@ public function matchHost(?string $regexp)
86104
*/
87105
public function matchPort(?int $port)
88106
{
89-
$this->port = $port;
107+
if (null === $port) {
108+
$this->remove(PortRequestMatcher::class);
109+
110+
return;
111+
}
112+
113+
$this->add(new PortRequestMatcher($port));
90114
}
91115

92116
/**
93117
* Adds a check for the URL path info.
94118
*/
95119
public function matchPath(?string $regexp)
96120
{
97-
$this->path = $regexp;
121+
if (null === $regexp) {
122+
$this->remove(PathRequestMatcher::class);
123+
124+
return;
125+
}
126+
127+
$this->add(new PathRequestMatcher($regexp));
98128
}
99129

100130
/**
@@ -114,69 +144,52 @@ public function matchIp(string $ip)
114144
*/
115145
public function matchIps(string|array|null $ips)
116146
{
117-
$ips = null !== $ips ? (array) $ips : [];
147+
if (null === $ips) {
148+
$this->remove(IpsRequestMatcher::class);
149+
150+
return;
151+
}
118152

119-
$this->ips = array_reduce($ips, static function (array $ips, string $ip) {
120-
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
121-
}, []);
153+
$this->add(new IpsRequestMatcher($ips));
122154
}
123155

124156
/**
125157
* Adds a check for the HTTP method.
126158
*
127-
* @param string|string[]|null $method An HTTP method or an array of HTTP methods
159+
* @param string|string[]|null $method
128160
*/
129161
public function matchMethod(string|array|null $method)
130162
{
131-
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
163+
if (null === $method) {
164+
$this->remove(MethodRequestMatcher::class);
165+
166+
return;
167+
}
168+
169+
$this->add(new MethodRequestMatcher($method));
132170
}
133171

134172
/**
135173
* Adds a check for request attribute.
136174
*/
137175
public function matchAttribute(string $key, string $regexp)
138176
{
139-
$this->attributes[$key] = $regexp;
177+
if (!isset($this->matchers[AttributesRequestMatcher::class])) {
178+
$this->matchers[AttributesRequestMatcher::class] = new AttributesRequestMatcher([]);
179+
}
180+
181+
$this->matchers[AttributesRequestMatcher::class]->addAttributeCheck($key, $regexp);
182+
140183
}
141184

142185
public function matches(Request $request): bool
143186
{
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)) {
187+
foreach ($this->matchers as $matcher) {
188+
if (!$matcher->matches($request)) {
155189
return false;
156190
}
157-
if (!preg_match('{'.$pattern.'}', $requestAttribute)) {
158-
return false;
159-
}
160-
}
161-
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;
176191
}
177192

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);
193+
return true;
181194
}
182195
}
+52Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
public function __construct(private array $regexps)
25+
{
26+
}
27+
28+
/**
29+
* @return $this
30+
*/
31+
public function addAttributeCheck(string $key, string $regexp): static
32+
{
33+
$this->regexps[$key] = $regexp;
34+
35+
return $this;
36+
}
37+
38+
public function matches(Request $request): bool
39+
{
40+
foreach ($this->regexps as $key => $regexp) {
41+
$attribute = $request->attributes->get($key);
42+
if (!\is_string($attribute)) {
43+
return false;
44+
}
45+
if (!preg_match('{'.$regexp.'}', $attribute)) {
46+
return false;
47+
}
48+
}
49+
50+
return true;
51+
}
52+
}
+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+
}
+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
/**
26+
* @var string[]
27+
*/
28+
private array $ips = [];
29+
30+
/**
31+
* @param string[]|string $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
32+
*/
33+
public function __construct(array|string $ips)
34+
{
35+
$this->ips = array_reduce((array) $ips, static function (array $ips, string $ip) {
36+
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
37+
}, []);
38+
}
39+
40+
public function matches(Request $request): bool
41+
{
42+
return IpUtils::checkIp($request->getClientIp() ?? '', $this->ips);
43+
}
44+
}
+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.