├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Exceptions └── RegexFailed.php ├── Helpers ├── Arr.php └── Str.php ├── MatchAllResult.php ├── MatchResult.php ├── Regex.php ├── RegexResult.php └── ReplaceResult.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->ignoreDotFiles(true) 10 | ->ignoreVCS(true); 11 | 12 | return (new PhpCsFixer\Config()) 13 | ->setRules([ 14 | '@PSR12' => true, 15 | 'array_syntax' => ['syntax' => 'short'], 16 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 17 | 'no_unused_imports' => true, 18 | 'not_operator_with_successor_space' => true, 19 | 'trailing_comma_in_multiline' => true, 20 | 'phpdoc_scalar' => true, 21 | 'unary_operator_spaces' => true, 22 | 'binary_operator_spaces' => true, 23 | 'blank_line_before_statement' => [ 24 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 25 | ], 26 | 'phpdoc_single_line_var_spacing' => true, 27 | 'phpdoc_var_without_name' => true, 28 | 'class_attributes_separation' => [ 29 | 'elements' => [ 30 | 'method' => 'one', 31 | ], 32 | ], 33 | 'method_argument_space' => [ 34 | 'on_multiline' => 'ensure_fully_multiline', 35 | 'keep_multiple_spaces_after_comma' => true, 36 | ], 37 | 'single_trait_insert_per_statement' => true, 38 | ]) 39 | ->setFinder($finder); -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `regex` will be documented in this file 4 | 5 | ## 3.1.1 - 2021-11-30 6 | 7 | ## What's Changed 8 | 9 | - Use nicer error messages by @Seldaek in https://github.com/spatie/regex/pull/64 10 | 11 | ## New Contributors 12 | 13 | - @Seldaek made their first contribution in https://github.com/spatie/regex/pull/64 14 | 15 | **Full Changelog**: https://github.com/spatie/regex/compare/3.1.0...3.1.1 16 | 17 | ## 3.1.0 - 2021-11-18 18 | 19 | - Add support for PHP 8.1 20 | 21 | **Full Changelog**: https://github.com/spatie/regex/compare/3.0.0...3.1.0 22 | 23 | ## 3.0.0 - 2021-10-06 24 | 25 | - implement `PREG_UNMATCHED_AS_NULL` (#61) 26 | 27 | ## 2.0.1 - 2021-04-20 28 | 29 | - small internal refactor 30 | 31 | ## 2.0.0 - 2021-03-31 32 | 33 | - require PHP 8+ 34 | - drop support for PHP 7.x 35 | - convert syntax to PHP 8 36 | - move exceptions to "Exceptions" directory to match structure of other packages 37 | 38 | ## 1.4.2 - 2020-11-04 39 | 40 | - add support for PHP 8.0 41 | - drop anything below PHP 7.3 42 | 43 | ## 1.4.1 - 2019-06-19 44 | 45 | - fix for #37 46 | 47 | ## 1.3.2 - 2018-10-29 48 | 49 | - fix determining the last preg error 50 | 51 | ## 1.3.1 - 2018-10-18 52 | 53 | - fix for PHP 7.3 54 | 55 | ## 1.3.0 - 2018-05-24 56 | 57 | - Treat named groups the same way as regular groups 58 | 59 | ## 1.2.0 - 2017-09-04 60 | 61 | - Added `resultOr` and `groupOr` methods 62 | 63 | ## 1.1.0 - 2016-09-15 64 | 65 | - Added `namedGroup` 66 | 67 | ## 1.0.0 - 2016-08-18 68 | 69 | - Initial release 70 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://supportukrainenow.org) 3 | 4 | # Making regex great again 5 | 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/regex.svg?style=flat-square)](https://packagist.org/packages/spatie/regex) 7 | ![Tests](https://github.com/spatie/regex/workflows/Tests/badge.svg) 8 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/regex.svg?style=flat-square)](https://packagist.org/packages/spatie/regex) 10 | 11 | Php's built in `preg_*` functions require some odd patterns like passing variables by reference and treating `false` or `null` values as errors. `spatie/regex` provides a cleaner interface for `preg_match`, `preg_match_all`, `preg_replace` and `preg_replace_callback`. 12 | 13 | ```php 14 | use Spatie\Regex\Regex; 15 | 16 | // Using `match` 17 | Regex::match('/a/', 'abc'); // `MatchResult` object 18 | Regex::match('/a/', 'abc')->hasMatch(); // true 19 | Regex::match('/a/', 'abc')->result(); // 'a' 20 | 21 | // Capturing groups with `match` 22 | Regex::match('/a(b)/', 'abc')->result(); // 'ab' 23 | Regex::match('/a(b)/', 'abc')->group(1); // 'b' 24 | 25 | // Setting defaults 26 | Regex::match('/a(b)/', 'xyz')->resultOr('default'); // 'default' 27 | Regex::match('/a(b)/', 'xyz')->groupOr(1, 'default'); // 'default' 28 | 29 | // Using `matchAll` 30 | Regex::matchAll('/a/', 'abcabc')->hasMatch(); // true 31 | Regex::matchAll('/a/', 'abcabc')->results(); // Array of `MatchResult` objects 32 | 33 | // Using replace 34 | Regex::replace('/a/', 'b', 'abc')->result(); // 'bbc'; 35 | Regex::replace('/a/', function (MatchResult $result) { 36 | return $result->result() . 'Hello!'; 37 | }, 'abc')->result(); // 'aHello!bc'; 38 | ``` 39 | 40 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 41 | 42 | ## Support us 43 | 44 | [](https://spatie.be/github-ad-click/regex) 45 | 46 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 47 | 48 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 49 | 50 | ## Installation 51 | 52 | You can install the package via composer: 53 | 54 | ``` bash 55 | composer require spatie/regex 56 | ``` 57 | 58 | ## Usage 59 | 60 | ### Matching a pattern once 61 | 62 | Matches a pattern on a subject. Returns a `MatchResult` object for the first match. 63 | 64 | ```php 65 | /** 66 | * @param string $pattern 67 | * @param string $subject 68 | * 69 | * @return \Spatie\Regex\MatchResult 70 | */ 71 | Regex::match(string $pattern, string $subject): MatchResult 72 | ``` 73 | 74 | #### `MatchResult::hasMatch(): bool` 75 | 76 | Checks if the pattern matches the subject. 77 | 78 | ```php 79 | Regex::match('/abc/', 'abc')->hasMatch(); // true 80 | Regex::match('/def/', 'abc')->hasMatch(); // false 81 | ``` 82 | 83 | #### `MatchResult::result(): string` 84 | 85 | Return the full match that was made. Returns `null` if no match was made. 86 | 87 | ```php 88 | Regex::match('/abc/', 'abc')->result(); // 'abc' 89 | Regex::match('/def/', 'abc')->result(); // null 90 | ``` 91 | 92 | #### `MatchResult::group(int $id): string` 93 | 94 | Return the contents of a captured group (with a 1-based index). Throws a `RegexFailed` exception if the group doesn't exist. 95 | 96 | ```php 97 | Regex::match('/a(b)c/', 'abc')->group(1); // 'b' 98 | Regex::match('/a(b)c/', 'abc')->group(2); // `RegexFailed` exception 99 | ``` 100 | 101 | ### Matching all occurences of a pattern 102 | 103 | Matches a pattern on a subject. Returns a `MatchAllResult` object containing all matches. 104 | 105 | ```php 106 | /** 107 | * @param string $pattern 108 | * @param string $subject 109 | * 110 | * @return \Spatie\Regex\MatchAllResult 111 | */ 112 | public static function matchAll(string $pattern, string $subject): MatchAllResult 113 | ``` 114 | 115 | #### `MatchAllResult::hasMatch(): bool` 116 | 117 | Checks if the pattern matches the subject. 118 | 119 | ```php 120 | Regex::matchAll('/abc/', 'abc')->hasMatch(); // true 121 | Regex::matchAll('/abc/', 'abcabc')->hasMatch(); // true 122 | Regex::matchAll('/def/', 'abc')->hasMatch(); // false 123 | ``` 124 | 125 | #### `MatchAllResult::results(): array` 126 | 127 | Returns an array of `MatchResult` objects. 128 | 129 | ```php 130 | $results = Regex::matchAll('/ab([a-z])/', 'abcabd')->results(); 131 | 132 | $results[0]->result(); // 'abc' 133 | $results[0]->group(1); // 'c' 134 | $results[1]->result(); // 'abd' 135 | $results[1]->group(1); // 'd' 136 | ``` 137 | 138 | ### Replacing a pattern in a subject 139 | 140 | Replaces a pattern in a subject. Returns a `ReplaceResult` object. 141 | 142 | ```php 143 | /** 144 | * @param string|array $pattern 145 | * @param string|array|callable $replacement 146 | * @param string|array $subject 147 | * @param int $limit 148 | * 149 | * @return \Spatie\Regex\ReplaceResult 150 | */ 151 | public static function replace($pattern, $replacement, $subject, $limit = -1): ReplaceResult 152 | ``` 153 | 154 | #### `ReplaceResult::result(): mixed` 155 | 156 | ```php 157 | Regex::replace('/a/', 'b', 'abc')->result(); // 'bbc' 158 | ``` 159 | 160 | `Regex::replace` also works with callables. The callable will receive a `MatchResult` instance as it's argument. 161 | 162 | ```php 163 | Regex::replace('/a/', function (MatchResult $matchResult) { 164 | return str_repeat($matchResult->result(), 2); 165 | }, 'abc')->result(); // 'aabc' 166 | ``` 167 | 168 | Patterns, replacements and subjects can also be arrays. `Regex::replace` behaves exactly like [`preg_replace`](http://php.net/manual/en/function.preg-replace.php) in those instances. 169 | 170 | ### Error handling 171 | 172 | If anything goes wrong in a `Regex` method, a `RegexFailed` exception gets thrown. No need for checking `preg_last_error()`. 173 | 174 | ## Testing 175 | 176 | ``` bash 177 | $ composer test 178 | ``` 179 | 180 | ## Changelog 181 | 182 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 183 | 184 | ## Contributing 185 | 186 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 187 | 188 | ## Security Vulnerabilities 189 | 190 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 191 | 192 | ## Postcardware 193 | 194 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 195 | 196 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 197 | 198 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 199 | 200 | ## Credits 201 | 202 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 203 | - [All Contributors](../../contributors) 204 | 205 | ## License 206 | 207 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 208 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/regex", 3 | "description": "A sane interface for php's built in preg_* functions", 4 | "keywords": [ 5 | "spatie", 6 | "regex", 7 | "regular", 8 | "expression", 9 | "expressions" 10 | ], 11 | "homepage": "https://github.com/spatie/regex", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Sebastian De Deyne", 16 | "email": "sebastian@spatie.be", 17 | "homepage": "https://spatie.be", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php" : "^8.0|^8.1" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.3" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Spatie\\Regex\\": "src" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Spatie\\Regex\\Test\\": "tests" 35 | } 36 | }, 37 | "scripts": { 38 | "test": "vendor/bin/phpunit" 39 | }, 40 | "config": { 41 | "sort-packages": true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Exceptions/RegexFailed.php: -------------------------------------------------------------------------------- 1 | [$element], $array); 18 | } 19 | 20 | $numHits = count($array[0]); 21 | $groups = array_keys($array); 22 | $result = []; 23 | for ($hit = 0; $hit < $numHits; $hit++) { 24 | $group = []; 25 | foreach ($groups as $groupName) { 26 | $group[$groupName] = $array[$groupName][$hit]; 27 | } 28 | $result[] = $group; 29 | } 30 | 31 | return $result; 32 | } 33 | 34 | public static function first(array $array): mixed 35 | { 36 | return reset($array); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Helpers/Str.php: -------------------------------------------------------------------------------- 1 | getMessage()); 28 | } 29 | 30 | if ($result === false) { 31 | throw RegexFailed::match($pattern, $subject, static::lastPregError()); 32 | } 33 | 34 | return new static($pattern, $subject, $result, $matches); 35 | } 36 | 37 | public function hasMatch(): bool 38 | { 39 | return $this->result; 40 | } 41 | 42 | /** 43 | * @return \Spatie\Regex\MatchResult[] 44 | */ 45 | public function results(): array 46 | { 47 | return Arr::map(Arr::transpose($this->matches), function ($match): MatchResult { 48 | return new MatchResult($this->pattern, $this->subject, true, $match); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/MatchResult.php: -------------------------------------------------------------------------------- 1 | getMessage()); 27 | } 28 | 29 | if ($result === false) { 30 | throw RegexFailed::match($pattern, $subject, static::lastPregError()); 31 | } 32 | 33 | return new static($pattern, $subject, $result, $matches); 34 | } 35 | 36 | public function hasMatch(): bool 37 | { 38 | return $this->hasMatch; 39 | } 40 | 41 | public function result(): ?string 42 | { 43 | return $this->matches[0] ?? null; 44 | } 45 | 46 | public function resultOr(string $default): string 47 | { 48 | return $this->result() ?? $default; 49 | } 50 | 51 | /** 52 | * Match group by index or name. 53 | * 54 | * @param int|string $group 55 | * 56 | * @return string 57 | * 58 | * @throws RegexFailed 59 | */ 60 | public function group(int | string $group): string 61 | { 62 | if (! isset($this->matches[$group])) { 63 | throw RegexFailed::groupDoesntExist($this->pattern, $this->subject, $group); 64 | } 65 | 66 | return $this->matches[$group]; 67 | } 68 | 69 | /** 70 | * Return an array of the matches. 71 | * 72 | * @return array 73 | */ 74 | public function groups(): array 75 | { 76 | return $this->matches; 77 | } 78 | 79 | /** 80 | * Match group by index or return default value if group doesn't exist. 81 | * 82 | * @param int|string $group 83 | * @param string $default 84 | * 85 | * @return string 86 | */ 87 | public function groupOr(int | string $group, string $default): string 88 | { 89 | try { 90 | return $this->group($group); 91 | } catch (RegexFailed $e) { 92 | return $default; 93 | } 94 | } 95 | 96 | /** 97 | * Match group by index or name. 98 | * 99 | * @param int|string $group 100 | * 101 | * @return string 102 | * 103 | * @throws RegexFailed 104 | */ 105 | public function namedGroup(int | string $group): string 106 | { 107 | return $this->group($group); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Regex.php: -------------------------------------------------------------------------------- 1 | getMessage()); 32 | } 33 | 34 | if ($result === null) { 35 | throw RegexFailed::replace($pattern, $subject, static::lastPregError()); 36 | } 37 | 38 | return new static($pattern, $replacement, $subject, $result, $count); 39 | } 40 | 41 | protected static function doReplacement( 42 | string | array $pattern, 43 | string | array | callable $replacement, 44 | string | array $subject, 45 | int $limit 46 | ): array { 47 | $count = 0; 48 | 49 | $result = preg_replace($pattern, $replacement, $subject, $limit, $count); 50 | 51 | return [$result, $count]; 52 | } 53 | 54 | protected static function doReplacementWithCallable( 55 | string | array $pattern, 56 | callable $replacement, 57 | string | array $subject, 58 | int $limit 59 | ): array { 60 | $replacement = function (array $matches) use ($pattern, $subject, $replacement) { 61 | return $replacement(new MatchResult($pattern, $subject, true, $matches)); 62 | }; 63 | 64 | $count = 0; 65 | 66 | $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count); 67 | 68 | return [$result, $count]; 69 | } 70 | 71 | public function result(): string | array 72 | { 73 | return $this->result; 74 | } 75 | 76 | public function count(): int 77 | { 78 | return $this->count; 79 | } 80 | } 81 | --------------------------------------------------------------------------------