Skip to content

Commit 846a7af

Browse files
committed
Add regex wrapper
1 parent 845ead4 commit 846a7af

File tree

5 files changed

+556
-6
lines changed

5 files changed

+556
-6
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Handles common string operations in PHP applications:
1010
- [Generate a random string](#generate-a-random-string) with a set of characters
1111
- [Condense a number into a string](#condense-a-string-into-a-number), and convert/expand a string into a number
1212
- [Process an URL](#url) and modify it in a safe way (convert to relative URL, change parts of it, etc.)
13+
- [Regex wrapper](#regex-wrapper) to better handle type hints and errors with the most common regex functions
1314

1415
Filter a string
1516
---------------
@@ -318,4 +319,9 @@ The URL class accepts an URL in the constructor and then lets you get or change
318319
- Change scheme, host, path and query string
319320
- Replace query string variables, or add/remove them
320321

321-
This can be used to easily build or change your URLs, or to sanitize certain parts of a given URL, for example when redirecting: use the relative URL instead of the absolute URL to avoid malicious redirecting to somewhere outside of your control.
322+
This can be used to easily build or change your URLs, or to sanitize certain parts of a given URL, for example when redirecting: use the relative URL instead of the absolute URL to avoid malicious redirecting to somewhere outside of your control.
323+
324+
Regex wrapper
325+
-------------
326+
327+
Using the built-in `preg_match`, `preg_match_all`, `preg_replace` and `preg_replace_callback` functions often makes code less readable and harder to understand for static analyzers. `Squirrel\Strings\Regex` wraps the basic functionality of these preg functions and throws a `Squirrel\Strings\Exception\RegexException` if anything goes wrong.

src/Common/RegexExceptionTrait.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ trait RegexExceptionTrait
1616
{
1717
private function generateRegexException(): \Throwable
1818
{
19-
/**
20-
* @var array $constants
21-
*/
2219
$constants = \get_defined_constants(true)['pcre'];
2320

2421
// Find the regex error code

src/Regex.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
namespace Squirrel\Strings;
4+
5+
use Squirrel\Debug\Debug;
6+
use Squirrel\Strings\Exception\RegexException;
7+
8+
/**
9+
* Wrapper around common regex functions for better type hints and error handling
10+
*/
11+
final class Regex
12+
{
13+
/**
14+
* @pure
15+
*
16+
* @throws RegexException
17+
*/
18+
public static function isMatch(
19+
string $pattern,
20+
string $subject,
21+
int $offset = 0,
22+
): bool {
23+
$result = @\preg_match($pattern, $subject, offset: $offset);
24+
25+
if (!\is_int($result)) {
26+
throw self::generateRegexException();
27+
}
28+
29+
return \boolval($result);
30+
}
31+
32+
/**
33+
* @pure
34+
*
35+
* @throws RegexException
36+
*/
37+
public static function getMatches(
38+
string $pattern,
39+
string $subject,
40+
int $flags = 0,
41+
int $offset = 0,
42+
): ?array {
43+
$result = @\preg_match_all($pattern, $subject, $matches, $flags, $offset);
44+
45+
if (!\is_int($result)) {
46+
throw self::generateRegexException();
47+
}
48+
49+
if ($result === 0) {
50+
return null;
51+
}
52+
53+
return $matches;
54+
}
55+
56+
/**
57+
* @pure
58+
*
59+
* @throws RegexException
60+
*/
61+
public static function replace(
62+
string|array $pattern,
63+
string|array $replacement,
64+
string $subject,
65+
int $limit = -1,
66+
): string {
67+
$result = @\preg_replace($pattern, $replacement, $subject, $limit);
68+
69+
if (!\is_string($result)) {
70+
throw self::generateRegexException();
71+
}
72+
73+
return $result;
74+
}
75+
76+
/**
77+
* @pure
78+
*
79+
* @throws RegexException
80+
*/
81+
public static function replaceArray(
82+
string|array $pattern,
83+
string|array $replacement,
84+
array $subject,
85+
int $limit = -1,
86+
): array {
87+
$result = [];
88+
89+
foreach ($subject as $key => $singleSubject) {
90+
$result[$key] = self::replace($pattern, $replacement, $singleSubject, $limit);
91+
}
92+
93+
return $result;
94+
}
95+
96+
/**
97+
* @throws RegexException
98+
*/
99+
public static function replaceWithCallback(
100+
string|array $pattern,
101+
callable $callback,
102+
string $subject,
103+
int $limit = -1,
104+
int $flags = 0,
105+
): string {
106+
$result = @\preg_replace_callback($pattern, $callback, $subject, $limit, flags: $flags);
107+
108+
if (!\is_string($result)) {
109+
throw self::generateRegexException();
110+
}
111+
112+
return $result;
113+
}
114+
115+
/**
116+
* @throws RegexException
117+
*/
118+
public static function replaceArrayWithCallback(
119+
string|array $pattern,
120+
callable $callback,
121+
array $subject,
122+
int $limit = -1,
123+
int $flags = 0,
124+
): array {
125+
$result = [];
126+
127+
foreach ($subject as $key => $singleSubject) {
128+
$result[$key] = self::replaceWithCallback($pattern, $callback, $singleSubject, $limit, flags: $flags);
129+
}
130+
131+
return $result;
132+
}
133+
134+
private static function generateRegexException(): \Throwable
135+
{
136+
$constants = \get_defined_constants(true)['pcre'];
137+
138+
// Find the regex error code
139+
foreach ($constants as $name => $value) {
140+
if ($value === \preg_last_error()) {
141+
$error = $name;
142+
}
143+
}
144+
145+
return Debug::createException(RegexException::class, [
146+
self::class,
147+
], 'Regex error in ' . __CLASS__ . ': ' . ( $error ?? 'Unrecognized error code' ));
148+
}
149+
}

0 commit comments

Comments
 (0)