├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── STRUCTURE.md ├── composer.json ├── composer.lock ├── example.php ├── example2.php ├── example3.php ├── goodMemory.php ├── gpt-knowledge ├── BasePattern.php.txt ├── BuilderPattern.php.txt ├── EloquentRegexTest.php.txt ├── builder-pattern-traits.php.txt ├── contracts.php.txt ├── docs&desc.md ├── main-classes.php.txt ├── options-examples.php.txt ├── pattern-examples.php.txt ├── predefined-patterns-tests-as-examples.php.txt └── tests-as-examples.php.txt ├── index.php ├── namedGroupsTest.php ├── phpunit.xml ├── searchTest ├── search.php └── test.json ├── src ├── Builder.php ├── Contracts │ ├── BuilderContract.php │ ├── OptionContract.php │ └── PatternContract.php ├── EloquentRegex.php ├── EloquentRegexServiceProvider.php ├── Facades │ └── EloquentRegex.php ├── Options │ ├── CardTypeOption.php │ ├── CharOption.php │ ├── CharacterOption.php │ ├── ContainSpacesOption.php │ ├── CountryCodeOption.php │ ├── DomainSpecificOption.php │ ├── FileExistsOption.php │ ├── FileOption.php │ ├── HtmlTagsOption.php │ ├── IPv6Option.php │ ├── LengthOption.php │ ├── NumberOption.php │ ├── OnlyAlphanumericOption.php │ ├── PathTypeOption.php │ ├── ProtocolOption.php │ └── SpecificCurrenciesOption.php ├── OptionsBuilder.php ├── OptionsManager.php ├── OptionsMapper.php ├── Patterns │ ├── BasePattern.php │ ├── BuilderPattern.php │ ├── CreditCardNumberPattern.php │ ├── CurrencyPattern.php │ ├── DatePattern.php │ ├── DomainNamePattern.php │ ├── EmailPattern.php │ ├── FilePathPattern.php │ ├── FilePathWinPattern.php │ ├── HtmlTagPattern.php │ ├── IPAddressPattern.php │ ├── IPv6AddressPattern.php │ ├── PasswordPattern.php │ ├── PhonePattern.php │ ├── TextOrNumbersPattern.php │ ├── TimePattern.php │ ├── UrlPattern.php │ └── UsernamePattern.php └── Traits │ ├── BuilderPatternTraits │ ├── AnchorsTrait.php │ ├── CharacterClassesTrait.php │ ├── GroupsTrait.php │ └── SpecificCharsTrait.php │ ├── BuilderTraits │ ├── BuilderPatternMethods.php │ └── InitMethods.php │ ├── IsOptionalTrait.php │ ├── Pattern.php │ └── ValidateUsingRegexTrait.php ├── swapTest.php ├── testing.json └── tests ├── Feature ├── BuilderPatternTest.php ├── EloquentRegexTest.php └── Patterns │ ├── CreditCardNumberPatternTest.php │ ├── CurrencyPatternTest.php │ ├── DatePatternTest.php │ ├── DomainNamePatternTest.php │ ├── EmailPatternTest.php │ ├── FilePathPatternTest.php │ ├── FilePathWinPatternTest.php │ ├── HtmlTagPatternTest.php │ ├── IPAddressPatternTest.php │ ├── IPv6AddressPatternTest.php │ ├── PasswordPatternTest.php │ ├── PhonePatternTest.php │ ├── RegexFlagsTest.php │ ├── TextOrNumbersPatternTest.php │ ├── TimePatternTest.php │ ├── UrlPatternTest.php │ └── UsernamePatternTest.php ├── Pest.php ├── TestCase.php ├── TestFiles └── document.txt └── Unit ├── ExampleTest.php ├── Options ├── CardTypeOptionTest.php ├── CharacterOptionTest.php ├── ContainSpacesOptionTest.php ├── CountryCodeOptionTest.php ├── DomainSpecificOptionTest.php ├── FileOptionTest.php ├── HtmlTagsOptionTest.php ├── LengthOptionTest.php ├── NumberOptionTest.php ├── OnlyAlphanumericOptionTest.php ├── PathTypeOption.php ├── ProtocolOptionTest.php └── SpecificCurrenciesOptionTest.php └── Patterns ├── BuilderPatternTest.php └── TextOrNumbersPatternTest.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: PHP CI 2 | 3 | on: 4 | pull_request: 5 | branches: [maestro] 6 | # push: 7 | # branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: "8.2" 18 | - name: Install dependencies 19 | run: composer install --prefer-dist --no-progress 20 | 21 | - name: Run tests 22 | run: ./vendor/bin/pest 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Laravel 4 specific 7 | bootstrap/compiled.php 8 | app/storage/ 9 | 10 | # Laravel 5 & Lumen specific 11 | public/storage 12 | public/hot 13 | 14 | # Laravel 5 & Lumen specific with changed public path 15 | public_html/storage 16 | public_html/hot 17 | 18 | storage/*.key 19 | .env 20 | Homestead.yaml 21 | Homestead.json 22 | /.vagrant 23 | .phpunit.result.cache 24 | 25 | searchTest/*.test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Revaz Gh. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maestroerror/eloquent-regex", 3 | "description": "Eloquent Regex brings the simplicity and elegance to regular expressions. Designed for Laravel developers, this package offers a fluent, intuitive interface for building and executing regex patterns in your PHP applications.", 4 | "type": "library", 5 | "license": "MIT", 6 | "autoload": { 7 | "psr-4": { 8 | "Maestroerror\\EloquentRegex\\": "src/" 9 | } 10 | }, 11 | "authors": [ 12 | { 13 | "name": "maestroerror", 14 | "email": "revaz.gh@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "dev", 18 | "require-dev": { 19 | "pestphp/pest": "3.x-dev" 20 | }, 21 | "config": { 22 | "allow-plugins": { 23 | "pestphp/pest-plugin": true 24 | } 25 | }, 26 | "scripts": { 27 | "test": "./vendor/bin/pest" 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "Maestroerror\\EloquentRegex\\EloquentRegexServiceProvider" 33 | ], 34 | "aliases": { 35 | "EloquentRegex": "Maestroerror\\EloquentRegex\\Facades\\EloquentRegex" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | str = $str; 9 | $this->pattern = ''; 10 | } 11 | 12 | public function textUppercase($length) { 13 | $this->pattern .= "[A-Z]{{$length}}"; 14 | return $this; 15 | } 16 | 17 | public function dash() { 18 | $this->pattern .= '-'; 19 | return $this; 20 | } 21 | 22 | public function anyNumbers() { 23 | $this->pattern .= '\d+'; 24 | return $this; 25 | } 26 | 27 | public function toRegex() { 28 | return "/{$this->pattern}/"; 29 | } 30 | 31 | public function check() { 32 | return preg_match($this->toRegex(), $this->str); 33 | } 34 | 35 | public function get() { 36 | preg_match_all($this->toRegex(), $this->str, $matches); 37 | return $matches[0]; 38 | } 39 | 40 | // Add more methods as needed 41 | } 42 | 43 | // Example usage 44 | $SR = new SimplifiedRegex("RI-214"); 45 | $check = $SR->textUppercase(2)->dash()->anyNumbers()->check(); 46 | echo $check ? 'True' : 'False'; // True 47 | 48 | // Getting multiple matches 49 | $multiSR = new SimplifiedRegex("RI-214 sdjajkgjkdhfsgdkjfjkhagkjhs, RQ-466 sakfdsjg kl;sdfgf"); 50 | $matches = $multiSR->textUppercase(2)->dash()->anyNumbers()->get(); 51 | print_r($matches); // ["RI-214", "RQ-466"] 52 | -------------------------------------------------------------------------------- /example2.php: -------------------------------------------------------------------------------- 1 | str = $str; 8 | } 9 | 10 | public function textUppercase($length) { 11 | $pattern = new TextUppercasePattern(); // Assuming TextUppercasePattern implements PatternContract 12 | $pattern->setOptions(new NumberOption(['min' => $length])); 13 | $this->patterns[] = $pattern; 14 | return $this; 15 | } 16 | 17 | public function dash() { 18 | $this->patterns[] = new DashPattern(); // Assuming DashPattern implements PatternContract 19 | return $this; 20 | } 21 | 22 | // ... other methods ... 23 | 24 | public function toRegex() { 25 | $regex = ''; 26 | foreach ($this->patterns as $pattern) { 27 | $regex = $pattern->addToPattern($regex); 28 | } 29 | return "/$regex/"; 30 | } 31 | 32 | // ... check and get methods ... 33 | } 34 | -------------------------------------------------------------------------------- /example3.php: -------------------------------------------------------------------------------- 1 | config = new class { 10 | public $minLength = 0; 11 | public $minUppercase = 0; 12 | public $minNumber = 0; 13 | }; 14 | } 15 | 16 | public function textAndNumbers(callable $configurator) { 17 | $this->pattern = '(?=.*[a-zA-Z0-9])'; 18 | $configurator($this->config); 19 | return $this; 20 | } 21 | 22 | public function get() { 23 | $length = $this->config->minLength; 24 | $uppercase = $this->config->minUppercase > 0 ? "(?=(?:.*[A-Z]).{{$this->config->minUppercase},})" : ''; 25 | $number = $this->config->minNumber > 0 ? "(?=(?:.*\d).{{$this->config->minNumber},})" : ''; 26 | return "/^{$this->pattern}{$uppercase}{$number}.{{$length},}$/"; 27 | } 28 | } 29 | 30 | // Example usage 31 | $simpleRegex = new SimplifiedRegex(); 32 | $pattern = $simpleRegex->textAndNumbers(function($options) { 33 | $options->minLength = 8; 34 | $options->minUppercase = 1; 35 | $options->minNumber = 1; 36 | })->get(); 37 | echo $pattern; 38 | echo "\n"; 39 | echo preg_match($pattern, "Pass1234"); 40 | -------------------------------------------------------------------------------- /goodMemory.php: -------------------------------------------------------------------------------- 1 | info('--Starting--'); 7 | 8 | $cutoff_date = Carbon::now()->subMonths(2)->format('Y-m-d'); 9 | 10 | $record_cell = DirectoryRecordCell::withTrashed() 11 | ->where('deleted_at', '<=', "$cutoff_date")->count(); 12 | 13 | $this->info("$record_cell record cells found to delete"); 14 | 15 | $record = DirectoryRecord::withTrashed() 16 | ->where('deleted_at', '<=', "$cutoff_date")->count(); 17 | 18 | 19 | $this->info("$record records found to delete"); 20 | 21 | $record_cell = DirectoryRecordCell::withTrashed() 22 | ->where('deleted_at', '<=', "$cutoff_date")->forceDelete(); 23 | 24 | $record = DirectoryRecord::withTrashed() 25 | ->where('deleted_at', '<=', "$cutoff_date")->forceDelete(); 26 | 27 | $this->info('Deleted records successfully.'); 28 | 29 | $this->info('--Finishing--'); 30 | } 31 | -------------------------------------------------------------------------------- /gpt-knowledge/BasePattern.php.txt: -------------------------------------------------------------------------------- 1 | pattern; 57 | } 58 | 59 | /** 60 | * Sets the options for this pattern. 61 | * 62 | * @param array $options Array of options to be applied to the pattern. 63 | */ 64 | public function setOptions(array $options) { 65 | $this->options = $options; 66 | } 67 | 68 | /** 69 | * Adds an option to this pattern. 70 | * 71 | * @param OptionContract $option Option to be added. 72 | */ 73 | public function setOption(OptionContract $option) { 74 | $this->options[] = $option; 75 | } 76 | 77 | 78 | /** 79 | * Validates an input string against the pattern and its options as exact match. 80 | * 81 | * @param string $input The input string to validate. 82 | * @return bool True if the input string validates against the pattern and options, false otherwise. 83 | */ 84 | public function validateInput(string $input): bool { 85 | // Get the main pattern 86 | $mainPattern = $this->getInputValidationPattern(); 87 | 88 | // First, check if the entire input matches the main pattern 89 | if (!preg_match($mainPattern, $input)) { 90 | return false; 91 | } 92 | 93 | // Then, validate the input against each option 94 | return $this->validateOptions($input); 95 | } 96 | 97 | /** 98 | * Validates that the input string contains matches for the pattern, filtered by options. 99 | * 100 | * @param string $input The input string to validate. 101 | * @return bool True if there are any matches for the pattern in the input, after applying options. 102 | */ 103 | public function validateMatches(string $input): bool { 104 | // Get the main pattern for matches 105 | $mainPattern = $this->getMatchesValidationPattern(); 106 | 107 | // Find all matches for the main pattern in the input 108 | if (preg_match_all($mainPattern, $input, $matches) == 0) { 109 | return false; 110 | } 111 | 112 | // Filter these matches based on the options 113 | $filteredMatches = $this->filterByOptions($matches[0]); 114 | 115 | // Check if there are any matches left after filtering 116 | return count($filteredMatches) > 0; 117 | } 118 | 119 | /** 120 | * Retrieves all matches of the pattern in the input string, filtered by options. 121 | * 122 | * @param string $input The input string to search for matches. 123 | * @return array An array of matches. 124 | */ 125 | public function getMatches(string $input, bool $returnGroups = false): ?array { 126 | $mainPattern = $this->getMatchesValidationPattern(); 127 | preg_match_all($mainPattern, $input, $matches); 128 | 129 | if (!$matches[0]) { 130 | return null; 131 | } 132 | 133 | if ($returnGroups) { 134 | // Filter matches but keep indexes same 135 | $results = $this->filterByOptions($matches[0], false); 136 | // Unset matches and keep only groups 137 | unset($matches[0]); 138 | $groups = $matches; 139 | return [ 140 | "results" => $results, 141 | "groups" => $groups 142 | ]; 143 | } else { 144 | // Filter matches based on each option 145 | return $this->filterByOptions($matches[0]); 146 | } 147 | } 148 | 149 | /** 150 | * Filters an array of matches based on the options. 151 | * 152 | * @param array $allMatches Array of matches to be filtered. 153 | * @return array Filtered array of matches. 154 | */ 155 | protected function filterByOptions(array $allMatches, $fixArrayIndexes = true): array { 156 | // Use array_filter to keep only those matches that pass all options' validation 157 | $filtered = array_filter($allMatches, function($match) { 158 | return $this->validateOptions($match); 159 | }); 160 | 161 | if ($fixArrayIndexes) { 162 | return array_values($filtered); 163 | } else { 164 | return $filtered; 165 | } 166 | } 167 | 168 | /** 169 | * Validates an input string against all set options. 170 | * 171 | * @param string $input The input string to validate against the options. 172 | * @return bool True if the input string passes all options' validation, false otherwise. 173 | */ 174 | protected function validateOptions(string $input): bool { 175 | if (!empty($this->options)) { 176 | foreach ($this->options as $option) { 177 | if (!$option->validate($input)) { 178 | return false; 179 | } 180 | } 181 | } 182 | return true; 183 | } 184 | 185 | /** 186 | * Default implementation of generating regex patterns for input validation 187 | * 188 | * @return string The regex pattern for validating the entire input. 189 | */ 190 | public function getInputValidationPattern(): string { 191 | return "/^{$this->pattern}$/" . $this->expressionFlags; 192 | } 193 | 194 | /** 195 | * Default implementation of generating regex patterns for matches validation 196 | * 197 | * @return string The regex pattern for finding matches within the input. 198 | */ 199 | public function getMatchesValidationPattern(): string { 200 | return "/{$this->pattern}/" . $this->expressionFlags; 201 | } 202 | 203 | /** 204 | * Processes an array of arguments and builds an options array. 205 | * 206 | * @param array $args Names of the arguments. 207 | * @param array $values Values of the arguments. 208 | * @return array An associative array of options. 209 | */ 210 | protected static function processArguments(array $args, array $values): array { 211 | $options = []; 212 | // Build options array based on condition 213 | for ($i=0; $i < count($args); $i++) { 214 | if (isset($values[$i])) { 215 | // If value is true (so not "", 0, null) 216 | if ($values[$i]) { 217 | $options[$args[$i]] = $values[$i]; 218 | } 219 | } 220 | } 221 | 222 | return $options; 223 | } 224 | 225 | /** 226 | * Processes a callback function to configure options. 227 | * 228 | * @param callable $callback The callback function used for configuring options. 229 | * @return array An associative array of options set by the callback. 230 | */ 231 | protected static function processCallback(callable $callback): array { 232 | $optionsBuilder = new OptionsBuilder(); 233 | $callback($optionsBuilder); 234 | return $optionsBuilder->getOptions(); 235 | } 236 | 237 | /** 238 | * Adds a regex expression flag to the pattern. 239 | * 240 | * @param string $flag The single-character flag to add to the regex pattern. 241 | */ 242 | public function addExpressionFlag(string $flag): void { 243 | if (strpos($this->expressionFlags, $flag) === false) { 244 | $this->expressionFlags .= $flag; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /gpt-knowledge/BuilderPattern.php.txt: -------------------------------------------------------------------------------- 1 | builder = $builder; 51 | } 52 | 53 | // Builder class implementation methods START 54 | 55 | public function end(array|callable $config = []): BuilderContract { 56 | return $this->builder->setOptions($config); // Return the Builder object 57 | } 58 | 59 | public function get(): ?array { 60 | return $this->builder->get(); 61 | } 62 | 63 | public function check(): bool { 64 | return $this->builder->check(); 65 | } 66 | 67 | public function checkString(): bool { 68 | return $this->builder->checkString(); 69 | } 70 | 71 | public function count(): int { 72 | return $this->builder->count(); 73 | } 74 | 75 | public function toRegex(): string { 76 | return $this->builder->toRegex(); 77 | } 78 | 79 | // Builder class implementation methods END 80 | 81 | public function getInputValidationPattern(): string { 82 | return "/^{$this->pattern}$/" . $this->expressionFlags; 83 | } 84 | 85 | public function getMatchesValidationPattern(): string { 86 | return "/{$this->pattern}/" . $this->expressionFlags; 87 | } 88 | 89 | /** 90 | * Applies a quantifier to a given regex pattern. 91 | * 92 | * @param string $pattern The regex pattern to which the quantifier will be applied. 93 | * @param string|null $quantifier The quantifier to apply. Can be 'zeroOrMore', 'oneOrMore', or 'optional'. 94 | * @return string The modified pattern with the quantifier applied. 95 | */ 96 | private function applyQuantifier(string $pattern, string|null $q): string { 97 | 98 | if (!$q) { 99 | return $pattern; 100 | } 101 | 102 | if ($q == 'zeroOrMore' || $q == '0>' || $q == '0+' || $q == '*') { 103 | $p = "(?:" . $pattern . ')*'; 104 | return $this->lazy ? $this->addLazy($p) : $p; 105 | } elseif ($q == 'oneOrMore' || $q == '1>' || $q == '1+' || $q == '+') { 106 | $p = "(?:" . $pattern . ')+'; 107 | return $this->lazy ? $this->addLazy($p) : $p; 108 | } elseif ($q == 'optional' || $q == '?' || $q == '|') { 109 | $p = "(?:" . $pattern . ')?'; 110 | return $this->lazy ? $this->addLazy($p) : $p; 111 | } 112 | 113 | if (is_int($q)) { 114 | $p = "(?:" . $pattern . "){".$q."}"; 115 | return $this->lazy ? $this->addLazy($p) : $p; 116 | } elseif (preg_match("/^\d{1,10}$/", $q)) { 117 | $p = "(?:" . $pattern . '){'.$q.'}'; 118 | return $this->lazy ? $this->addLazy($p) : $p; 119 | } elseif (preg_match("/^\d{1,10},\d{1,10}$/", $q)) { 120 | $range = explode(",", $q); 121 | $f = $range[0]; 122 | $s = $range[1]; 123 | $p = "(?:" . $pattern . ")" . "{" . $f . "," . $s ."}"; 124 | return $this->lazy ? $this->addLazy($p) : $p; 125 | } 126 | 127 | return $pattern; 128 | } 129 | 130 | /** 131 | * Generates a regex quantifier string based on length parameters. 132 | * 133 | * @param int|null $length Exact length for the quantifier. 134 | * @param int $minLength Minimum length for the quantifier. 135 | * @param int $maxLength Maximum length for the quantifier. 136 | * @return string The generated regex quantifier string. 137 | */ 138 | private function getLengthOption(int|null $length = null, int $minLength = 0, int $maxLength = 0): string { 139 | if (is_int($length) && $length > 0) { 140 | $qntf = "{" . $length . "}"; 141 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 142 | } elseif ($length === 0 || $this->inCharSet) { 143 | return ""; 144 | } 145 | 146 | if ($minLength > 0 && $maxLength > 0) { 147 | $qntf = "{" . $minLength . "," . $maxLength . "}"; 148 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 149 | } else if ($minLength > 0) { 150 | $qntf = "{" . $minLength . ",}"; 151 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 152 | } else if ($maxLength > 0) { 153 | $qntf = "{0," . $maxLength . "}"; 154 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 155 | } 156 | 157 | $qntf = "+"; // Default case, one or more times 158 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 159 | } 160 | 161 | /** 162 | * Adds a lazy (non-greedy) modifier to a quantifier 163 | * and sets $lazy to false for ensuring single use 164 | * 165 | * @param string $quantifier The quantifier to which the lazy modifier will be added. 166 | * @return string The quantifier with the lazy modifier applied. 167 | */ 168 | private function addLazy($quantifier): string { 169 | $this->lazy = false; 170 | return $quantifier . "?"; 171 | } 172 | 173 | /** 174 | * Creates a lazy (non-greedy) quantifier for the next method call. 175 | * 176 | * @return self 177 | */ 178 | public function lazy(): self { 179 | $this->lazy = true; 180 | return $this; 181 | } 182 | 183 | public function inCharSet(): self { 184 | $this->inCharSet = true; 185 | return $this; 186 | } 187 | 188 | 189 | } -------------------------------------------------------------------------------- /gpt-knowledge/contracts.php.txt: -------------------------------------------------------------------------------- 1 | value (arg)) or a callback function to configure options. 60 | * @return void 61 | */ 62 | public function setOptions(array|callable $config): self; 63 | 64 | /** 65 | * Registers a single pattern in the Builder. 66 | * 67 | * @param PatternContract $pattern The pattern instance to be registered. 68 | * @return self Returns the Builder instance for fluent interface. 69 | */ 70 | public function registerPattern(PatternContract $pattern): self; 71 | 72 | /** 73 | * Registers multiple patterns in the Builder. 74 | * 75 | * @param array $patterns An array of pattern instances to be registered. 76 | * @return self Returns the Builder instance for fluent interface. 77 | */ 78 | public function registerPatterns(array $patterns): self; 79 | 80 | /** 81 | * Retrieves all registered patterns in the Builder. 82 | * 83 | * @return array An array of registered pattern instances. 84 | */ 85 | public function getPatterns(): array; 86 | 87 | /** 88 | * Sets returnGroups property 89 | * 90 | * @return self Returns the Builder instance. 91 | */ 92 | public function setReturnGroups(bool $enable): self; 93 | 94 | /** 95 | * Gets returnGroups property 96 | * 97 | * @return self Returns the Builder instance. 98 | */ 99 | public function getReturnGroups(): bool; 100 | 101 | /** 102 | * Magic method to handle dynamic method calls. 103 | * 104 | * This method is triggered when invoking inaccessible or non-existing methods. 105 | * It is used to dynamically handle pattern-specific method calls. 106 | * 107 | * @param string $name The name of the method being called. 108 | * @param array $args An array of arguments passed to the method. 109 | * @return self Returns the Builder instance for fluent interface. 110 | */ 111 | public function __call($name, $args): self; 112 | 113 | } 114 | 115 | 116 | 117 | 118 | namespace Maestroerror\EloquentRegex\Contracts; 119 | 120 | /** 121 | * Interface for defining regex options. 122 | * 123 | * This interface provides a set of methods for building and managing 124 | * regex patterns in a flexible and modular way. Implementing classes 125 | * can define specific regex behaviors and characteristics. 126 | */ 127 | interface OptionContract { 128 | /** 129 | * Validates the given input against the option's criteria 130 | * Using PHP statements and/or built Regex pattern. 131 | * 132 | * @param string $input The input string to validate. 133 | * @return bool True if the input is valid, false otherwise. 134 | */ 135 | public function validate(string $input): bool; 136 | 137 | /** 138 | * Builds and returns the regex pattern for this option. 139 | * If option doesn't need regex, can return empty string. 140 | * 141 | * @return string The constructed regex pattern segment. 142 | */ 143 | public function build(): string; 144 | } 145 | 146 | 147 | 148 | 149 | namespace Maestroerror\EloquentRegex\Contracts; 150 | 151 | /** 152 | * Interface PatternContract 153 | * 154 | * Defines the structure for regex pattern classes within the EloquentRegex system. 155 | * Each pattern class implementing this interface is responsible for building and managing 156 | * a specific segment of a regex pattern. 157 | */ 158 | interface PatternContract { 159 | 160 | /** 161 | * Builds and returns the regex pattern segment for this pattern. (Without options) 162 | * 163 | * @return string The constructed regex pattern segment. 164 | */ 165 | public function getPattern(): string; 166 | 167 | /** 168 | * Sets the options for this pattern. 169 | * 170 | * @param OptionContract $option The options to be applied to this pattern. 171 | * @return void 172 | */ 173 | public function setOption(OptionContract $option); 174 | 175 | /** 176 | * Sets the options for this pattern. 177 | * 178 | * @param array $options The options [OptionContract] to be applied to this pattern. 179 | * @return void 180 | */ 181 | public function setOptions(array $options); 182 | 183 | /** 184 | * Validates a given input string against this pattern as exact match. 185 | * 186 | * @param string $input The input string to validate. 187 | * @return bool True if the input is valid according to this pattern, false otherwise. 188 | */ 189 | public function validateInput(string $input): bool; 190 | 191 | /** 192 | * Validates a given input string against this pattern as it includes min 1 match. 193 | * 194 | * @param string $input The input string to validate. 195 | * @return bool True if the input is valid according to this pattern, false otherwise. 196 | */ 197 | public function validateMatches(string $input): bool; 198 | 199 | /** 200 | * Returns all matches found by this pattern. 201 | * 202 | * @param string $input The input string to validate. 203 | * @param bool $returnGroups if true returns whole array of matches (including groups). 204 | * @return array all matches found in input. 205 | */ 206 | public function getMatches(string $input, bool $returnGroups = false): ?array; 207 | 208 | /** 209 | * Generates the regex pattern for input validation. 210 | * 211 | * This pattern is used to check if the entire input string exactly matches the constructed pattern. 212 | * 213 | * @return string The regex pattern for validating the entire input. 214 | */ 215 | public function getInputValidationPattern(): string; 216 | 217 | /** 218 | * Generates the regex pattern for finding matches within the input. 219 | * 220 | * This pattern is used to search for occurrences of the pattern within the input string. 221 | * 222 | * @return string The regex pattern for finding matches within the input. 223 | */ 224 | public function getMatchesValidationPattern(): string; 225 | 226 | /** 227 | * Adds regex flag to the pattern 228 | * 229 | * @param string $flag to add after regex pattern 230 | * @return void 231 | */ 232 | public function addExpressionFlag(string $flag): void; 233 | 234 | /** 235 | * Executes the pattern with the provided arguments. 236 | * 237 | * This method is responsible for processing the arguments provided to the pattern 238 | * and configuring it accordingly. It can handle an array of options, a set of 239 | * individual arguments, or a callback function for more complex configurations. 240 | * 241 | * @param mixed $firstArgument The first argument which could be an array of options, 242 | * an integer, a string, or a callback function. 243 | * @param mixed ...$args Additional arguments if the first argument is not an array or callback. 244 | * @return array Returns an array of configuration options processed by the method. 245 | * @throws \InvalidArgumentException If the first argument does not meet the expected types. 246 | */ 247 | public static function execute(mixed $firstArgument = 1, ...$args): array; 248 | 249 | } -------------------------------------------------------------------------------- /gpt-knowledge/pattern-examples.php.txt: -------------------------------------------------------------------------------- 1 | pattern}$/u"; 43 | } 44 | 45 | public function getMatchesValidationPattern(): string { 46 | return "/{$this->pattern}/u"; 47 | } 48 | } 49 | 50 | 51 | 52 | namespace Maestroerror\EloquentRegex\Patterns; 53 | 54 | use Maestroerror\EloquentRegex\Patterns\BasePattern; 55 | use Maestroerror\EloquentRegex\Traits\Pattern; 56 | 57 | class FilePathPattern extends BasePattern { 58 | 59 | use Pattern; 60 | 61 | // Matches both Directory and File paths 62 | protected string $pattern = "[~\/]?[^\/:*,?\"<>|\r\n\s]+(?:\/[^\/:*,?\"<>|\r\n\s]+)+\/?(?:\.[a-zA-Z0-9]+)?"; 63 | 64 | public static string $name = "filePath"; 65 | 66 | public static array $args = [ 67 | "isDirectory", 68 | "isFile", 69 | "fileExists", 70 | "pathType" 71 | ]; 72 | } 73 | 74 | 75 | namespace Maestroerror\EloquentRegex\Patterns; 76 | 77 | use Maestroerror\EloquentRegex\Patterns\BasePattern; 78 | use Maestroerror\EloquentRegex\Traits\Pattern; 79 | 80 | class IPAddressPattern extends BasePattern { 81 | 82 | use Pattern; 83 | 84 | // Regex pattern for IPv4 85 | protected string $pattern = "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"; 86 | 87 | public static string $name = "ipAddress"; 88 | 89 | public static array $args = []; 90 | } 91 | 92 | 93 | 94 | namespace Maestroerror\EloquentRegex\Patterns; 95 | 96 | use Maestroerror\EloquentRegex\Patterns\BasePattern; 97 | use Maestroerror\EloquentRegex\Traits\Pattern; 98 | 99 | class UrlPattern extends BasePattern { 100 | 101 | use Pattern; 102 | 103 | protected string $pattern = "(https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[a-zA-Z0-9\/]*)?)"; 104 | 105 | 106 | public static string $name = "url"; 107 | 108 | public static array $args = [ 109 | "onlyProtocol" 110 | ]; 111 | 112 | } 113 | 114 | 115 | 116 | 117 | namespace Maestroerror\EloquentRegex\Patterns; 118 | 119 | use Maestroerror\EloquentRegex\Patterns\BasePattern; 120 | use Maestroerror\EloquentRegex\Traits\Pattern; 121 | 122 | class PhonePattern extends BasePattern { 123 | 124 | use Pattern; 125 | 126 | // For more precise validation use package: giggsey/libphonenumber-for-php 127 | protected string $pattern = "(?:[+\d]{1,4})[ -]?(?:[()\d]{1,5})[- \d]{4,23}"; 128 | 129 | public static string $name = "phone"; 130 | 131 | public static array $args = [ 132 | "countryCode" 133 | ]; 134 | } 135 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | textOrNumbers(8, 0, 1)->check(); // Exact same (entire input from start ^ to end $) 11 | print_r($check); 12 | 13 | echo "\n"; 14 | 15 | $string = "asd Passs1234 wawd"; 16 | $builder = new Builder($string); 17 | // Min 8 chars, min 1 uppercase 18 | $check = $builder->textOrNumbers([ 19 | "minLength" => 8, 20 | "minUppercase" => 1 21 | ])->checkString(); // Includes min 1 22 | print_r($check); 23 | 24 | echo "\n"; 25 | 26 | $string = "asd Passs1234 Wawoline343 text here"; 27 | $builder = new Builder($string); 28 | $count = $builder->textOrNumbers(function($query) { 29 | return $query->minLength(8)->minUppercase(1); 30 | })->count(); // Returns number of matches 31 | print_r("Count: " . $count); 32 | 33 | echo "\n"; 34 | 35 | 36 | $string = "Passs1234 an 1sada a 5464565"; 37 | $builder = (new Builder($string))->textOrNumbers(4); 38 | $get = $builder->get(); 39 | print_r($get); 40 | 41 | echo "\n"; 42 | 43 | $regex = $builder->toRegex(); 44 | print_r($regex); 45 | echo "\n"; 46 | 47 | // $string = "Passs1234 an 1sada a 5464565"; 48 | // $builder = (new Builder($string))->textOrNumbers("string")->check(); 49 | 50 | $SR = new Builder("RI-214"); 51 | $check = $SR->start()->textUppercase(2)->dash()->digits()->end()->check(); 52 | print_r($check); // true 53 | 54 | echo "\n"; 55 | 56 | $SR = new Builder("RI-2142"); 57 | $check = $SR->pattern(function ($builder) { 58 | return $builder->textUppercase(2)->dash()->digits(3); 59 | })->check(); 60 | print_r($check); // false 61 | 62 | echo "\n"; 63 | 64 | 65 | 66 | $SR = new Builder("RI-214 - A nice task"); 67 | $get = $SR->pattern(function ($builder) { 68 | return $builder->textUppercase(2)->dash()->digits(3); 69 | })->get(); 70 | print_r($get); // 71 | 72 | echo "\n"; -------------------------------------------------------------------------------- /namedGroupsTest.php: -------------------------------------------------------------------------------- 1 | pattern(function ($builder) { 10 | return $builder 11 | ->namedGroup(function ($pattern) { 12 | return $pattern->textUppercase(2); 13 | }, "project", 1) 14 | ->dash() 15 | ->namedGroup(function ($pattern) { 16 | return $pattern->digitsRange(2, 4); 17 | }, "issue", 1); 18 | }); 19 | 20 | $results = $result->get(); 21 | 22 | foreach ($results as $item) { 23 | $id = $item["result"]; 24 | $projectPart = $item["groups"]["project"]; 25 | $issueNumber = $item["groups"]["issue"]; 26 | echo "ID: $id; Project: $projectPart; Issue: $issueNumber\n"; 27 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./app 15 | ./src 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /searchTest/search.php: -------------------------------------------------------------------------------- 1 | start() 39 | ->anyChar() 40 | ->lookAhead(function ($pattern) use ($keyword) { 41 | $pattern->exact($keyword); 42 | })->anyChar()->end(); // .+(?=$keyword).+ 43 | 44 | return $builder->get(); 45 | } 46 | 47 | function makeObject($json) { 48 | return json_decode($json); 49 | } 50 | 51 | function decode($data) { 52 | $json = implode(",", $data); 53 | // file_put_contents("testing.json", "[" . $json . "]"); 54 | return json_decode("[" . $json . "]"); 55 | } 56 | 57 | function test($keyword, $file, $print = false) { 58 | $start = microtime(true); 59 | $data = search($keyword, $file); 60 | $time_elapsed_secs = microtime(true) - $start; 61 | echo "Search of $file took: " . $time_elapsed_secs . "\n"; 62 | echo "Matches found: " . count($data) . "\n"; 63 | if ($print) { 64 | // $data = array_map("makeObject", $data); 65 | $data = decode($data); 66 | // var_dump($data); 67 | $time_elapsed_secs = microtime(true) - $start; 68 | echo "Search and decode of $file took: " . $time_elapsed_secs . "\n"; 69 | } 70 | return $data; 71 | } 72 | 73 | 74 | 75 | // test('"eyeColor":"green"', "data.1000.test", true); 76 | // test("green", "data.20k.test", true); 77 | // test("Livingston Hendricks", "data.5000.test", true); 78 | 79 | test("65d9f50aa8f5ea8f53c9ab9f", "data.1000.test", true); 80 | 81 | $start = microtime(true); 82 | // Using decode function: 83 | // 1k 10 times is faster (112 ms) then 10K at once (465 ms) 84 | // test("green", "data.10k.test", true); 85 | for ($i=0; $i < 10; $i++) { 86 | test("green", "data.1000.test", true); 87 | } 88 | $time_elapsed_secs = microtime(true) - $start; 89 | echo "Search and decode took: " . $time_elapsed_secs . "\n"; -------------------------------------------------------------------------------- /searchTest/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | { 4 | "_id": "65d9f50aa8f5ea8f53c9ab9f", 5 | "index": 0, 6 | "guid": "159feb98-2c0e-4ea6-b313-92f4779f6038", 7 | "isActive": false, 8 | "balance": "$2,752.32", 9 | "picture": "http://placehold.it/32x32", 10 | "age": 21, 11 | "eyeColor": "brown", 12 | "name": "Rollins Rutledge", 13 | "gender": "male", 14 | "company": "ZORROMOP", 15 | "email": "rollinsrutledge@zorromop.com", 16 | "phone": "+1 (878) 402-3333", 17 | "address": "788 Tehama Street, Norfolk, Arizona, 317", 18 | "about": "Est occaecat veniam proident laborum consectetur pariatur ex consectetur sit Lorem. Pariatur laboris nisi ullamco fugiat magna officia sit do cillum sit ipsum reprehenderit sunt dolore. Excepteur ex anim voluptate quis eiusmod irure sit esse tempor sunt labore nulla.\r\n", 19 | "registered": "2017-04-09T11:12:19 -04:00", 20 | "latitude": 76.177825, 21 | "longitude": -86.610081, 22 | "tags": [ 23 | "sunt", 24 | "aliqua", 25 | "sit", 26 | "consectetur", 27 | "est", 28 | "excepteur", 29 | "fugiat" 30 | ], 31 | "friends": [ 32 | { "id": 0, "name": "Hendricks Henderson" }, 33 | { "id": 1, "name": "Autumn Rose" }, 34 | { "id": 2, "name": "Beck Cain" } 35 | ], 36 | "greeting": "Hello, Rollins Rutledge! You have 6 unread messages.", 37 | "favoriteFruit": "apple" 38 | }, 39 | { 40 | "_id": "65d9f50a11279b0eb7d52633", 41 | "index": 1, 42 | "guid": "ada8d9b9-89fb-4caf-a7fb-4692158e4d0c", 43 | "isActive": false, 44 | "balance": "$1,449.13", 45 | "picture": "http://placehold.it/32x32", 46 | "age": 37, 47 | "eyeColor": "brown", 48 | "name": "Daniel Fowler", 49 | "gender": "male", 50 | "company": "CUIZINE", 51 | "email": "danielfowler@cuizine.com", 52 | "phone": "+1 (919) 518-3796", 53 | "address": "616 Croton Loop, Fresno, Georgia, 6149", 54 | "about": "Ex dolore ipsum esse consequat reprehenderit laborum anim est et nulla magna dolor reprehenderit. Sit consequat cupidatat minim sint qui. Adipisicing exercitation sit minim minim consequat officia nostrud culpa amet occaecat sit laborum eu. Esse reprehenderit exercitation eiusmod irure ad irure nulla Lorem sint aliqua id ex. Culpa deserunt proident labore nisi. Excepteur ipsum irure ipsum excepteur nisi ea quis elit incididunt deserunt enim nostrud est.\r\n", 55 | "registered": "2015-10-11T11:08:56 -04:00", 56 | "latitude": -43.533929, 57 | "longitude": 14.347532, 58 | "tags": [ 59 | "cupidatat", 60 | "eu", 61 | "ipsum", 62 | "proident", 63 | "Lorem", 64 | "fugiat", 65 | "veniam" 66 | ], 67 | "friends": [ 68 | { "id": 0, "name": "Della Mcleod" }, 69 | { "id": 1, "name": "Marcy Fox" }, 70 | { "id": 2, "name": "Bettie Wall" } 71 | ], 72 | "greeting": "Hello, Daniel Fowler! You have 3 unread messages.", 73 | "favoriteFruit": "apple" 74 | }, 75 | { 76 | "_id": "65d9f50a44b56c2c99d80e75", 77 | "index": 2, 78 | "guid": "b2cbd371-99b1-42df-8b2e-8c83ea6fcb51", 79 | "isActive": false, 80 | "balance": "$3,801.62", 81 | "picture": "http://placehold.it/32x32", 82 | "age": 37, 83 | "eyeColor": "green", 84 | "name": "Lydia Goff", 85 | "gender": "female", 86 | "company": "EXTRO", 87 | "email": "lydiagoff@extro.com", 88 | "phone": "+1 (860) 477-2510", 89 | "address": "444 Dekoven Court, Edenburg, Mississippi, 2543", 90 | "about": "Voluptate eu laborum sit amet nostrud nisi. Non aliquip occaecat est amet sit. In ipsum enim sit dolore Lorem magna deserunt id culpa in fugiat velit sint. Adipisicing dolor veniam aliquip adipisicing enim pariatur quis dolore nisi commodo do. Deserunt ut consequat eiusmod veniam aliqua non.\r\n", 91 | "registered": "2021-09-25T11:34:03 -04:00", 92 | "latitude": 88.634904, 93 | "longitude": -60.86038, 94 | "tags": [ 95 | "exercitation", 96 | "in", 97 | "do", 98 | "sunt", 99 | "tempor", 100 | "adipisicing", 101 | "minim" 102 | ], 103 | "friends": [ 104 | { "id": 0, "name": "Nash Solomon" }, 105 | { "id": 1, "name": "Burton Gates" }, 106 | { "id": 2, "name": "Johnnie Steele" } 107 | ], 108 | "greeting": "Hello, Lydia Goff! You have 1 unread messages.", 109 | "favoriteFruit": "apple" 110 | }, 111 | { 112 | "_id": "65d9f50a2f5995dcd208f775", 113 | "index": 3, 114 | "guid": "7d805d37-89ff-4082-a014-d4f023568251", 115 | "isActive": true, 116 | "balance": "$3,704.75", 117 | "picture": "http://placehold.it/32x32", 118 | "age": 35, 119 | "eyeColor": "brown", 120 | "name": "Koch Diaz", 121 | "gender": "male", 122 | "company": "SYBIXTEX", 123 | "email": "kochdiaz@sybixtex.com", 124 | "phone": "+1 (812) 572-3192", 125 | "address": "218 Pooles Lane, Florence, Illinois, 5487", 126 | "about": "Consectetur est deserunt laborum occaecat sint cillum laboris est dolore eiusmod exercitation. Anim sunt do ipsum fugiat dolore et voluptate incididunt amet. Nisi elit et velit do aliqua nostrud eiusmod id in voluptate in id.\r\n", 127 | "registered": "2017-10-20T01:07:18 -04:00", 128 | "latitude": 63.602765, 129 | "longitude": -86.143282, 130 | "tags": [ 131 | "nostrud", 132 | "incididunt", 133 | "tempor", 134 | "minim", 135 | "cillum", 136 | "adipisicing", 137 | "esse" 138 | ], 139 | "friends": [ 140 | { "id": 0, "name": "Georgina Kent" }, 141 | { "id": 1, "name": "Bruce William" }, 142 | { "id": 2, "name": "Myrtle Ramos" } 143 | ], 144 | "greeting": "Hello, Koch Diaz! You have 4 unread messages.", 145 | "favoriteFruit": "strawberry" 146 | }, 147 | { 148 | "_id": "65d9f50ac35b0d21549ac686", 149 | "index": 4, 150 | "guid": "287346a6-1268-48f4-a72f-34596aff60f5", 151 | "isActive": true, 152 | "balance": "$2,498.82", 153 | "picture": "http://placehold.it/32x32", 154 | "age": 26, 155 | "eyeColor": "blue", 156 | "name": "Tasha Mcintyre", 157 | "gender": "female", 158 | "company": "INTRAWEAR", 159 | "email": "tashamcintyre@intrawear.com", 160 | "phone": "+1 (956) 583-2591", 161 | "address": "412 Mill Lane, Ahwahnee, Maryland, 7615", 162 | "about": "Eu ipsum magna pariatur incididunt laboris Lorem ipsum nulla sunt. Occaecat dolor mollit pariatur proident esse exercitation dolor enim ea voluptate ex eu consectetur quis. Incididunt velit dolore deserunt ut. Qui minim anim consectetur enim do exercitation velit consectetur dolore.\r\n", 163 | "registered": "2019-12-11T10:48:29 -04:00", 164 | "latitude": -1.305172, 165 | "longitude": -86.053847, 166 | "tags": ["cillum", "proident", "do", "ipsum", "labore", "aute", "amet"], 167 | "friends": [ 168 | { "id": 0, "name": "Mercado Hodges" }, 169 | { "id": 1, "name": "Hart Mcconnell" }, 170 | { "id": 2, "name": "Tara Riddle" } 171 | ], 172 | "greeting": "Hello, Tasha Mcintyre! You have 6 unread messages.", 173 | "favoriteFruit": "apple" 174 | }, 175 | { 176 | "_id": "65d9f50abe3f8754da3bcb8b", 177 | "index": 5, 178 | "guid": "2f08c2a9-6072-45aa-911d-c755fa8cd34a", 179 | "isActive": true, 180 | "balance": "$1,581.93", 181 | "picture": "http://placehold.it/32x32", 182 | "age": 23, 183 | "eyeColor": "brown", 184 | "name": "Fowler Simon", 185 | "gender": "male", 186 | "company": "GENMOM", 187 | "email": "fowlersimon@genmom.com", 188 | "phone": "+1 (946) 581-3127", 189 | "address": "405 Vandalia Avenue, Gorham, California, 7644", 190 | "about": "Incididunt nisi enim cupidatat labore id duis eu fugiat do consectetur mollit nostrud eu proident. Irure irure nostrud sunt laboris deserunt occaecat eiusmod consequat. Non aliquip do do ea incididunt qui ad deserunt magna.\r\n", 191 | "registered": "2019-11-08T08:41:44 -04:00", 192 | "latitude": -58.124141, 193 | "longitude": 118.718241, 194 | "tags": [ 195 | "sunt", 196 | "incididunt", 197 | "pariatur", 198 | "nostrud", 199 | "non", 200 | "commodo", 201 | "ut" 202 | ], 203 | "friends": [ 204 | { "id": 0, "name": "Joni Hendrix" }, 205 | { "id": 1, "name": "Hale Love" }, 206 | { "id": 2, "name": "Catalina Mack" } 207 | ], 208 | "greeting": "Hello, Fowler Simon! You have 6 unread messages.", 209 | "favoriteFruit": "banana" 210 | } 211 | ] 212 | ] 213 | -------------------------------------------------------------------------------- /src/Contracts/BuilderContract.php: -------------------------------------------------------------------------------- 1 | value (arg)) or a callback function to configure options. 60 | * @return void 61 | */ 62 | public function setOptions(array|callable $config): self; 63 | 64 | /** 65 | * Registers a single pattern in the Builder. 66 | * 67 | * @param PatternContract $pattern The pattern instance to be registered. 68 | * @return self Returns the Builder instance for fluent interface. 69 | */ 70 | public function registerPattern(PatternContract $pattern): self; 71 | 72 | /** 73 | * Registers multiple patterns in the Builder. 74 | * 75 | * @param array $patterns An array of pattern instances to be registered. 76 | * @return self Returns the Builder instance for fluent interface. 77 | */ 78 | public function registerPatterns(array $patterns): self; 79 | 80 | /** 81 | * Retrieves all registered patterns in the Builder. 82 | * 83 | * @return array An array of registered pattern instances. 84 | */ 85 | public function getPatterns(): array; 86 | 87 | /** 88 | * Sets returnGroups property 89 | * 90 | * @return self Returns the Builder instance. 91 | */ 92 | public function setReturnGroups(bool $enable): self; 93 | 94 | /** 95 | * Gets returnGroups property 96 | * 97 | * @return self Returns the Builder instance. 98 | */ 99 | public function getReturnGroups(): bool; 100 | 101 | /** 102 | * Magic method to handle dynamic method calls. 103 | * 104 | * This method is triggered when invoking inaccessible or non-existing methods. 105 | * It is used to dynamically handle pattern-specific method calls. 106 | * 107 | * @param string $name The name of the method being called. 108 | * @param array $args An array of arguments passed to the method. 109 | * @return self Returns the Builder instance for fluent interface. 110 | */ 111 | public function __call($name, $args): self; 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/Contracts/OptionContract.php: -------------------------------------------------------------------------------- 1 | source($str); 12 | } 13 | 14 | public static function source(string $str) { 15 | return (new Builder)->source($str); 16 | } 17 | 18 | public static function start(string $str) { 19 | return (new Builder)->source($str)->start(); 20 | } 21 | 22 | public static function customPattern(string $str) { 23 | return (new Builder)->source($str)->start(); 24 | } 25 | 26 | public static function builder() { 27 | return new Builder; 28 | } 29 | } -------------------------------------------------------------------------------- /src/EloquentRegexServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('eloquentregex', function ($app) { 12 | return new \Maestroerror\EloquentRegex\EloquentRegex(); 13 | }); 14 | } 15 | 16 | public function boot() 17 | { 18 | // booting code 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Facades/EloquentRegex.php: -------------------------------------------------------------------------------- 1 | validateUsingRegex($input); 20 | } 21 | 22 | public function build(): string { 23 | $patterns = []; 24 | 25 | if ($this->onlyVisa) { 26 | $patterns[] = '4[0-9]{12}(?:[0-9]{3})?'; // Visa card numbers 27 | } 28 | 29 | if ($this->onlyMasterCard) { 30 | $patterns[] = '5[1-5][0-9]{14}'; // MasterCard numbers 31 | } 32 | 33 | if ($this->onlyAmex) { 34 | $patterns[] = '^3[47][0-9]{13}$'; // American Express card numbers 35 | } 36 | 37 | return implode('|', $patterns); // Combine the patterns with OR 38 | } 39 | 40 | public function onlyVisa(bool $only = true): self { 41 | $this->onlyVisa = $only; 42 | return $this; 43 | } 44 | 45 | public function onlyMasterCard(bool $only = true): self { 46 | $this->onlyMasterCard = $only; 47 | return $this; 48 | } 49 | 50 | public function onlyAmex(bool $only = true): self { 51 | $this->onlyAmex = $only; 52 | return $this; 53 | } 54 | 55 | public function allowCardTypes(string $cardTypes): self { 56 | $types = explode(',', $cardTypes); 57 | 58 | foreach ($types as $type) { 59 | switch (trim(strtolower($type))) { 60 | case 'visa': 61 | $this->onlyVisa = true; 62 | break; 63 | case 'mastercard': 64 | $this->onlyMasterCard = true; 65 | break; 66 | case 'amex': 67 | $this->onlyAmex = true; 68 | break; 69 | // Add cases for additional card types if necessary 70 | } 71 | } 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Options/CharOption.php: -------------------------------------------------------------------------------- 1 | onlyLowercase) { 21 | if ($lowercaseCount != $inputCount) { 22 | return false; 23 | } 24 | } 25 | 26 | if ($this->onlyUppercase) { 27 | if ($uppercaseCount != $inputCount) { 28 | return false; 29 | } 30 | } 31 | 32 | if (!$this->checkSpecialChars($input)) { 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | public function build(): string { 39 | return ""; 40 | } 41 | 42 | private function checkSpecialChars(string $input) { 43 | // Special character count validation 44 | if ($this->minSpecialCharacters > 0 || $this->maxSpecialCharacters !== null) { 45 | $specialCharsCount = preg_match_all('/[^\w\s]/', $input); 46 | if ($this->minSpecialCharacters > 0 && $specialCharsCount < $this->minSpecialCharacters) { 47 | return false; // Not enough special characters 48 | } 49 | if ($this->maxSpecialCharacters !== null && $specialCharsCount > $this->maxSpecialCharacters) { 50 | return false; // Too many special characters 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | // Option methods: 57 | public function minSpecialCharacters(int $count) { 58 | $this->minSpecialCharacters = $count; 59 | return $this; 60 | } 61 | 62 | public function maxSpecialCharacters(int $count) { 63 | $this->maxSpecialCharacters = $count; 64 | return $this; 65 | } 66 | 67 | public function noSpecialCharacters(bool $disable = true) { 68 | if ($disable) { 69 | $this->maxSpecialCharacters = 0; 70 | } 71 | return $this; 72 | } 73 | 74 | public function onlyLowercase(bool $only = true) { 75 | $this->onlyLowercase = $only; 76 | return $this; 77 | } 78 | 79 | public function onlyUppercase(bool $only = true) { 80 | $this->onlyUppercase = $only; 81 | return $this; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Options/CharacterOption.php: -------------------------------------------------------------------------------- 1 | validateUsingRegex) { 20 | return $this->validateUsingRegex($input); 21 | } 22 | 23 | $uppercaseCount = preg_match_all('/[A-Z]/', $input); 24 | $lowercaseCount = preg_match_all('/[a-z]/', $input); 25 | 26 | if ($this->minUppercase > 0 && $uppercaseCount < $this->minUppercase) { 27 | return false; 28 | } 29 | 30 | if ($this->minLowercase > 0 && $lowercaseCount < $this->minLowercase) { 31 | return false; 32 | } 33 | 34 | foreach (str_split($input) as $char) { 35 | if (!empty($this->allowedCharacters) && !in_array($char, $this->allowedCharacters, true)) { 36 | return false; // Character not in the allowed list 37 | } 38 | 39 | if (in_array($char, $this->excludedCharacters, true)) { 40 | return false; // Character is in the excluded list 41 | } 42 | } 43 | 44 | return true; 45 | } 46 | 47 | public function build(): string { 48 | // Building the pattern based on allowed and excluded characters 49 | $pattern = ''; 50 | 51 | // Lookahead for minimum uppercase 52 | if ($this->minUppercase > 0) { 53 | $pattern .= '(?=(?:.*[A-Z]){' . $this->minUppercase . ',})'; 54 | } 55 | 56 | // Lookahead for minimum lowercase 57 | if ($this->minLowercase > 0) { 58 | $pattern .= '(?=(?:.*[a-z]){' . $this->minLowercase . ',})'; 59 | } 60 | 61 | // Handle allowed characters 62 | if (!empty($this->allowedCharacters)) { 63 | $allowedPattern = '[' . implode('', array_map('preg_quote', $this->allowedCharacters)) . ']+'; 64 | } else { 65 | // $allowedPattern = '.*'; // If no allowed characters are specified, allow anything. 66 | $allowedPattern = '.*'; // If no allowed characters are specified, allow anything. 67 | } 68 | 69 | // Handle excluded characters 70 | if (!empty($this->excludedCharacters)) { 71 | $excludedPattern = '(?!.*[' . implode('', array_map('preg_quote', $this->excludedCharacters)) . '])'; 72 | } else { 73 | $excludedPattern = ''; // If no excluded characters, no restriction. 74 | } 75 | 76 | $pattern .= $excludedPattern . $allowedPattern; 77 | 78 | return $pattern; 79 | } 80 | 81 | // Option methods 82 | public function allow(array $characters) { 83 | $this->allowedCharacters = $characters; 84 | return $this; 85 | } 86 | 87 | public function exclude(array $characters) { 88 | $this->excludedCharacters = $characters; 89 | return $this; 90 | } 91 | 92 | public function minUppercase(int $count) { 93 | $this->minUppercase = $count; 94 | return $this; 95 | } 96 | 97 | public function minLowercase(int $count) { 98 | $this->minLowercase = $count; 99 | return $this; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Options/ContainSpacesOption.php: -------------------------------------------------------------------------------- 1 | allowSpaces && strpos($input, ' ') !== false) { 15 | return false; 16 | } 17 | 18 | if ($this->noDoubleSpaces && preg_match('/\s{2,}/', $input)) { 19 | return false; 20 | } 21 | 22 | if ($this->maxSpaces !== null && substr_count($input, ' ') > $this->maxSpaces) { 23 | return false; 24 | } 25 | 26 | return true; 27 | } 28 | 29 | public function build(): string { 30 | // Not used as validation is done in PHP. 31 | return ""; 32 | } 33 | 34 | public function noSpaces(bool $disallow = true): self { 35 | $this->allowSpaces = !$disallow; 36 | return $this; 37 | } 38 | 39 | public function noDoubleSpaces(bool $disallow = true): self { 40 | $this->noDoubleSpaces = $disallow; 41 | return $this; 42 | } 43 | 44 | public function maxSpaces(int $max): self { 45 | $this->maxSpaces = $max; 46 | return $this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Options/CountryCodeOption.php: -------------------------------------------------------------------------------- 1 | countryCode === '') { 13 | return true; // If no country code is set, pass validation by default 14 | } 15 | 16 | return strpos($input, '+' . $this->countryCode) === 0 || 17 | strpos($input, $this->countryCode) === 0; 18 | } 19 | 20 | public function build(): string { 21 | // This method is not used as the validation is done in PHP and not with regex. 22 | return ""; 23 | } 24 | 25 | public function setCountryCode(string $code): self { 26 | $this->countryCode = $code; 27 | return $this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Options/DomainSpecificOption.php: -------------------------------------------------------------------------------- 1 | allowedDomains) && empty($this->allowedExtensions)) { 14 | return true; // If no specific domains or extensions are set, pass validation by default 15 | } 16 | 17 | foreach ($this->allowedDomains as $domain) { 18 | if (preg_match('/' . preg_quote($domain) . '$/', $input)) { 19 | return true; 20 | } 21 | } 22 | 23 | foreach ($this->allowedExtensions as $extension) { 24 | if (preg_match('/\.' . preg_quote($extension) . '$/', $input)) { 25 | return true; 26 | } 27 | } 28 | 29 | return false; 30 | } 31 | 32 | public function build(): string { 33 | // Not used as validation is done in PHP. 34 | return ""; 35 | } 36 | 37 | public function setAllowedDomains(array|string $domains): self { 38 | if (is_string($domains)) { 39 | $domains = explode(",", $domains); 40 | } 41 | $this->allowedDomains = $domains; 42 | return $this; 43 | } 44 | 45 | public function setAllowedExtensions(array|string $extensions): self { 46 | if (is_string($extensions)) { 47 | $extensions = explode(",", $extensions); 48 | } 49 | $this->allowedExtensions = $extensions; 50 | return $this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Options/FileExistsOption.php: -------------------------------------------------------------------------------- 1 | fileExists && !file_exists($input)) { 14 | return false; 15 | } 16 | 17 | return true; 18 | } 19 | 20 | public function build(): string { 21 | return ''; 22 | } 23 | 24 | public function fileExists(bool $check = true) { 25 | $this->fileExists = $check; 26 | return $this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Options/FileOption.php: -------------------------------------------------------------------------------- 1 | validateUsingRegex) { 19 | return $this->validateUsingRegex($input); 20 | } 21 | 22 | if ($this->isFile) { 23 | if ($this->fileExtension) { 24 | if (!preg_match("/\." . preg_quote($this->fileExtension) . "$/", $input)) { 25 | return false; 26 | } 27 | } elseif (!preg_match("/\.[a-zA-Z0-9]+$/", $input)) { 28 | return false; 29 | } 30 | } 31 | 32 | if ($this->isDirectory) { 33 | if (substr($input, -1) != '/') { 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | 41 | public function build(): string { 42 | if ($this->isFile) { 43 | if ($this->fileExtension) { 44 | return "[A-Za-z0-9\\/:\.\-\\\\]*\." . preg_quote($this->fileExtension); 45 | } else { 46 | return "[A-Za-z0-9\\/:\.\-\\\\]*\.[a-zA-Z0-9]+"; 47 | } 48 | } 49 | 50 | if ($this->isDirectory) { 51 | return "(?:[a-zA-Z0-9\\/:\-\\\\]+)+"; 52 | } 53 | 54 | return '.*'; 55 | } 56 | 57 | public function isFile(string|null $extension = null) { 58 | $this->isFile = true; 59 | $this->fileExtension = $extension; 60 | return $this; 61 | } 62 | 63 | public function isDirectory(int $check = 1) { 64 | $this->isDirectory = $check; 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Options/HtmlTagsOption.php: -------------------------------------------------------------------------------- 1 | restrictedTags as $tag) { 15 | $tag = trim($tag); 16 | if (strpos($input, "<$tag") !== false) { 17 | return false; 18 | } 19 | } 20 | 21 | // If allowed tags are specified, check if all tags in input are allowed 22 | if (!empty($this->allowedTags)) { 23 | preg_match_all('/<([a-z]+)[\s>]/i', $input, $matches); 24 | foreach ($matches[1] as $tag) { 25 | if (!in_array(strtolower($tag), $this->allowedTags)) { 26 | return false; 27 | } 28 | } 29 | } 30 | 31 | return true; 32 | } 33 | 34 | public function build(): string { 35 | // This method is not used for HTML tag validation 36 | return ""; 37 | } 38 | 39 | public function allowTags(array|string $tags): self { 40 | if (!is_array($tags)) { 41 | $tags = explode(",", $tags); 42 | } 43 | // Trim spaces 44 | $tags = array_map('trim', $tags); 45 | // Make lower 46 | $this->allowedTags = array_map('strtolower', $tags); 47 | return $this; 48 | } 49 | 50 | public function restrictTags(array|string $tags): self { 51 | if (!is_array($tags)) { 52 | $tags = explode(",", $tags); 53 | } 54 | // Trim spaces 55 | $tags = array_map('trim', $tags); 56 | // Make lower 57 | $this->restrictedTags = array_map('strtolower', $tags); 58 | return $this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Options/IPv6Option.php: -------------------------------------------------------------------------------- 1 | validate) { 13 | return filter_var($input, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; 14 | } 15 | return true; 16 | } 17 | 18 | public function build(): string { 19 | return ""; 20 | } 21 | 22 | // Option methods 23 | public function validIPv6(bool $check = true) { 24 | $this->validate = $check; 25 | return $this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Options/LengthOption.php: -------------------------------------------------------------------------------- 1 | exactLength !== null && $length !== $this->exactLength) { 17 | return false; 18 | } 19 | 20 | if ($this->minLength !== null && $length < $this->minLength) { 21 | return false; 22 | } 23 | 24 | if ($this->maxLength !== null && $length > $this->maxLength) { 25 | return false; 26 | } 27 | 28 | return true; 29 | } 30 | 31 | public function build(): string { 32 | if ($this->exactLength !== null) { 33 | return "{{$this->exactLength}}"; 34 | } 35 | 36 | $min = $this->minLength ?? ''; 37 | $max = $this->maxLength ?? ''; 38 | 39 | if ($min === '' && $max !== '') { 40 | $pattern = "{0,{$max}}"; 41 | } else { 42 | $pattern = "{{$min},{$max}}"; 43 | } 44 | 45 | return $pattern; 46 | } 47 | 48 | // Option methods 49 | public function minLength(int $length) { 50 | $this->minLength = $length; 51 | $this->exactLength = null; // Reset exact length if min or max length is set 52 | return $this; 53 | } 54 | 55 | public function maxLength(int $length) { 56 | $this->maxLength = $length; 57 | $this->exactLength = null; // Reset exact length if min or max length is set 58 | return $this; 59 | } 60 | 61 | public function exactLength(int $length) { 62 | $this->exactLength = $length; 63 | $this->minLength = null; 64 | $this->maxLength = null; 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Options/NumberOption.php: -------------------------------------------------------------------------------- 1 | exactValue !== null && $numericCount !== $this->exactValue) { 18 | return false; 19 | } 20 | 21 | if ($this->minValue !== null && $numericCount < $this->minValue) { 22 | return false; 23 | } 24 | 25 | if ($this->maxValue !== null && $numericCount > $this->maxValue) { 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | public function build(): string { 33 | // Check if exactValue is set 34 | if ($this->exactValue !== null) { 35 | return "\\d{{$this->exactValue}}"; 36 | } 37 | 38 | // Build the pattern based on minValue and maxValue 39 | $min = $this->minValue ?? ''; 40 | $max = $this->maxValue ?? ''; 41 | 42 | if ($min === '' && $max === '') { 43 | // If both min and max are not set, default to '\d+' 44 | $pattern = "\\d+"; 45 | } else { 46 | // Handle cases where min and/or max are set 47 | $pattern = "\\d{{$min},{$max}}"; 48 | } 49 | 50 | // Handling for only minValue or maxValue set 51 | if ($min === '' && $max !== '') { 52 | $pattern = "\\d{0,{$max}}"; // Use {0, max} instead of {, max} 53 | } 54 | 55 | return $pattern; 56 | } 57 | 58 | // Option methods 59 | public function setMinValue(int $minValue) { 60 | $this->minValue = $minValue; 61 | $this->exactValue = null; // Reset exact value if min or max value is set 62 | return $this; 63 | } 64 | 65 | public function setMaxValue(int $maxValue) { 66 | $this->maxValue = $maxValue; 67 | $this->exactValue = null; // Reset exact value if min or max value is set 68 | return $this; 69 | } 70 | 71 | public function setExactValue(int $exactValue) { 72 | $this->exactValue = $exactValue; 73 | $this->minValue = null; 74 | $this->maxValue = null; 75 | return $this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Options/OnlyAlphanumericOption.php: -------------------------------------------------------------------------------- 1 | validateUsingRegex) { 17 | return $this->validateUsingRegex($input); 18 | } 19 | 20 | if ($this->allowOnlyAlphanumeric) { 21 | // Check if input contains only alphanumeric characters 22 | return ctype_alnum($input); 23 | } 24 | return true; 25 | } 26 | 27 | public function build(): string { 28 | // Returns a regex pattern that matches alphanumeric characters if the option is enabled 29 | return $this->allowOnlyAlphanumeric ? '[a-zA-Z0-9]+' : '.+'; 30 | } 31 | 32 | public function onlyAlphanumeric(bool $only = true): self { 33 | $this->allowOnlyAlphanumeric = $only; 34 | return $this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Options/PathTypeOption.php: -------------------------------------------------------------------------------- 1 | relativePath) { 14 | return $this->isRelativePath($input); 15 | } 16 | 17 | if ($this->absolutePath) { 18 | return $this->isAbsolutePath($input); 19 | } 20 | 21 | // If neither relativePath nor absolutePath is specified, validation passes by default. 22 | return true; 23 | } 24 | 25 | public function build(): string { 26 | // This method is not used as the validation is done in PHP and not with regex. 27 | return ""; 28 | } 29 | 30 | private function isRelativePath(string $path): bool { 31 | return !preg_match('/^(?:\/|[a-zA-Z]:\\\\)/', $path); 32 | } 33 | 34 | private function isAbsolutePath(string $path): bool { 35 | return preg_match('/^(?:\/|[a-zA-Z]:\\\\)/', $path); 36 | } 37 | 38 | // Option methods 39 | public function setPathType(string|int $value = 0): self { 40 | if ($value) { 41 | if ($value == 1 || $value == "absolute") { 42 | $this->absolutePath = $value; 43 | } 44 | if ($value == 2 || $value == "relative") { 45 | $this->relativePath = $value; 46 | } 47 | } 48 | return $this; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Options/ProtocolOption.php: -------------------------------------------------------------------------------- 1 | allowedProtocols)) { 13 | return true; // If no specific protocols are set, pass validation by default. 14 | } 15 | 16 | foreach ($this->allowedProtocols as $protocol) { 17 | if (strpos($input, $protocol . '://') === 0) { 18 | return true; // The input starts with one of the allowed protocols. 19 | } 20 | } 21 | 22 | return false; // None of the allowed protocols matched. 23 | } 24 | 25 | public function build(): string { 26 | // This method is not used as the validation is done in PHP and not with regex. 27 | return ""; 28 | } 29 | 30 | public function onlyProtocol(string|array $protocol): self { 31 | if (is_array($protocol)) { 32 | $this->allowedProtocols = $protocol; 33 | } else { 34 | $this->allowedProtocols[] = $protocol; 35 | } 36 | return $this; 37 | } 38 | 39 | public function onlyHttp(bool $only = true): self { 40 | if ($only) { 41 | $this->allowedProtocols[] = 'http'; 42 | } 43 | return $this; 44 | } 45 | 46 | public function onlyHttps(bool $only = true): self { 47 | if ($only) { 48 | $this->allowedProtocols[] = 'https'; 49 | } 50 | return $this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Options/SpecificCurrenciesOption.php: -------------------------------------------------------------------------------- 1 | specificCurrencies)) { 16 | return true; // If no specific currencies are set, pass validation by default 17 | } 18 | 19 | // Build regex pattern for specific currencies 20 | $pattern = $this->build(); 21 | return preg_match("/$pattern/", $input) > 0; 22 | } 23 | 24 | public function build(): string { 25 | if (empty($this->specificCurrencies)) { 26 | return ''; // If no specific currencies are set, no pattern needed 27 | } 28 | 29 | $escapedCurrencies = array_map('preg_quote', $this->specificCurrencies); 30 | $pattern = implode('|', $escapedCurrencies); 31 | 32 | return $pattern; // Returns a regex pattern to match any of the specific currencies 33 | } 34 | 35 | public function setSpecificCurrencies(array|string $currencies): self { 36 | if (is_string($currencies)) { 37 | $currencies = explode(",", $currencies); 38 | } 39 | $this->specificCurrencies = $currencies; 40 | return $this; 41 | } 42 | 43 | public function onlyUSD($only = true) { 44 | if ($only) { 45 | $this->specificCurrencies = ["$"]; 46 | } 47 | return $this; 48 | } 49 | 50 | public function onlyEUR($only = true) { 51 | if ($only) { 52 | $this->specificCurrencies = ["€"]; 53 | } 54 | return $this; 55 | } 56 | 57 | public function onlyGBP($only = true) { 58 | if ($only) { 59 | $this->specificCurrencies = ["£"]; 60 | } 61 | return $this; 62 | } 63 | 64 | public function onlyGEL($only = true) { 65 | if ($only) { 66 | $this->specificCurrencies = ["₾"]; 67 | } 68 | return $this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/OptionsBuilder.php: -------------------------------------------------------------------------------- 1 | manager = new OptionsManager(); 28 | } 29 | 30 | /** 31 | * Magic method to handle dynamic method calls for setting options. 32 | * 33 | * This method intercepts calls to methods that are not explicitly defined in this class 34 | * and maps them to corresponding option methods defined in the OptionsMapper. 35 | * 36 | * @param string $name The name of the method being called. 37 | * @param array $arguments The arguments passed to the method. 38 | * @return $this Allows for method chaining. 39 | */ 40 | public function __call($name, $arguments) { 41 | // Get the option class and method from the OptionsMapper 42 | if (OptionsMapper::GetOptionMethodByName($name)) { 43 | $this->options[$name] = $arguments[0]; 44 | } 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * Retrieves the options set by the dynamic method calls. 51 | * 52 | * @return array An associative array of options and their set values. 53 | */ 54 | public function getOptions(): array { 55 | return $this->options; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/OptionsManager.php: -------------------------------------------------------------------------------- 1 | usedOptions; 28 | } 29 | 30 | /** 31 | * Builds option instances based on provided names and values. 32 | * For each option name and value, it identifies the corresponding class and method 33 | * to create and configure the option instance. 34 | * 35 | * @param array $optionNamesAndValues Associative array of option names and their values. 36 | */ 37 | public function buildOptions(array $optionNamesAndValues): void { 38 | foreach ($optionNamesAndValues as $name => $value) { 39 | $option = OptionsMapper::GetOptionMethodByName($name); 40 | $this->processOption($option, $value); 41 | } 42 | } 43 | 44 | /** 45 | * Processes an individual option by either updating an existing instance or creating a new one. 46 | * It calls the specific method of the option class to set its value. 47 | * 48 | * @param array $option The array containing the class and method name for the option. 49 | * @param mixed $value The value to set for the option. 50 | */ 51 | private function processOption(array $option, mixed $value): void { 52 | if (isset($this->usedOptions[$option[0]])) { 53 | // If the option instance already exists, update it with the new method and value. 54 | $this->usedOptions[$option[0]]->{$option[1]}($value); 55 | } else { 56 | // If the option instance does not exist, create it, call the method and set the value (arg). 57 | $this->usedOptions[$option[0]] = (new $option[0])->{$option[1]}($value); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/OptionsMapper.php: -------------------------------------------------------------------------------- 1 | [LengthOption::class, "minLength"], 36 | "maxLength" => [LengthOption::class, "maxLength"], 37 | "length" => [LengthOption::class, "exactLength"], 38 | 39 | "minNumbers" => [NumberOption::class, "setMinValue"], 40 | "maxNumbers" => [NumberOption::class, "setMaxValue"], 41 | "minDigits" => [NumberOption::class, "setMinValue"], 42 | "maxDigits" => [NumberOption::class, "setMaxValue"], 43 | "numberAmount" => [NumberOption::class, "setExactValue"], 44 | 45 | "onlyChars" => [CharacterOption::class, "allow"], 46 | "excludeChars" => [CharacterOption::class, "exclude"], 47 | "minUppercase" => [CharacterOption::class, "minUppercase"], 48 | "minLowercase" => [CharacterOption::class, "minLowercase"], 49 | 50 | "minSpecialChars" => [CharOption::class, "minSpecialCharacters"], 51 | "maxSpecialChars" => [CharOption::class, "maxSpecialCharacters"], 52 | "onlyLowercase" => [CharOption::class, "onlyLowercase"], 53 | "onlyUppercase" => [CharOption::class, "onlyUppercase"], 54 | "noSpecialChars" => [CharOption::class, "noSpecialCharacters"], 55 | 56 | "validIPv6" => [IPv6Option::class, "validIPv6"], 57 | 58 | "isFile" => [FileOption::class, "isFile"], 59 | "isDirectory" => [FileOption::class, "isDirectory"], 60 | 61 | "fileExists" => [FileExistsOption::class, "fileExists"], 62 | 63 | "specificCurrencies" => [SpecificCurrenciesOption::class, "setSpecificCurrencies"], 64 | "onlyUSD" => [SpecificCurrenciesOption::class, "onlyUSD"], 65 | "onlyEUR" => [SpecificCurrenciesOption::class, "onlyEUR"], 66 | "onlyGBP" => [SpecificCurrenciesOption::class, "onlyGBP"], 67 | "onlyGEL" => [SpecificCurrenciesOption::class, "onlyGEL"], 68 | 69 | "pathType" => [PathTypeOption::class, "setPathType"], 70 | 71 | "countryCode" => [CountryCodeOption::class, "setCountryCode"], 72 | 73 | "noSpaces" => [ContainSpacesOption::class, "noSpaces"], 74 | "noDoubleSpaces" => [ContainSpacesOption::class, "noDoubleSpaces"], 75 | "maxSpaces" => [ContainSpacesOption::class, "maxSpaces"], 76 | 77 | "onlyDomains" => [DomainSpecificOption::class, "setAllowedDomains"], 78 | "onlyExtensions" => [DomainSpecificOption::class, "setAllowedExtensions"], 79 | 80 | "onlyProtocol" => [ProtocolOption::class, "onlyProtocol"], 81 | "onlyHttp" => [ProtocolOption::class, "onlyHttp"], 82 | "onlyHttps" => [ProtocolOption::class, "onlyHttps"], 83 | 84 | "onlyVisa" => [CardTypeOption::class, "onlyVisa"], 85 | "onlyMasterCard" => [CardTypeOption::class, "onlyMasterCard"], 86 | "onlyAmex" => [CardTypeOption::class, "onlyAmex"], 87 | "cardTypes" => [CardTypeOption::class, "allowCardTypes"], 88 | 89 | "onlyAlphanumeric" => [OnlyAlphanumericOption::class, "onlyAlphanumeric"], 90 | 91 | "onlyTags" => [HtmlTagsOption::class, "allowTags"], 92 | "restrictTags" => [HtmlTagsOption::class, "restrictTags"], 93 | ]; 94 | 95 | /** 96 | * Retrieves the option method (class and method pair) by its name. 97 | * 98 | * @param string $optionName The name of the option method. 99 | * @return array An array containing the class and method for the specified option. 100 | * @throws \InvalidArgumentException If the option name does not exist in the mapping. 101 | */ 102 | public static function GetOptionMethodByName(string $optionName): array { 103 | if (!array_key_exists($optionName, self::$optionMethods)) { 104 | throw new \InvalidArgumentException("Option method not found: $optionName"); 105 | } 106 | return self::$optionMethods[$optionName]; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Patterns/BasePattern.php: -------------------------------------------------------------------------------- 1 | pattern; 57 | } 58 | 59 | /** 60 | * Sets the options for this pattern. 61 | * 62 | * @param array $options Array of options to be applied to the pattern. 63 | */ 64 | public function setOptions(array $options) { 65 | $this->options = $options; 66 | } 67 | 68 | /** 69 | * Adds an option to this pattern. 70 | * 71 | * @param OptionContract $option Option to be added. 72 | */ 73 | public function setOption(OptionContract $option) { 74 | $this->options[] = $option; 75 | } 76 | 77 | 78 | /** 79 | * Validates an input string against the pattern and its options as exact match. 80 | * 81 | * @param string $input The input string to validate. 82 | * @return bool True if the input string validates against the pattern and options, false otherwise. 83 | */ 84 | public function validateInput(string $input): bool { 85 | // Get the main pattern 86 | $mainPattern = $this->getInputValidationPattern(); 87 | 88 | // First, check if the entire input matches the main pattern 89 | if (!preg_match($mainPattern, $input)) { 90 | return false; 91 | } 92 | 93 | // Then, validate the input against each option 94 | return $this->validateOptions($input); 95 | } 96 | 97 | /** 98 | * Validates that the input string contains matches for the pattern, filtered by options. 99 | * 100 | * @param string $input The input string to validate. 101 | * @return bool True if there are any matches for the pattern in the input, after applying options. 102 | */ 103 | public function validateMatches(string $input): bool { 104 | // Get the main pattern for matches 105 | $mainPattern = $this->getMatchesValidationPattern(); 106 | 107 | // Find all matches for the main pattern in the input 108 | if (preg_match_all($mainPattern, $input, $matches) == 0) { 109 | return false; 110 | } 111 | 112 | // Filter these matches based on the options 113 | $filteredMatches = $this->filterByOptions($matches[0]); 114 | 115 | // Check if there are any matches left after filtering 116 | return count($filteredMatches) > 0; 117 | } 118 | 119 | /** 120 | * Retrieves all matches of the pattern in the input string, filtered by options. 121 | * 122 | * @param string $input The input string to search for matches. 123 | * @return array An array of matches. 124 | */ 125 | public function getMatches(string $input, bool $returnGroups = false): ?array { 126 | $mainPattern = $this->getMatchesValidationPattern(); 127 | preg_match_all($mainPattern, $input, $matches); 128 | 129 | if (!$matches[0]) { 130 | return null; 131 | } 132 | 133 | if ($returnGroups) { 134 | // Filter matches but keep indexes same 135 | $results = $this->filterByOptions($matches[0], false); 136 | // Unset matches and keep only groups 137 | unset($matches[0]); 138 | $groups = $matches; 139 | return [ 140 | "results" => $results, 141 | "groups" => $groups 142 | ]; 143 | } else { 144 | // Filter matches based on each option 145 | return $this->filterByOptions($matches[0]); 146 | } 147 | } 148 | 149 | /** 150 | * Filters an array of matches based on the options. 151 | * 152 | * @param array $allMatches Array of matches to be filtered. 153 | * @return array Filtered array of matches. 154 | */ 155 | protected function filterByOptions(array $allMatches, $fixArrayIndexes = true): array { 156 | // Use array_filter to keep only those matches that pass all options' validation 157 | $filtered = array_filter($allMatches, function($match) { 158 | return $this->validateOptions($match); 159 | }); 160 | 161 | if ($fixArrayIndexes) { 162 | return array_values($filtered); 163 | } else { 164 | return $filtered; 165 | } 166 | } 167 | 168 | /** 169 | * Validates an input string against all set options. 170 | * 171 | * @param string $input The input string to validate against the options. 172 | * @return bool True if the input string passes all options' validation, false otherwise. 173 | */ 174 | protected function validateOptions(string $input): bool { 175 | if (!empty($this->options)) { 176 | foreach ($this->options as $option) { 177 | if (!$option->validate($input)) { 178 | return false; 179 | } 180 | } 181 | } 182 | return true; 183 | } 184 | 185 | /** 186 | * Default implementation of generating regex patterns for input validation 187 | * 188 | * @return string The regex pattern for validating the entire input. 189 | */ 190 | public function getInputValidationPattern(): string { 191 | return "/^{$this->pattern}$/" . $this->expressionFlags; 192 | } 193 | 194 | /** 195 | * Default implementation of generating regex patterns for matches validation 196 | * 197 | * @return string The regex pattern for finding matches within the input. 198 | */ 199 | public function getMatchesValidationPattern(): string { 200 | return "/{$this->pattern}/" . $this->expressionFlags; 201 | } 202 | 203 | /** 204 | * Processes an array of arguments and builds an options array. 205 | * 206 | * @param array $args Names of the arguments. 207 | * @param array $values Values of the arguments. 208 | * @return array An associative array of options. 209 | */ 210 | protected static function processArguments(array $args, array $values): array { 211 | $options = []; 212 | // Build options array based on condition 213 | for ($i=0; $i < count($args); $i++) { 214 | if (isset($values[$i])) { 215 | // If value is true (so not "", 0, null) 216 | if ($values[$i]) { 217 | $options[$args[$i]] = $values[$i]; 218 | } 219 | } 220 | } 221 | 222 | return $options; 223 | } 224 | 225 | /** 226 | * Processes a callback function to configure options. 227 | * 228 | * @param callable $callback The callback function used for configuring options. 229 | * @return array An associative array of options set by the callback. 230 | */ 231 | protected static function processCallback(callable $callback): array { 232 | $optionsBuilder = new OptionsBuilder(); 233 | $callback($optionsBuilder); 234 | return $optionsBuilder->getOptions(); 235 | } 236 | 237 | /** 238 | * Adds a regex expression flag to the pattern. 239 | * 240 | * @param string $flag The single-character flag to add to the regex pattern. 241 | */ 242 | public function addExpressionFlag(string $flag): void { 243 | if (strpos($this->expressionFlags, $flag) === false) { 244 | $this->expressionFlags .= $flag; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Patterns/BuilderPattern.php: -------------------------------------------------------------------------------- 1 | builder = $builder; 51 | } 52 | 53 | // Builder class implementation methods START 54 | 55 | public function end(array|callable $config = []): BuilderContract { 56 | return $this->builder->setOptions($config); // Return the Builder object 57 | } 58 | 59 | public function get(): mixed { 60 | return $this->builder->get(); 61 | } 62 | 63 | public function check(): bool { 64 | return $this->builder->check(); 65 | } 66 | 67 | public function checkString(): bool { 68 | return $this->builder->checkString(); 69 | } 70 | 71 | public function count(): int { 72 | return $this->builder->count(); 73 | } 74 | 75 | public function toRegex(): string { 76 | return $this->builder->toRegex(); 77 | } 78 | 79 | public function replace(callable $replaceFunction): string { 80 | return $this->builder->replace($replaceFunction); 81 | } 82 | 83 | public function search(string|callable $keywordOrPattern): mixed { 84 | return $this->builder->search($keywordOrPattern); 85 | } 86 | 87 | public function searchReverse(string|callable $keywordOrPattern): mixed { 88 | return $this->builder->searchReverse($keywordOrPattern); 89 | } 90 | 91 | public function swap(string|callable $stringOrCallback): mixed { 92 | return $this->builder->swap($stringOrCallback); 93 | } 94 | 95 | // Builder class implementation methods END 96 | 97 | public function getInputValidationPattern(): string { 98 | return "/^{$this->pattern}$/" . $this->expressionFlags; 99 | } 100 | 101 | public function getMatchesValidationPattern(): string { 102 | return "/{$this->pattern}/" . $this->expressionFlags; 103 | } 104 | 105 | /** 106 | * Applies a quantifier to a given regex pattern. 107 | * 108 | * @param string $pattern The regex pattern to which the quantifier will be applied. 109 | * @param string|null $quantifier The quantifier to apply. Can be 'zeroOrMore', 'oneOrMore', or 'optional'. 110 | * @return string The modified pattern with the quantifier applied. 111 | */ 112 | private function applyQuantifier(string $pattern, string|null $q): string { 113 | 114 | if (!$q) { 115 | return $pattern; 116 | } 117 | 118 | if ($q == 'zeroOrMore' || $q == '0>' || $q == '0+' || $q == '*') { 119 | $p = "(?:" . $pattern . ')*'; 120 | return $this->lazy ? $this->addLazy($p) : $p; 121 | } elseif ($q == 'oneOrMore' || $q == '1>' || $q == '1+' || $q == '+') { 122 | $p = "(?:" . $pattern . ')+'; 123 | return $this->lazy ? $this->addLazy($p) : $p; 124 | } elseif ($q == 'optional' || $q == '?' || $q == '|') { 125 | $p = "(?:" . $pattern . ')?'; 126 | return $this->lazy ? $this->addLazy($p) : $p; 127 | } 128 | 129 | if (is_int($q)) { 130 | $p = "(?:" . $pattern . "){".$q."}"; 131 | return $this->lazy ? $this->addLazy($p) : $p; 132 | } elseif (preg_match("/^\d{1,10}$/", $q)) { 133 | $p = "(?:" . $pattern . '){'.$q.'}'; 134 | return $this->lazy ? $this->addLazy($p) : $p; 135 | } elseif (preg_match("/^\d{1,10},\d{1,10}$/", $q)) { 136 | $range = explode(",", $q); 137 | $f = $range[0]; 138 | $s = $range[1]; 139 | $p = "(?:" . $pattern . ")" . "{" . $f . "," . $s ."}"; 140 | return $this->lazy ? $this->addLazy($p) : $p; 141 | } 142 | 143 | return $pattern; 144 | } 145 | 146 | /** 147 | * Generates a regex quantifier string based on length parameters. 148 | * 149 | * @param int|null $length Exact length for the quantifier. 150 | * @param int $minLength Minimum length for the quantifier. 151 | * @param int $maxLength Maximum length for the quantifier. 152 | * @return string The generated regex quantifier string. 153 | */ 154 | private function getLengthOption(int|null $length = null, int $minLength = 0, int $maxLength = 0): string { 155 | if (is_int($length) && $length > 0) { 156 | $qntf = "{" . $length . "}"; 157 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 158 | } elseif ($length === 0 || $this->inCharSet) { 159 | return ""; 160 | } 161 | 162 | if ($minLength > 0 && $maxLength > 0) { 163 | $qntf = "{" . $minLength . "," . $maxLength . "}"; 164 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 165 | } else if ($minLength > 0) { 166 | $qntf = "{" . $minLength . ",}"; 167 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 168 | } else if ($maxLength > 0) { 169 | $qntf = "{0," . $maxLength . "}"; 170 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 171 | } 172 | 173 | $qntf = "+"; // Default case, one or more times 174 | return $this->lazy ? $this->addLazy($qntf) : $qntf; 175 | } 176 | 177 | /** 178 | * Adds a lazy (non-greedy) modifier to a quantifier 179 | * and sets $lazy to false for ensuring single use 180 | * 181 | * @param string $quantifier The quantifier to which the lazy modifier will be added. 182 | * @return string The quantifier with the lazy modifier applied. 183 | */ 184 | private function addLazy($quantifier): string { 185 | $this->lazy = false; 186 | return $quantifier . "?"; 187 | } 188 | 189 | /** 190 | * Creates a lazy (non-greedy) quantifier for the next method call. 191 | * 192 | * @return self 193 | */ 194 | public function lazy(): self { 195 | $this->lazy = true; 196 | return $this; 197 | } 198 | 199 | public function inCharSet(): self { 200 | $this->inCharSet = true; 201 | return $this; 202 | } 203 | 204 | 205 | } -------------------------------------------------------------------------------- /src/Patterns/CreditCardNumberPattern.php: -------------------------------------------------------------------------------- 1 | pattern}$/u"; 24 | } 25 | 26 | public function getMatchesValidationPattern(): string { 27 | return "/{$this->pattern}/u"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Patterns/DatePattern.php: -------------------------------------------------------------------------------- 1 | |\r\n\s]+(?:\/[^\/:*,?\"<>|\r\n\s]+)+\/?(?:\.[a-zA-Z0-9]+)?"; 14 | 15 | public static string $name = "filePath"; 16 | 17 | public static array $args = [ 18 | "isDirectory", 19 | "isFile", 20 | "fileExists", 21 | "pathType" 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /src/Patterns/FilePathWinPattern.php: -------------------------------------------------------------------------------- 1 | |\\r\\n]*)"; 15 | 16 | public static string $name = "filePathWin"; 17 | 18 | public static array $args = [ 19 | "isDirectory", 20 | "isFile", 21 | "fileExists", 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /src/Patterns/HtmlTagPattern.php: -------------------------------------------------------------------------------- 1 | ]*>(.*?)<\/\\1>"; 13 | 14 | public static string $name = "htmlTag"; 15 | 16 | public static array $args = [ 17 | "restrictTags", 18 | "onlyTags", 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /src/Patterns/IPAddressPattern.php: -------------------------------------------------------------------------------- 1 | true 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/Patterns/PasswordPattern.php: -------------------------------------------------------------------------------- 1 | ]"; 15 | 16 | public static string $name = "password"; 17 | 18 | public static array $args = [ 19 | "minLength", 20 | "minUppercase", 21 | "minNumbers", 22 | "minSpecialChars" 23 | ]; 24 | 25 | 26 | public function getInputValidationPattern(): string { 27 | return "/^{$this->pattern}+$/"; 28 | } 29 | 30 | public function getMatchesValidationPattern(): string { 31 | return "/{$this->pattern}+/"; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Patterns/PhonePattern.php: -------------------------------------------------------------------------------- 1 | 2 28 | ]; 29 | 30 | 31 | public function getInputValidationPattern(): string { 32 | return "/^{$this->pattern}+$/"; 33 | } 34 | 35 | public function getMatchesValidationPattern(): string { 36 | return "/{$this->pattern}+/"; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Patterns/TimePattern.php: -------------------------------------------------------------------------------- 1 | pattern .= '\\b'; 16 | return $this; 17 | } 18 | 19 | /** 20 | * Adds a word border marker at the current position in the pattern. 21 | * Note: This method is similar to 'wordBoundary' and can be used interchangeably. 22 | * 23 | * @return self The current instance of the BuilderPattern for method chaining. 24 | */ 25 | public function wordBorder(): self { 26 | $this->pattern .= '\\b'; 27 | return $this; 28 | } 29 | 30 | /** 31 | * Encloses the current pattern within word boundaries. 32 | * This ensures that the pattern matches only a complete word. 33 | * 34 | * @return self The current instance of the BuilderPattern for method chaining. 35 | */ 36 | public function asWord(): self { 37 | $this->pattern = '\\b' . $this->pattern . '\\b'; 38 | return $this; 39 | } 40 | 41 | /** 42 | * Adds a start of line marker at the start of the pattern. 43 | * 44 | * @return self The current instance of the BuilderPattern for method chaining. 45 | */ 46 | public function useStringBeginning(): self { 47 | $this->pattern = '^' . $this->pattern; 48 | return $this; 49 | } 50 | 51 | /** 52 | * Adds a start of line marker at the start of the pattern. 53 | * 54 | * @return self The current instance of the BuilderPattern for method chaining. 55 | */ 56 | public function useStringEnd(): self { 57 | $this->pattern = $this->pattern . '$'; 58 | return $this; 59 | } 60 | 61 | // Anchors END 62 | } 63 | -------------------------------------------------------------------------------- /src/Traits/BuilderPatternTraits/GroupsTrait.php: -------------------------------------------------------------------------------- 1 | getPattern() . ']'; 18 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 19 | return $this; 20 | } 21 | 22 | /** 23 | * Adds a new set of denied characters. 24 | * 25 | * @param callable $callback A callback that receives a BuilderPattern instance to define the subpattern. 26 | * @param ?string $q a Quantifier 27 | * @return self 28 | */ 29 | public function negativeCharSet(callable $callback, ?string $q = null): self { 30 | $subPattern = new self(); 31 | $subPattern->inCharSet(); 32 | $callback($subPattern); 33 | $p = '[^' . $subPattern->getPattern() . ']'; 34 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 35 | return $this; 36 | } 37 | 38 | /** 39 | * Adds a new grouped subpattern. 40 | * 41 | * @param callable $callback A callback that receives a BuilderPattern instance to define the subpattern. 42 | * @param ?string $q a Quantifier 43 | * @return self 44 | */ 45 | public function group(callable $callback, ?string $q = null): self { 46 | $this->builder->setReturnGroups(true); 47 | $subPattern = new self(); 48 | $callback($subPattern); 49 | $p = '(' . $subPattern->getPattern() . ')'; 50 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 51 | return $this; 52 | } 53 | 54 | /** 55 | * Adds a new grouped subpattern. 56 | * 57 | * @param callable $callback A callback that receives a BuilderPattern instance to define the subpattern. 58 | * @param string $name of a group 59 | * @param ?string $q a Quantifier 60 | * @return self 61 | */ 62 | public function namedGroup(callable $callback, string $name, ?string $q = null): self { 63 | $this->builder->setReturnGroups(true); 64 | $this->builder->setNamedGroups(true); 65 | $subPattern = new self(); 66 | $callback($subPattern); 67 | $p = $subPattern->getPattern(); 68 | $p = "(?P<$name>" . $p . ')'; 69 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 70 | return $this; 71 | } 72 | 73 | /** 74 | * Adds a new non-capturing grouped subpattern. 75 | * 76 | * @param callable $callback A callback that receives a BuilderPattern instance to define the subpattern. 77 | * @param ?string $q a Quantifier 78 | * @return self 79 | */ 80 | public function nonCapturingGroup(callable $callback, ?string $q = null): self { 81 | $subPattern = new self(); 82 | $callback($subPattern); 83 | $p = '(?:' . $subPattern->getPattern() . ')'; 84 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 85 | return $this; 86 | } 87 | 88 | /** 89 | * Adds an alternation pattern. 90 | * 91 | * @param callable $callback A callback that receives a BuilderPattern instance to define the alternation. 92 | * @param ?string $q a Quantifier 93 | * @return self 94 | */ 95 | public function orPattern(callable $callback, ?string $q = null): self { 96 | $builder = new self(); 97 | $callback($builder); 98 | $p = $builder->getPattern(); 99 | $this->pattern .= $q ? '|' . $this->applyQuantifier($p, $q) : '|' . $p; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Adds a positive lookahead assertion. 105 | * 106 | * @param callable $callback A callback that receives a BuilderPattern instance to define the assertion. 107 | * @return self 108 | */ 109 | public function lookAhead(callable $callback): self { 110 | $builder = new self(); 111 | $callback($builder); 112 | $this->pattern .= '(?=' . $builder->getPattern() . ')'; 113 | return $this; 114 | } 115 | 116 | /** 117 | * Adds a positive lookbehind assertion. 118 | * 119 | * @param callable $callback A callback that receives a BuilderPattern instance to define the assertion. 120 | * @return self 121 | */ 122 | public function lookBehind(callable $callback): self { 123 | $builder = new self(); 124 | $callback($builder); 125 | $this->pattern .= '(?<=' . $builder->getPattern() . ')'; 126 | return $this; 127 | } 128 | 129 | /** 130 | * Adds a negative lookahead assertion. 131 | * 132 | * @param callable $callback A callback that receives a BuilderPattern instance to define the assertion. 133 | * @return self 134 | */ 135 | public function negativeLookAhead(callable $callback): self { 136 | $builder = new self(); 137 | $callback($builder); 138 | $this->pattern .= '(?!' . $builder->pattern . ')'; 139 | return $this; 140 | } 141 | 142 | /** 143 | * Adds a negative lookbehind assertion. 144 | * 145 | * @param callable $callback A callback that receives a BuilderPattern instance to define the assertion. 146 | * @return self 147 | */ 148 | public function negativeLookBehind(callable $callback): self { 149 | $builder = new self(); 150 | $callback($builder); 151 | $this->pattern .= '(?pattern . ')'; 152 | return $this; 153 | } 154 | 155 | /** 156 | * Adds a raw regex string to the pattern. 157 | * 158 | * @param string $regex The raw regex string to add. 159 | * @return self 160 | */ 161 | public function addRawRegex(string $regex): self { 162 | $this->pattern .= $regex; 163 | return $this; 164 | } 165 | 166 | /** 167 | * Wraps a given regex string in a non-capturing group and adds it to the pattern. 168 | * 169 | * @param string $regex The regex string to wrap and add. 170 | * @return self 171 | */ 172 | public function addRawNonCapturingGroup(string $regex, ?string $q = null): self { 173 | $p = '(?:' . $regex . ')'; 174 | $this->pattern .= $q ? $this->applyQuantifier($p, $q) : $p; 175 | return $this; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/Traits/BuilderPatternTraits/SpecificCharsTrait.php: -------------------------------------------------------------------------------- 1 | escapeArray($string); 12 | $escapedString = "(?:" . implode("|", $string) . ")"; 13 | } else { 14 | $escapedString = preg_quote($string, '/'); 15 | } 16 | $pattern = $caseSensitive ? $escapedString : "(?i)" . $escapedString; 17 | $this->pattern .= $this->applyQuantifier($pattern, $quantifier); 18 | return $this; 19 | } 20 | 21 | private function escapeAndAdd(string $char, $quantifier = null): self { 22 | $escapedChar = preg_quote($char, '/'); 23 | $this->pattern .= $quantifier ? $this->applyQuantifier($escapedChar, $quantifier) : $escapedChar; 24 | return $this; 25 | } 26 | 27 | private function escapeArray(array $arr) { 28 | return array_map(function ($item) { 29 | return preg_quote($item, '/'); 30 | }, $arr); 31 | } 32 | 33 | // Exact string methods: 34 | 35 | public function exact(string|array $string, $caseSensitive = true, $quantifier = null): self { 36 | return $this->handleExact($string, $caseSensitive, $quantifier); 37 | } 38 | 39 | public function exactly(string|array $string, $caseSensitive = true, $quantifier = null): self { 40 | return $this->handleExact($string, $caseSensitive, $quantifier); 41 | } 42 | 43 | public function literal(string|array $string, $caseSensitive = true, $quantifier = null): self { 44 | return $this->handleExact($string, $caseSensitive, $quantifier); 45 | } 46 | 47 | public function character(string $char, $caseSensitive = true, $quantifier = null): self { 48 | return $this->handleExact($char, $caseSensitive, $quantifier); 49 | } 50 | 51 | public function char(string $char, $caseSensitive = true, $quantifier = null): self { 52 | return $this->handleExact($char, $caseSensitive, $quantifier); 53 | } 54 | 55 | // Specific Characters: 56 | 57 | public function tab(): self { 58 | $this->pattern .= "\\t"; // Matches a tab character 59 | return $this; 60 | } 61 | 62 | public function newLine(): self { 63 | $this->pattern .= "\\n"; // Matches a newline character 64 | return $this; 65 | } 66 | 67 | public function carriageReturn(): self { 68 | $this->pattern .= "\\r"; // Matches a carriage return character 69 | return $this; 70 | } 71 | 72 | public function verticalTab(): self { 73 | $this->pattern .= "\\v"; // Matches a vertical tab character 74 | return $this; 75 | } 76 | 77 | public function formFeed(): self { 78 | $this->pattern .= "\\f"; // Matches a form feed character 79 | return $this; 80 | } 81 | 82 | public function dash(string|null $q = null) { 83 | return $this->escapeAndAdd("-", $q); 84 | } 85 | 86 | public function dot(string|null $q = null): self { 87 | return $this->escapeAndAdd(".", $q); // Matches dot "." character 88 | } 89 | 90 | public function space(string|null $q = null) { 91 | return $this->escapeAndAdd(" ", $q); 92 | } 93 | 94 | public function backslash(string|null $q = null): self { 95 | return $this->escapeAndAdd("\\", $q); 96 | } 97 | 98 | public function forwardSlash(string|null $q = null): self { 99 | return $this->escapeAndAdd("/", $q); 100 | } 101 | 102 | public function slash(string|null $q = null): self { 103 | return $this->escapeAndAdd("/", $q); 104 | } 105 | 106 | public function doubleSlash(string|null $q = null): self { 107 | return $this->escapeAndAdd("//", $q); 108 | } 109 | 110 | public function underscore(string|null $q = null): self { 111 | return $this->escapeAndAdd("_", $q); 112 | } 113 | 114 | public function pipe(string|null $q = null): self { 115 | return $this->escapeAndAdd("|", $q); 116 | } 117 | 118 | public function ampersand(string|null $q = null): self { 119 | return $this->escapeAndAdd("&", $q); 120 | } 121 | 122 | public function asterisk(string|null $q = null): self { 123 | return $this->escapeAndAdd("*", $q); 124 | } 125 | 126 | public function plus(string|null $q = null): self { 127 | return $this->escapeAndAdd("+", $q); 128 | } 129 | 130 | public function questionMark(string|null $q = null): self { 131 | return $this->escapeAndAdd("?", $q); 132 | } 133 | 134 | public function atSign(string|null $q = null): self { 135 | return $this->escapeAndAdd("@", $q); 136 | } 137 | 138 | public function atSymbol(string|null $q = null): self { 139 | return $this->escapeAndAdd("@", $q); 140 | } 141 | 142 | public function exclamationMark(string|null $q = null): self { 143 | return $this->escapeAndAdd("!", $q); 144 | } 145 | 146 | public function period(string|null $q = null): self { 147 | return $this->escapeAndAdd(".", $q); 148 | } 149 | 150 | public function comma(string|null $q = null): self { 151 | return $this->escapeAndAdd(",", $q); 152 | } 153 | 154 | public function semicolon(string|null $q = null): self { 155 | return $this->escapeAndAdd(";", $q); 156 | } 157 | 158 | public function colon(string|null $q = null): self { 159 | return $this->escapeAndAdd(":", $q); 160 | } 161 | 162 | public function equalSign(string|null $q = null): self { 163 | return $this->escapeAndAdd("=", $q); 164 | } 165 | 166 | public function tilde(string|null $q = null): self { 167 | return $this->escapeAndAdd("~", $q); 168 | } 169 | 170 | public function hyphen(string|null $q = null): self { 171 | return $this->escapeAndAdd("-", $q); 172 | } 173 | 174 | public function minus(string|null $q = null): self { 175 | return $this->escapeAndAdd("-", $q); 176 | } 177 | 178 | public function doubleQuote(string|null $q = null): self { 179 | return $this->escapeAndAdd("\"", $q); 180 | } 181 | 182 | public function singleQuote(string|null $q = null): self { 183 | return $this->escapeAndAdd("'", $q); 184 | } 185 | 186 | public function percent(string|null $q = null): self { 187 | return $this->escapeAndAdd("%", $q); 188 | } 189 | 190 | public function dollar(string|null $q = null): self { 191 | return $this->escapeAndAdd("$", $q); 192 | } 193 | 194 | public function hash(string|null $q = null): self { 195 | return $this->escapeAndAdd("#", $q); 196 | } 197 | 198 | public function hashtag(string|null $q = null): self { 199 | return $this->escapeAndAdd("#", $q); 200 | } 201 | 202 | public function backtick(string|null $q = null): self { 203 | return $this->escapeAndAdd("`", $q); 204 | } 205 | 206 | public function caret(string|null $q = null): self { 207 | return $this->escapeAndAdd("^", $q); 208 | } 209 | 210 | public function unicode($code): self { 211 | $this->pattern .= "\\x{" . dechex($code) . "}"; 212 | $this->addExpressionFlag("u"); 213 | return $this; 214 | } 215 | 216 | // Methods for paired characters with separate open and close methods and an extra method with a boolean argument 217 | 218 | public function openSquareBracket(string|null $q = null): self { 219 | return $this->escapeAndAdd("[", $q); 220 | } 221 | 222 | public function closeSquareBracket(string|null $q = null): self { 223 | return $this->escapeAndAdd("]", $q); 224 | } 225 | 226 | public function squareBracket($isOpen = true): self { 227 | return $isOpen ? $this->openSquareBracket() : $this->closeSquareBracket(); 228 | } 229 | 230 | public function openCurlyBrace(string|null $q = null): self { 231 | return $this->escapeAndAdd("{", $q); 232 | } 233 | 234 | public function closeCurlyBrace(string|null $q = null): self { 235 | return $this->escapeAndAdd("}", $q); 236 | } 237 | 238 | public function curlyBrace($isOpen = true): self { 239 | return $isOpen ? $this->openCurlyBrace() : $this->closeCurlyBrace(); 240 | } 241 | 242 | public function openParenthesis(string|null $q = null): self { 243 | return $this->escapeAndAdd("(", $q); 244 | } 245 | 246 | public function closeParenthesis(string|null $q = null): self { 247 | return $this->escapeAndAdd(")", $q); 248 | } 249 | 250 | public function parenthesis($isOpen = true): self { 251 | return $isOpen ? $this->openParenthesis() : $this->closeParenthesis(); 252 | } 253 | 254 | public function openAngleBracket(string|null $q = null): self { 255 | return $this->escapeAndAdd("<", $q); 256 | } 257 | 258 | public function closeAngleBracket(string|null $q = null): self { 259 | return $this->escapeAndAdd(">", $q); 260 | } 261 | 262 | public function angleBracket($isOpen = true): self { 263 | return $isOpen ? $this->openAngleBracket() : $this->closeAngleBracket(); 264 | } 265 | 266 | } 267 | -------------------------------------------------------------------------------- /src/Traits/BuilderTraits/BuilderPatternMethods.php: -------------------------------------------------------------------------------- 1 | pattern = new BuilderPattern($this); 27 | return $this->pattern; 28 | } 29 | 30 | /** 31 | * Creates a custom regex pattern using a callback function. 32 | * 33 | * This method allows the user to define a custom regex pattern using the BuilderPattern class. 34 | * If a callback is provided, it is executed with the BuilderPattern instance as an argument, 35 | * allowing the user to define the pattern using method chaining. If no callback is provided, 36 | * it simply initiates a new BuilderPattern instance. 37 | * 38 | * @param callable|null $callback A callback function that receives a BuilderPattern instance to define the regex pattern. 39 | * @return BuilderContract|BuilderPattern Returns the main Builder instance after the pattern is defined. 40 | */ 41 | public function pattern(callable|null $callback = null): BuilderContract|BuilderPattern { 42 | if (is_null($callback)) { 43 | return $this->start(); 44 | } 45 | // Pass the current Builder instance to the BuilderPattern 46 | $this->pattern = new BuilderPattern($this); 47 | // Run callback to create pattern 48 | $callback($this->pattern); 49 | // return back the Builder object 50 | return $this->pattern->end(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Traits/BuilderTraits/InitMethods.php: -------------------------------------------------------------------------------- 1 | setString($str); 17 | return $this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Traits/IsOptionalTrait.php: -------------------------------------------------------------------------------- 1 | isOptional = true; 11 | return $this; 12 | } 13 | 14 | public function isOptional(): bool { 15 | return $this->isOptional; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/Pattern.php: -------------------------------------------------------------------------------- 1 | $value) { 41 | if(!isset($options[$option])) { 42 | $options[$option] = $value; 43 | } 44 | } 45 | } 46 | return $options; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Traits/ValidateUsingRegexTrait.php: -------------------------------------------------------------------------------- 1 | build() . "$/"; 8 | return preg_match($regex, $input) === 1; 9 | } 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /swapTest.php: -------------------------------------------------------------------------------- 1 | namedGroup(function ($pattern) { 10 | return $pattern->textUppercase(2); 11 | }, "project", 1) 12 | ->dash() 13 | ->namedGroup(function ($pattern) { 14 | return $pattern->digitsRange(2, 4); 15 | }, "issue", 1)->end(); 16 | 17 | $results = $result->swap(function ($data) { 18 | return "In project '" . $data["project"] . "' issue #" . $data["issue"] . " is in progress"; 19 | }); 20 | 21 | print_r($results); 22 | 23 | 24 | $builder = EloquentRegex::start("/container-tbilisi-1585"); 25 | $result = $builder->slash() 26 | ->exact("container") 27 | ->dash() 28 | ->namedGroup(function ($pattern) { 29 | return $pattern->text(); 30 | }, "CITY", 1) 31 | ->dash() 32 | ->namedGroup(function ($pattern) { 33 | return $pattern->digitsRange(2, 5); 34 | }, "id", 1)->end(); 35 | 36 | $results = $result->swap("/container/[ID]?city=[CITY]"); 37 | 38 | print_r($results); -------------------------------------------------------------------------------- /tests/Feature/BuilderPatternTest.php: -------------------------------------------------------------------------------- 1 | start()->exact("alt=")->group(function ($pattern) { 9 | $pattern->doubleQuote()->orPattern(function ($pattern) { 10 | $pattern->singleQuote(); 11 | }); 12 | })->toRegex(); 13 | 14 | 15 | expect($regex)->toBe("alt\=(\"|')"); 16 | }); 17 | 18 | it('reproduces hashtag prefix pattern from HSA', function () { 19 | $builder = new Builder(""); 20 | 21 | $regex = $builder->start()->lookBehind(function ($pattern) { 22 | $pattern->charSet(function ($pattern) { 23 | $pattern->doubleQuote()->closeAngleBracket()->addRawRegex("\\s"); 24 | }); 25 | })->hash()->toRegex(); 26 | 27 | expect($regex)->toBe('(?<=["\>\s])\#'); 28 | }); 29 | 30 | it('reproduces Text suffix pattern from HSA', function () { 31 | $builder = new Builder(""); 32 | 33 | $regex = $builder->start() 34 | ->openAngleBracket()->slash()->alphanumericRange(0, 10)->closeAngleBracket() 35 | ->toRegex(); 36 | 37 | expect($regex)->toBe('\<\/[a-zA-Z0-9]{0,10}\>'); 38 | }); 39 | 40 | it('constructs regex for simple email validation', function () { 41 | $builder = new Builder(); 42 | 43 | $regex = $builder->start() 44 | ->textLowercase() 45 | ->atSymbol() 46 | ->textLowercase() 47 | ->dot() 48 | ->textLowercaseRange(2, 4) 49 | ->toRegex(); 50 | 51 | expect($regex)->toBe('[a-z]+@[a-z]+\.[a-z]{2,4}'); 52 | }); 53 | 54 | it('constructs regex for URL validation', function () { 55 | $builder = new Builder(); 56 | 57 | $regex = $builder->start() 58 | ->exact(['http', 'https']) 59 | ->colon() 60 | ->doubleSlash() 61 | ->text() 62 | ->dot() 63 | ->text() 64 | ->toRegex(); 65 | 66 | expect($regex)->toBe('(?:http|https)\:\/\/[a-zA-Z]+\.[a-zA-Z]+'); 67 | }); 68 | 69 | it('constructs regex for specific phone number format', function () { 70 | $builder = new Builder(); 71 | 72 | $regex = $builder->start() 73 | ->openParenthesis() 74 | ->digits(3) 75 | ->closeParenthesis() 76 | ->space() 77 | ->digits(3) 78 | ->dash() 79 | ->digits(4) 80 | ->toRegex(); 81 | 82 | expect($regex)->toBe('\(\d{3}\) \d{3}\-\d{4}'); 83 | }); 84 | 85 | it('extracts dates in specific format from text', function () { 86 | $builder = new Builder("Meeting on 2021-09-15 and 2021-10-20"); 87 | 88 | $matches = $builder->start() 89 | ->digits(4) 90 | ->dash() 91 | ->digits(2) 92 | ->dash() 93 | ->digits(2) 94 | ->get(); 95 | 96 | expect($matches)->toEqual(['2021-09-15', '2021-10-20']); 97 | }); 98 | 99 | it('validates usernames in a string', function () { 100 | $builder = new Builder("Users: user_123, JohnDoe99"); 101 | 102 | $check = $builder->start() 103 | ->alphanumeric() 104 | ->underscore() 105 | ->digitsRange(0, 2) 106 | ->checkString(); 107 | 108 | expect($check)->toBeTrue(); 109 | }); 110 | 111 | it('extracts hashtags from text', function () { 112 | $builder = new Builder("#hello #world This is a #test"); 113 | 114 | $matches = $builder->start() 115 | ->hash() 116 | ->text() 117 | ->get(); 118 | 119 | expect($matches)->toEqual(['#hello', '#world', '#test']); 120 | }); 121 | 122 | it('extracts secret coded messages from text', function () { 123 | $text = "Normal text {secret: message one} more text {secret: another hidden text} end"; 124 | $builder = new Builder($text); 125 | 126 | // Pattern: Look for curly braces containing 'secret: ' followed by any characters (non-greedy) 127 | $matches = $builder->start() 128 | ->lookBehind(function ($pattern) { 129 | $pattern->openCurlyBrace()->exact('secret: '); 130 | }) 131 | ->lazy()->anyChars() 132 | ->lookAhead(function ($pattern) { 133 | $pattern->closeCurlyBrace(); 134 | }) 135 | ->get(); 136 | 137 | // Expected secret messages are 'secret: message one' and 'secret: another hidden text' 138 | expect($matches)->toEqual(['message one', 'another hidden text']); 139 | }); 140 | 141 | -------------------------------------------------------------------------------- /tests/Feature/Patterns/CreditCardNumberPatternTest.php: -------------------------------------------------------------------------------- 1 | creditCardNumber()->get(); 13 | 14 | expect($matches)->toEqual([ 15 | '4111 1111 1111 1111', 16 | '5500 0000 0000 0004', 17 | '3400 000000 00009', 18 | '4111-1111-1111-1111', 19 | '5500-0000-0000-0004', 20 | '3400-000000-00009', 21 | '4111111111111111', 22 | '5500000000000004', 23 | '340000000000009', 24 | ]); 25 | }); 26 | 27 | it('does not match invalid credit card numbers', function () { 28 | $string = "Invalid: 1234 5678 9012 3456, Another: 0000 0000 0000 0000, Another 2400 000000 00009"; 29 | $builder = new Builder($string); 30 | $matches = $builder->creditCardNumber()->get(); 31 | 32 | expect($matches)->toBeEmpty(); 33 | }); 34 | 35 | 36 | it('checks valid credit card number', function () { 37 | $string = "4111 1111 1111 1111"; 38 | $builder = new Builder($string); 39 | $check = $builder->creditCardNumber()->check(); 40 | 41 | expect($check)->toBeTrue(); 42 | }); 43 | 44 | 45 | it('checks invalid credit card number correctly', function () { 46 | $string = "1111 1111 1111 1111"; 47 | $builder = new Builder($string); 48 | $check = $builder->creditCardNumber()->check(); 49 | 50 | expect($check)->toBeFalse(); 51 | }); 52 | 53 | 54 | it('gets only needed card types', function () { 55 | $string = "Visa: 4111 1111 1111 1111, MasterCard: 5500 0000 0000 0004, Amex: 3400 000000 00009"; 56 | $builder = new Builder($string); 57 | $check = $builder->creditCardNumber("visa, amex")->get(); 58 | 59 | expect($check)->toEqual([ 60 | "4111 1111 1111 1111", 61 | "3400 000000 00009" 62 | ]); 63 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/CurrencyPatternTest.php: -------------------------------------------------------------------------------- 1 | currency()->get(); 9 | 10 | // Assert that the returned matches are as expected 11 | expect($matches)->toEqual(['$1,000.00', '€200', '£30.5', '¥1,250']); 12 | }); 13 | 14 | it('does not match invalid currency formats', function () { 15 | $builder = new Builder("1000, 200.999, 30,50$"); 16 | 17 | $check = $builder->currency()->checkString(); 18 | 19 | // Assert that invalid currency formats are not matched 20 | expect($check)->toBeFalse(); 21 | }); 22 | 23 | it('matches currencies with a minimum number of digits', function () { 24 | $builder = new Builder("$100, €20, £3"); 25 | 26 | $matches = $builder->currency(2)->get(); // minDigits 27 | 28 | // Assert that only currency amounts with at least 2 digits are matched 29 | expect($matches)->toEqual(['$100', '€20']); 30 | }); 31 | 32 | it('matches currencies with a maximum number of digits', function () { 33 | $builder = new Builder("$1000, €200, £30"); 34 | 35 | $matches = $builder->currency(0, 3)->get(); // maxDigits 36 | 37 | // Assert that only currency amounts with no more than 3 digits are matched 38 | expect($matches)->toEqual(['€200', '£30']); 39 | }); 40 | 41 | it('matches specific currency symbols', function () { 42 | $builder = new Builder("$1000, €200, £30, ¥1250"); 43 | 44 | $matches = $builder->currency(0, 0, "$,€")->get(); 45 | 46 | // Assert that only specified currency symbols are matched 47 | expect($matches)->toEqual(['$1000', '€200']); 48 | }); 49 | 50 | it('does not match currencies outside specific symbols', function () { 51 | $builder = new Builder("$1000, €200, £30"); 52 | 53 | $check = $builder->currency(function($query) { 54 | $query->specificCurrencies(['¥']); // You can use options without return 55 | // return $query->specificCurrencies(['¥']); // Or with return, both has the same effect 56 | })->checkString(); 57 | 58 | // Assert that currencies not in the specific symbols are not matched 59 | expect($check)->toBeFalse(); 60 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/DatePatternTest.php: -------------------------------------------------------------------------------- 1 | date()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['2024-01-30', '2024/01/30', '30.01.2024', '30.01.24']); 13 | }); 14 | 15 | it('validates a single date format correctly', function () { 16 | $string = "2024/04/20"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->date()->check(); 20 | 21 | // Assert that the single date format is validated correctly 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | 26 | it('validates a single date format is incorrect', function () { 27 | $string = "04-2024-20"; 28 | $builder = new Builder($string); 29 | 30 | $check = $builder->date()->check(); 31 | 32 | // Assert that the single date format is validated correctly 33 | expect($check)->toBeFalse(); 34 | }); 35 | 36 | 37 | it('validates a date format is in string', function () { 38 | $string = "Deadline: 04-2024-20"; 39 | $builder = new Builder($string); 40 | 41 | $check = $builder->date()->checkString(); 42 | 43 | // Assert that the single date format is validated correctly 44 | expect($check)->toBeFalse(); 45 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/DomainNamePatternTest.php: -------------------------------------------------------------------------------- 1 | domainName()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['example.com', 'sub.domain.org']); 13 | }); 14 | 15 | it('validates a single domain name correctly', function () { 16 | $string = "Check our portal: portal.example.net"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->domainName()->checkString(); // Check if the string contains at least one domain name 20 | 21 | // Assert that the string contains a valid domain name 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | it('counts domain names correctly', function () { 26 | $string = "Multiple domains: first.com, second.org, third.co.uk"; 27 | $builder = new Builder($string); 28 | 29 | $count = $builder->domainName()->count(); // Count the number of domain names 30 | 31 | // Assert that the count matches the expected number of domain names 32 | expect($count)->toEqual(3); 33 | }); 34 | 35 | it('matches domain names with specific extensions', function () { 36 | $string = "example.com, example.org, example.net"; 37 | $builder = new Builder($string); 38 | 39 | $matches = $builder->domainName(0, "", 'com,org')->get(); 40 | 41 | // Assert that only domains with specified extensions are matched 42 | expect($matches)->toEqual(['example.com', 'example.org']); 43 | }); 44 | 45 | it('matches domain names from specific domains', function () { 46 | $string = "visit example.com, contact us at info@example.org"; 47 | $builder = new Builder($string); 48 | 49 | $matches = $builder->domainName(['onlyDomains' => ['example.com']])->get(); 50 | 51 | // Assert that only domains from the specified list are matched 52 | expect($matches)->toEqual(['example.com']); 53 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/EmailPatternTest.php: -------------------------------------------------------------------------------- 1 | email()->check(); 8 | expect($check)->toBeTrue(); 9 | }); 10 | 11 | it('correctly invalidates an incorrect email with EmailPattern', function () { 12 | $builder = new Builder("example@.com"); 13 | $check = $builder->email()->check(); 14 | expect($check)->toBeFalse(); 15 | }); 16 | 17 | it('correctly validates a valid hard email with EmailPattern', function () { 18 | $builder = new Builder("example-hard@email.com.ge"); 19 | $check = $builder->email()->check(); 20 | expect($check)->toBeTrue(); 21 | }); 22 | 23 | it('correctly finds a valid email with EmailPattern', function () { 24 | $text = "Please contact us at support@example.com for general inquiries. \n 25 | For technical support, reach out to tech.help@exampletech.com. Additionally, \n 26 | you can send your feedback directly to feedback@example.net. We look forward to hearing from you!"; 27 | $builder = new Builder($text); 28 | $get = $builder->email()->get(); 29 | expect(count($get))->toBe(3); 30 | }); 31 | 32 | it('correctly invalidates an incorrect email with EmailPattern and MaxLength option', function () { 33 | $builder = new Builder("example@email.com"); 34 | $check = $builder->email(10)->check(); 35 | expect($check)->toBeFalse(); 36 | }); 37 | 38 | it('validates email addresses with specific domain extensions', function () { 39 | $builder = new Builder("user@example.com"); 40 | 41 | $check = $builder->email(['onlyExtensions' => ['com', 'org']])->check(); 42 | 43 | // Assert that the email with the specified extension is validated correctly 44 | expect($check)->toBeTrue(); 45 | }); 46 | 47 | it('does not validate email addresses with unlisted domain extensions', function () { 48 | $builder = new Builder("user@example.net"); 49 | 50 | $check = $builder->email(function($query) { 51 | $query->onlyExtensions(['com', 'org']); 52 | })->check(); 53 | 54 | // Assert that the email with an unlisted extension is not validated 55 | expect($check)->toBeFalse(); 56 | }); 57 | 58 | it('validates email addresses from specific domains', function () { 59 | $builder = new Builder("user@example.com"); 60 | 61 | $check = $builder->email(['onlyDomains' => ['example.com', 'example.org']])->check(); 62 | 63 | // Assert that the email from the specified domain is validated correctly 64 | expect($check)->toBeTrue(); 65 | }); 66 | 67 | it('does not validate email addresses from unlisted domains', function () { 68 | $builder = new Builder("user@otherdomain.com"); 69 | 70 | $check = $builder->email(['onlyDomains' => ['example.com', 'example.org']])->check(); 71 | 72 | // Assert that the email from an unlisted domain is not validated 73 | expect($check)->toBeFalse(); 74 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/FilePathPatternTest.php: -------------------------------------------------------------------------------- 1 | filePath()->get(); 11 | 12 | expect($matches)->toEqual(['/home/user/document.pdf', '/var/log/syslog', '/etc/nginx/nginx.conf']); 13 | }); 14 | 15 | it('validates a single Linux file path correctly', function () { 16 | $string = "/usr/local/bin/script.sh"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->filePath()->check(); 20 | 21 | expect($check)->toBeTrue(); 22 | }); 23 | 24 | it('does not match invalid Linux file paths', function () { 25 | $string = "This is not a file path: /invalid//path"; 26 | $builder = new Builder($string); 27 | 28 | $matches = $builder->filePath()->get(); 29 | 30 | expect($matches)->toBeEmpty(); 31 | }); 32 | 33 | it('does not validate a Linux file path in a string without a valid path', function () { 34 | $string = "This string does not contain a valid file path."; 35 | $builder = new Builder($string); 36 | 37 | $check = $builder->filePath()->checkString(); 38 | 39 | expect($check)->toBeFalse(); 40 | }); 41 | 42 | it('get path using isFile option', function () { 43 | $string = "/usr/local/bin/script.sh /home/user/document.pdf"; 44 | $builder = new Builder($string); 45 | 46 | $check = $builder->filePath(0, "sh")->get(); 47 | 48 | expect($check)->toEqual(["/usr/local/bin/script.sh"]); 49 | }); 50 | 51 | it('checks path using isFile option', function () { 52 | $string = "/usr/local/bin/script.sh"; 53 | $builder = new Builder($string); 54 | 55 | $check = $builder->filePath(0, null)->check(); 56 | 57 | expect($check)->toBeTrue(); 58 | }); 59 | 60 | 61 | it('checks path using pathType option', function () { 62 | $string = "/usr/local/bin/script.sh"; 63 | $builder = new Builder($string); 64 | 65 | // With int 66 | $check = $builder->filePath(0, null, false, 1)->check(); // 1 for absolute path 67 | expect($check)->toBeTrue(); 68 | // With string 69 | $check = $builder->filePath(0, null, false, "absolute")->check(); 70 | expect($check)->toBeTrue(); 71 | // With array 72 | $check = $builder->filePath(["pathType" => "absolute"])->get(); 73 | expect($check)->toEqual(["/usr/local/bin/script.sh"]); 74 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/FilePathWinPatternTest.php: -------------------------------------------------------------------------------- 1 | filePathWin()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual([ 13 | 'C:\\Users\\Example\\file.txt', 14 | 'D:\\Documents\\report.docx', 15 | 'E:\\Photos\\image.jpg' 16 | ]); 17 | }); 18 | 19 | it('validates a single Windows file path correctly', function () { 20 | $string = "C:\\Program Files\\app\\config.ini"; 21 | $builder = new Builder($string); 22 | 23 | $check = $builder->filePathWin()->check(); 24 | 25 | // Assert that the single file path is validated correctly 26 | expect($check)->toBeTrue(); 27 | }); 28 | 29 | it('validates a single Windows file path correctly in string', function () { 30 | $string = "Please refer to C:\\Program Files\\app\\config.ini for configuration settings."; 31 | $builder = new Builder($string); 32 | 33 | $check = $builder->filePathWin()->checkString(); 34 | 35 | // Assert that the single file path is validated correctly in the string 36 | expect($check)->toBeTrue(); 37 | }); 38 | 39 | it('does not match invalid Windows file paths', function () { 40 | $string = "InvalidPath//file.txt, Another/Invalid/Path"; 41 | $builder = new Builder($string); 42 | 43 | $matches = $builder->filePathWin()->get(); 44 | 45 | // Assert that no invalid file paths are matched 46 | expect($matches)->toBeEmpty(); 47 | }); 48 | 49 | it('checks file exists using fileExists option', function () { 50 | $string = __DIR__.'\..\..\TestFiles\document.txt'; 51 | $builder = new Builder($string); 52 | 53 | $check = $builder->filePathWin(0, null, true)->check(); 54 | 55 | expect($check)->toBeTrue(); 56 | })->onlyOnWindows(); 57 | 58 | it('checks file using array options', function () { 59 | $string = __DIR__.'\..\..\TestFiles\document.txt'; 60 | $builder = new Builder($string); 61 | 62 | $check = $builder->filePathWin([ 63 | "isDirectory" => 0, 64 | "isFile" => "txt", 65 | "fileExists" => true, 66 | ])->check(); 67 | 68 | expect($check)->toBeTrue(); 69 | })->onlyOnWindows(); -------------------------------------------------------------------------------- /tests/Feature/Patterns/HtmlTagPatternTest.php: -------------------------------------------------------------------------------- 1 | Paragraph

Div content
"); 7 | 8 | $matches = $builder->htmlTag()->get(); 9 | 10 | // Assert that the returned matches are as expected 11 | expect($matches)->toEqual(['

Paragraph

', '
Div content
']); 12 | }); 13 | 14 | it('does not match invalid HTML tags', function () { 15 | $builder = new Builder("This is a valid "); 16 | 17 | $check = $builder->htmlTag()->checkString(); 18 | 19 | // Assert that invalid HTML tags are not matched 20 | expect($check)->toBeFalse(); 21 | }); 22 | 23 | it('matches only allowed HTML tags correctly', function () { 24 | $builder = new Builder("

Paragraph

Div content
"); 25 | 26 | $matches = $builder->htmlTag("", "p, small")->get(); 27 | 28 | // Assert that the returned matches are as expected 29 | expect($matches)->toEqual(['

Paragraph

']); 30 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/IPAddressPatternTest.php: -------------------------------------------------------------------------------- 1 | ipAddress()->get(); 9 | expect($matches)->toEqual(['192.168.1.1', '10.0.0.1', '255.255.255.255']); 10 | }); 11 | 12 | it('does not match invalid IPv4 addresses', function () { 13 | $string = "192.168.1.256, 10.0.0.999, 300.255.255.255"; 14 | $builder = new Builder($string); 15 | $matches = $builder->ipAddress()->get(); 16 | expect($matches)->toBeEmpty(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/Feature/Patterns/IPv6AddressPatternTest.php: -------------------------------------------------------------------------------- 1 | ipv6Address()->get(); 9 | 10 | expect($matches)->toEqual(['2001:0db8:85a3:0000:0000:8a2e:0370:7334']); 11 | }); 12 | 13 | it('does not match invalid IPv6 addresses', function () { 14 | $string = "2001:0db8:85a3:0000:0000:8a2e:0370:7334Z, 2001::85a3::7334"; 15 | $builder = new Builder($string); 16 | $matches = $builder->ipv6Address()->get(); 17 | 18 | expect($matches)->toBeEmpty(); 19 | }); 20 | 21 | it('validates IPv6 addresses using filter_var', function () { 22 | $validIPv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; 23 | $invalidIPv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334Z"; 24 | 25 | $builder = new Builder($validIPv6); 26 | expect($builder->ipv6Address()->check())->toBeTrue(); 27 | 28 | $builder->setString($invalidIPv6); 29 | expect($builder->ipv6Address()->check())->toBeFalse(); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/Feature/Patterns/PasswordPatternTest.php: -------------------------------------------------------------------------------- 1 | password(8, 1, 1, 1)->check(); 9 | 10 | // Assert that the password is strong 11 | expect($check)->toBeTrue(); 12 | }); 13 | 14 | it('validates strong passwords correctly using callback', function () { 15 | $builder = new Builder("StrongP@ssw0rd"); 16 | 17 | $check = $builder->password(function($string) { 18 | return $string->minLength(8)->minUppercase(1)->minNumbers(1)->minSpecialChars(1); 19 | })->check(); 20 | 21 | // Assert that the password is strong 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | it('rejects weak passwords correctly', function () { 26 | $builder = new Builder("weak"); 27 | 28 | $check = $builder->password(8, 1, 1, 1)->check(); 29 | 30 | // Assert that the password is weak 31 | expect($check)->toBeFalse(); 32 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/PhonePatternTest.php: -------------------------------------------------------------------------------- 1 | phone()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['+1 (555) 123-4567', '1234567890', '0044 20 7946 0958']); 13 | }); 14 | 15 | it('validates a single phone number format correctly', function () { 16 | $string = "+1-800-123-4567"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->phone()->check(); 20 | 21 | // Assert that the single phone number format is validated correctly 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | it('validates a phone number format correctly within a string', function () { 26 | $string = "Call us now at 800-123-4567 for more information."; 27 | $builder = new Builder($string); 28 | 29 | $check = $builder->phone()->checkString(); 30 | 31 | // Assert that the phone number format within a string is validated correctly 32 | expect($check)->toBeTrue(); 33 | }); 34 | 35 | it('does not match invalid phone numbers', function () { 36 | $string = "Invalid numbers: 123, +1 24, 567_890"; 37 | $builder = new Builder($string); 38 | 39 | $matches = $builder->phone()->get(); 40 | 41 | // Assert that no valid phone numbers are matched 42 | expect($matches)->toBeEmpty(); 43 | }); 44 | 45 | 46 | 47 | it('validates a single phone number using country code correctly', function () { 48 | $string = "+1-800-123-4567"; 49 | $builder = new Builder($string); 50 | 51 | $check = $builder->phone("1")->check(); 52 | 53 | // Assert that the single phone number format is validated correctly 54 | expect($check)->toBeTrue(); 55 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/RegexFlagsTest.php: -------------------------------------------------------------------------------- 1 | start() 8 | ->lowercaseText() 9 | ->character("@") 10 | ->lowercaseText() 11 | ->dot() 12 | ->uppercaseText() 13 | ->end() 14 | ->check(); 15 | 16 | expect($checkWithoutFlag)->toBeFalse(); 17 | 18 | $builder = new Builder("Example@Email.COM"); 19 | $checkWithFlag = $builder->start() 20 | ->lowercaseText() 21 | ->character("@") 22 | ->lowercaseText() 23 | ->dot() 24 | ->uppercaseText() 25 | ->end() 26 | ->asCaseInsensitive()->check(); 27 | 28 | expect($checkWithFlag)->toBeTrue(); 29 | }); 30 | 31 | it('matches dates across multiple lines', function () { 32 | $string = "2024-01-30\n 2024-02-15\n 2024-11-30"; 33 | $builder = new Builder($string); 34 | $matches = $builder->start() 35 | ->digits(4)->dash() 36 | ->digits(2)->dash() 37 | ->digits(2) 38 | ->end() 39 | ->asMultiline()->check(); 40 | expect($matches)->toBeTrue(); 41 | }); 42 | 43 | it('matches a text in multiline as a single line string', function () { 44 | $string = "Check out\n this site:"; 45 | $builder = new Builder($string); 46 | $check = $builder->start()->anyChars()->character(":")->end()->asSingleline()->check(); 47 | expect($check)->toBeTrue(); // It don't match without Singleline flag 48 | }); 49 | 50 | it('matches text with Unicode characters', function () { 51 | $string = "მზადაა #1 ✔️ და #2 ✔️"; 52 | $builder = new Builder($string); 53 | $matches = $builder->start()->wordCharsRange(0, 2)->end()->asUnicode()->get(); 54 | expect($matches)->toContain('და'); // It don't match without unicode char 55 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/TextOrNumbersPatternTest.php: -------------------------------------------------------------------------------- 1 | textOrNumbers(8, 0, 1)->check(); 9 | expect($check)->toBeTrue(); 10 | }); 11 | 12 | it('finds pattern within string using TextOrNumbersPattern', function () { 13 | $string = "asd Passs1234 wawd"; 14 | $builder = new Builder($string); 15 | $check = $builder->textOrNumbers([ 16 | "minLength" => 8, 17 | "minUppercase" => 1 18 | ])->checkString(); 19 | expect($check)->toBeTrue(); 20 | }); 21 | 22 | it('counts matches correctly with TextOrNumbersPattern', function () { 23 | $string = "asd Passs1234 Wawoline343 text here"; 24 | $builder = new Builder($string); 25 | $count = $builder->textOrNumbers(function($query) { 26 | return $query->minLength(8)->minUppercase(1); 27 | })->count(); 28 | expect($count)->toEqual(2); 29 | }); 30 | 31 | it('retrieves all matches using TextOrNumbersPattern', function () { 32 | $string = "Passs1234 an 1sada a 5464565"; 33 | $builder = (new Builder($string))->textOrNumbers(4); 34 | $get = $builder->get(); 35 | expect($get)->toBeArray()->toHaveLength(3); 36 | }); 37 | 38 | it('generates correct regex string with TextOrNumbersPattern', function () { 39 | $builder = (new Builder("Passs1234"))->textOrNumbers(4); 40 | $regex = $builder->toRegex(); 41 | expect($regex)->toEqual("[a-zA-Z0-9]"); // @todo Regex isn't usable, need to update toRegex method 42 | // expect($regex)->toEqual("/^[a-zA-Z0-9]$/"); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/Feature/Patterns/TimePatternTest.php: -------------------------------------------------------------------------------- 1 | time()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['08:30', '14:45:15', '12:00 PM']); 13 | }); 14 | 15 | it('validates a single time format correctly', function () { 16 | $string = "07:15 AM"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->time()->check(); 20 | 21 | // Assert that the single time format is validated correctly 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | 26 | it('validates a single time format correctly in string', function () { 27 | $string = "Alarm set for 07:15 AM"; 28 | $builder = new Builder($string); 29 | 30 | $check = $builder->time()->checkString(); 31 | 32 | // Assert that the single time format is validated correctly 33 | expect($check)->toBeTrue(); 34 | }); -------------------------------------------------------------------------------- /tests/Feature/Patterns/UrlPatternTest.php: -------------------------------------------------------------------------------- 1 | url()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['https://www.example.com', 'http://social.example.org']); 13 | }); 14 | 15 | it('validates a single URL correctly', function () { 16 | $string = "https://www.example.com"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->url()->check(); 20 | 21 | // Assert that the URL validation passes 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | it('matches valid URLs with URI correctly', function () { 26 | $string = "Visit our site at https://www.example.com/home or follow us on http://social.example.org/profile/name"; 27 | $builder = new Builder($string); 28 | 29 | $matches = $builder->url()->get(); 30 | 31 | expect($matches)->toEqual(['https://www.example.com/home', 'http://social.example.org/profile/name']); 32 | }); 33 | 34 | it('validates URLs with specific protocols', function () { 35 | $builder = new Builder("http://example.com"); 36 | 37 | $check = $builder->url('http')->check(); 38 | 39 | // Assert that the URL with the specified protocol is validated correctly 40 | expect($check)->toBeTrue(); 41 | }); 42 | 43 | it('does not validate URLs with unlisted protocols', function () { 44 | $builder = new Builder("ftp://example.com"); 45 | 46 | $check = $builder->url('https')->check(); 47 | 48 | // Assert that the URL with an unlisted protocol is not validated 49 | expect($check)->toBeFalse(); 50 | }); 51 | 52 | it('validates URLs with only HTTP protocol', function () { 53 | $builder = new Builder("http://example.com"); 54 | 55 | $check = $builder->url(['onlyHttp' => true])->check(); 56 | 57 | // Assert that the URL with only HTTP protocol is validated correctly 58 | expect($check)->toBeTrue(); 59 | }); 60 | 61 | it('validates URLs with only HTTPS protocol', function () { 62 | $builder = new Builder("https://example.com"); 63 | 64 | $check = $builder->url(['onlyHttps' => true])->check(); 65 | 66 | // Assert that the URL with only HTTPS protocol is validated correctly 67 | expect($check)->toBeTrue(); 68 | }); 69 | -------------------------------------------------------------------------------- /tests/Feature/Patterns/UsernamePatternTest.php: -------------------------------------------------------------------------------- 1 | username()->get(); 10 | 11 | // Assert that the returned matches are as expected 12 | expect($matches)->toEqual(['user1', '_user_name', 'user-name123']); 13 | }); 14 | 15 | it('validates a single username format correctly', function () { 16 | $string = "user2023"; 17 | $builder = new Builder($string); 18 | 19 | $check = $builder->username()->check(); 20 | 21 | // Assert that the single username format is validated correctly 22 | expect($check)->toBeTrue(); 23 | }); 24 | 25 | it('validates a username format correctly within a string', function () { 26 | $string = "My username is user_2023."; 27 | $builder = new Builder($string); 28 | 29 | $check = $builder->username()->checkString(); 30 | 31 | // Assert that the username format within a string is validated correctly 32 | expect($check)->toBeTrue(); 33 | }); 34 | 35 | it('does not match invalid usernames', function () { 36 | $string = "us, user@name.com, verylongusernamebeyondlimit"; 37 | $builder = new Builder($string); 38 | 39 | $matches = $builder->username()->get(); 40 | 41 | // Assert that no valid usernames are matched 42 | expect($matches)->toBeEmpty(); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Expectations 19 | |-------------------------------------------------------------------------- 20 | | 21 | | When you're writing tests, you often need to check that values meet certain conditions. The 22 | | "expect()" function gives you access to a set of "expectations" methods that you can use 23 | | to assert different things. Of course, you may extend the Expectation API at any time. 24 | | 25 | */ 26 | 27 | expect()->extend('toBeOne', function () { 28 | return $this->toBe(1); 29 | }); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Functions 34 | |-------------------------------------------------------------------------- 35 | | 36 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 37 | | project that you don't want to repeat in every file. Here you can also expose helpers as 38 | | global functions to help you to reduce the number of lines of code in your test files. 39 | | 40 | */ 41 | 42 | function something() 43 | { 44 | // .. 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | -------------------------------------------------------------------------------- /tests/Unit/Options/CardTypeOptionTest.php: -------------------------------------------------------------------------------- 1 | onlyVisa(); 8 | 9 | expect($cardTypeOption->validate('4111111111111111'))->toBeTrue(); // Valid (Visa) 10 | expect($cardTypeOption->validate('5500000000000004'))->toBeFalse(); // Invalid (MasterCard) 11 | }); 12 | 13 | it('validates MasterCard numbers correctly', function () { 14 | $cardTypeOption = new CardTypeOption(); 15 | $cardTypeOption->onlyMasterCard(); 16 | 17 | expect($cardTypeOption->validate('5500000000000004'))->toBeTrue(); // Valid (MasterCard) 18 | expect($cardTypeOption->validate('4111111111111111'))->toBeFalse(); // Invalid (Visa) 19 | }); 20 | 21 | it('validates American Express card numbers correctly', function () { 22 | $cardTypeOption = new CardTypeOption(); 23 | $cardTypeOption->onlyAmex(); 24 | 25 | expect($cardTypeOption->validate('371449635398431'))->toBeTrue(); // Valid (AMEX) 26 | expect($cardTypeOption->validate('4111111111111111'))->toBeFalse(); // Invalid (Visa) 27 | expect($cardTypeOption->validate('5500000000000004'))->toBeFalse(); // Invalid (MasterCard) 28 | }); 29 | 30 | it('validates multiple card types correctly', function () { 31 | $cardTypeOption = new CardTypeOption(); 32 | $cardTypeOption->allowCardTypes('visa,mastercard,amex'); 33 | 34 | expect($cardTypeOption->validate('4111111111111111'))->toBeTrue(); // Valid Visa 35 | expect($cardTypeOption->validate('5500000000000004'))->toBeTrue(); // Valid MasterCard 36 | expect($cardTypeOption->validate('371449635398431'))->toBeTrue(); // Valid AMEX 37 | expect($cardTypeOption->validate('6011111111111117'))->toBeFalse(); // Invalid Discover 38 | }); 39 | -------------------------------------------------------------------------------- /tests/Unit/Options/CharacterOptionTest.php: -------------------------------------------------------------------------------- 1 | allow(['a', 'b', 'c']); 10 | expect($charOption->validate('abc'))->toBeTrue(); 11 | expect($charOption->validate('abd'))->toBeFalse(); // 'd' is not allowed 12 | }); 13 | 14 | it('excludes specific characters', function () { 15 | $charOption = new CharacterOption(); 16 | $charOption->exclude(['x', 'y', 'z']); 17 | expect($charOption->validate('abc'))->toBeTrue(); 18 | expect($charOption->validate('abx'))->toBeFalse(); // 'x' is excluded 19 | }); 20 | 21 | it('enforces minimum uppercase characters', function () { 22 | $charOption = new CharacterOption(); 23 | $charOption->minUppercase(2); 24 | expect($charOption->validate('ABc'))->toBeTrue(); 25 | expect($charOption->validate('Abc'))->toBeFalse(); // Only 1 uppercase 26 | }); 27 | 28 | it('enforces minimum lowercase characters', function () { 29 | $charOption = new CharacterOption(); 30 | $charOption->minLowercase(2); 31 | expect($charOption->validate('abC'))->toBeTrue(); 32 | expect($charOption->validate('aBC'))->toBeFalse(); // Only 1 lowercase 33 | }); 34 | 35 | 36 | it('generates correct regex pattern for allowed characters', function () { 37 | $charOption = new CharacterOption(); 38 | $charOption->allow(['a', 'b', 'c']); 39 | expect($charOption->build())->toBe('[abc]+'); 40 | }); 41 | 42 | it('generates correct regex pattern for excluded characters', function () { 43 | $charOption = new CharacterOption(); 44 | $charOption->exclude(['x', 'y', 'z']); 45 | expect($charOption->build())->toBe('(?!.*[xyz]).*'); 46 | }); 47 | 48 | it('matches strings with allowed characters only', function () { 49 | $charOption = new CharacterOption(); 50 | $charOption->allow(['a', 'b', 'c']); 51 | $regex = "/^" . $charOption->build() . "$/"; 52 | // echo $regex; 53 | expect(preg_match($regex, 'aa'))->toBe(1); 54 | expect(preg_match($regex, 'd'))->toBe(0); 55 | }); 56 | 57 | it('rejects strings with excluded characters', function () { 58 | $charOption = new CharacterOption(); 59 | $charOption->exclude(['x', 'y', 'z']); 60 | $regex = "/^" . $charOption->build() . "$/"; 61 | expect(preg_match($regex, 'abc'))->toBe(1); 62 | expect(preg_match($regex, 'axy'))->toBe(0); // Contains excluded character 'x' 63 | }); 64 | 65 | it('requires minimum number of uppercase characters', function () { 66 | $charOption = new CharacterOption(); 67 | $charOption->minUppercase(2); 68 | $regex = "/" . $charOption->build() . "/"; 69 | expect(preg_match($regex, 'ABc'))->toBe(1); // 2 uppercase characters 70 | expect(preg_match($regex, 'Abc'))->toBe(0); // Only 1 uppercase, less than the minimum required 71 | }); 72 | 73 | 74 | it('requires minimum number of lowercase characters', function () { 75 | $charOption = new CharacterOption(); 76 | $charOption->minLowercase(2); 77 | $regex = "/^" . $charOption->build() . "$/"; 78 | expect(preg_match($regex, 'abC'))->toBe(1); 79 | expect(preg_match($regex, 'aBC'))->toBe(0); // Only 1 lowercase, less than the minimum required 80 | }); 81 | 82 | 83 | it('requires minimum number of lowercase characters and excludes some', function () { 84 | $charOption = new CharacterOption(); 85 | $charOption->minLowercase(2); 86 | $charOption->exclude(['x', 'y', 'z']); 87 | $regex = "/^" . $charOption->build() . "$/"; 88 | expect($charOption->validateUsingRegex('abC'))->toBe(true); 89 | expect($charOption->validateUsingRegex('abc'))->toBe(true); 90 | expect($charOption->validateUsingRegex('abz'))->toBe(false); 91 | expect($charOption->validateUsingRegex('aBC'))->toBe(false); 92 | }); 93 | 94 | 95 | it('requires minimum number of lowercase, allows some characters and excludes some', function () { 96 | $charOption = new CharacterOption(); 97 | $charOption->allow(['a', 'b', 'c', "Z"]); 98 | $charOption->exclude(['x', 'y', 'z']); 99 | $charOption->minLowercase(2); 100 | 101 | expect($charOption->validateUsingRegex('abc'))->toBe(true); 102 | expect($charOption->validateUsingRegex('aZ'))->toBe(false); 103 | expect($charOption->validateUsingRegex('aaaaaaZ'))->toBe(true); 104 | expect($charOption->validateUsingRegex('hgf'))->toBe(false); 105 | expect($charOption->validateUsingRegex('xyz'))->toBe(false); 106 | }); 107 | 108 | it('enforces minimum special characters', function () { 109 | $charOption = new CharOption(); 110 | $charOption->minSpecialCharacters(2); 111 | expect($charOption->validate('ab#d$'))->toBeTrue(); 112 | expect($charOption->validate('ab#c'))->toBeFalse(); // Only 1 special character 113 | }); 114 | 115 | it('enforces maximum special characters', function () { 116 | $charOption = new CharOption(); 117 | $charOption->maxSpecialCharacters(1); 118 | expect($charOption->validate('ab#d$'))->toBeFalse(); 119 | expect($charOption->validate('ab#c'))->toBeTrue(); // Only 1 special character 120 | }); 121 | 122 | it('enforces no special characters', function () { 123 | $charOption = new CharOption(); 124 | $charOption->noSpecialCharacters(); 125 | expect($charOption->validate('ab#d$'))->toBeFalse(); 126 | expect($charOption->validate('abc'))->toBeTrue(); // Only 1 special character 127 | }); 128 | 129 | it('enforces only lowercase characters', function () { 130 | $charOption = new CharOption(); 131 | $charOption->onlyLowercase(); 132 | echo $charOption->build(); 133 | expect($charOption->validate('abSDF'))->toBeFalse(); 134 | expect($charOption->validate('abc'))->toBeTrue(); 135 | }); 136 | 137 | it('enforces only uppercase characters', function () { 138 | $charOption = new CharOption(); 139 | $charOption->onlyUppercase(); 140 | expect($charOption->validate('abSDF'))->toBeFalse(); 141 | expect($charOption->validate('ABC'))->toBeTrue(); 142 | }); -------------------------------------------------------------------------------- /tests/Unit/Options/ContainSpacesOptionTest.php: -------------------------------------------------------------------------------- 1 | noSpaces(); 10 | expect($spaceOption->validate('NoSpacesHere'))->toBeTrue(); 11 | expect($spaceOption->validate('Spaces here'))->toBeFalse(); 12 | 13 | // Test no double spaces 14 | $spaceOption->noSpaces(false)->noDoubleSpaces(); 15 | expect($spaceOption->validate('No double spaces or more'))->toBeFalse(); 16 | expect($spaceOption->validate('Single spaces only'))->toBeTrue(); 17 | 18 | // Test max spaces 19 | $spaceOption->maxSpaces(3); 20 | expect($spaceOption->validate('One two three'))->toBeTrue(); 21 | expect($spaceOption->validate('Too many spaces here right?'))->toBeFalse(); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/Unit/Options/CountryCodeOptionTest.php: -------------------------------------------------------------------------------- 1 | setCountryCode('995'); 8 | 9 | expect($countryCodeOption->validate('+995123456789'))->toBeTrue(); 10 | expect($countryCodeOption->validate('995123456789'))->toBeTrue(); 11 | expect($countryCodeOption->validate('123456789'))->toBeFalse(); // Missing country code 12 | expect($countryCodeOption->validate('+123123456789'))->toBeFalse(); // Different country code 13 | }); 14 | -------------------------------------------------------------------------------- /tests/Unit/Options/DomainSpecificOptionTest.php: -------------------------------------------------------------------------------- 1 | setAllowedDomains(['example.com', 'example.org']); 8 | 9 | // Test allowed domains 10 | expect($domainOption->validate('user@example.com'))->toBeTrue(); 11 | expect($domainOption->validate('user@example.org'))->toBeTrue(); 12 | 13 | // Test disallowed domain 14 | expect($domainOption->validate('user@anotherdomain.com'))->toBeFalse(); 15 | }); 16 | 17 | it('validates email addresses based on specific domain extensions', function () { 18 | $domainOption = new DomainSpecificOption(); 19 | $domainOption->setAllowedExtensions(['com', 'org']); 20 | 21 | // Test allowed extensions 22 | expect($domainOption->validate('user@example.com'))->toBeTrue(); 23 | expect($domainOption->validate('user@example.org'))->toBeTrue(); 24 | 25 | // Test disallowed extension 26 | expect($domainOption->validate('user@example.net'))->toBeFalse(); 27 | }); -------------------------------------------------------------------------------- /tests/Unit/Options/FileOptionTest.php: -------------------------------------------------------------------------------- 1 | isFile('txt'); 9 | expect($fileOption->validate('document.txt'))->toBeTrue(); 10 | expect($fileOption->validate('image.jpg'))->toBeFalse(); // Wrong extension 11 | }); 12 | 13 | it('validates if the input is a file regardless of extension', function () { 14 | $fileOption = new FileOption(); 15 | $fileOption->isFile(); 16 | expect($fileOption->validate('document.txt'))->toBeTrue(); 17 | expect($fileOption->validate('folder/'))->toBeFalse(); // Not a file 18 | }); 19 | 20 | it('validates if the input is a directory', function () { 21 | $fileOption = new FileOption(); 22 | $fileOption->isDirectory(); 23 | expect($fileOption->validate('folder/'))->toBeTrue(); 24 | expect($fileOption->validate('document.txt'))->toBeFalse(); // Not a directory 25 | }); 26 | 27 | it('validates if the file exists in the filesystem', function () { 28 | $fileOption = new FileExistsOption(); 29 | $fileOption->fileExists(); 30 | expect($fileOption->validate(__DIR__.'/../../TestFiles/document.txt'))->toBeTrue(); 31 | expect($fileOption->validate('path/to/nonexistent/file.txt'))->toBeFalse(); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/Unit/Options/HtmlTagsOptionTest.php: -------------------------------------------------------------------------------- 1 | allowTags(['p', 'b']); 8 | 9 | expect($option->validate('

Paragraph

'))->toBeTrue(); 10 | expect($option->validate('

Boldtext

'))->toBeTrue(); 11 | expect($option->validate('
Div
'))->toBeFalse(); // 'div' is not in the allowed list 12 | }); 13 | 14 | it('restricts specified HTML tags', function () { 15 | $option = new HtmlTagsOption(); 16 | $option->restrictTags(['script', 'iframe']); 17 | 18 | expect($option->validate('

Paragraph

'))->toBeTrue(); 19 | expect($option->validate(''))->toBeFalse(); // 'script' is restricted 20 | expect($option->validate(''))->toBeFalse(); // 'iframe' is restricted 21 | }); 22 | -------------------------------------------------------------------------------- /tests/Unit/Options/LengthOptionTest.php: -------------------------------------------------------------------------------- 1 | minLength(3); 8 | expect($lengthOption->validate('abc'))->toBeTrue(); 9 | expect($lengthOption->validate('ab'))->toBeFalse(); 10 | }); 11 | 12 | it('enforces maximum length', function () { 13 | $lengthOption = new LengthOption(); 14 | $lengthOption->maxLength(3); 15 | expect($lengthOption->validate('abc'))->toBeTrue(); 16 | expect($lengthOption->validate('abcd'))->toBeFalse(); 17 | }); 18 | 19 | it('enforces exact length', function () { 20 | $lengthOption = new LengthOption(); 21 | $lengthOption->exactLength(3); 22 | expect($lengthOption->validate('abc'))->toBeTrue(); 23 | expect($lengthOption->validate('ab'))->toBeFalse(); 24 | expect($lengthOption->validate('abcd'))->toBeFalse(); 25 | }); 26 | 27 | it('generates correct regex pattern for length constraints', function () { 28 | $lengthOption = new LengthOption(); 29 | $lengthOption->minLength(2)->maxLength(4); 30 | expect($lengthOption->build())->toBe('{2,4}'); 31 | 32 | $lengthOption = new LengthOption(); 33 | $lengthOption->exactLength(3); 34 | expect($lengthOption->build())->toBe('{3}'); 35 | }); 36 | 37 | 38 | it('matches strings according to minimum length', function () { 39 | $lengthOption = new LengthOption(); 40 | $lengthOption->minLength(3); 41 | $regex = "/^." . $lengthOption->build() . "$/"; 42 | // echo $regex; 43 | expect(preg_match($regex, 'abc'))->toBe(1); 44 | expect(preg_match($regex, 'ab'))->toBe(0); 45 | }); 46 | 47 | it('matches strings according to maximum length', function () { 48 | $lengthOption = new LengthOption(); 49 | $lengthOption->maxLength(3); 50 | $regex = "/^." . $lengthOption->build() . "$/"; 51 | expect(preg_match($regex, 'abc'))->toBe(1); 52 | expect(preg_match($regex, 'abcd'))->toBe(0); 53 | }); 54 | 55 | it('matches strings according to exact length', function () { 56 | $lengthOption = new LengthOption(); 57 | $lengthOption->exactLength(3); 58 | $regex = "/^." . $lengthOption->build() . "$/"; 59 | expect(preg_match($regex, 'abc'))->toBe(1); 60 | expect(preg_match($regex, 'ab'))->toBe(0); 61 | expect(preg_match($regex, 'abcd'))->toBe(0); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/Unit/Options/NumberOptionTest.php: -------------------------------------------------------------------------------- 1 | validate('12345'))->toBeTrue(); 8 | }); 9 | 10 | it('enforces minimum number of digits', function () { 11 | $numberOption = new NumberOption(); 12 | $numberOption->setMinValue(2); 13 | expect($numberOption->validate('123'))->toBeTrue(); 14 | expect($numberOption->validate('1'))->toBeFalse(); // Only 1 digit, minimum is 2 15 | }); 16 | 17 | it('enforces maximum number of digits', function () { 18 | $numberOption = new NumberOption(); 19 | $numberOption->setMaxValue(3); 20 | expect($numberOption->validate('123'))->toBeTrue(); 21 | expect($numberOption->validate('1234'))->toBeFalse(); // 4 digits, maximum is 3 22 | }); 23 | 24 | it('enforces exact number of digits', function () { 25 | $numberOption = new NumberOption(); 26 | $numberOption->setExactValue(3); 27 | expect($numberOption->validate('123'))->toBeTrue(); 28 | expect($numberOption->validate('1234'))->toBeFalse(); // 4 digits, exact is 3 29 | }); 30 | 31 | 32 | it('generates correct regex pattern for default behavior', function () { 33 | $numberOption = new NumberOption(); 34 | expect($numberOption->build())->toBe('\d+'); 35 | }); 36 | 37 | it('generates correct regex pattern for minimum number of digits', function () { 38 | $numberOption = new NumberOption(); 39 | $numberOption->setMinValue(2); 40 | expect($numberOption->build())->toBe('\d{2,}'); 41 | }); 42 | 43 | it('generates correct regex pattern for maximum number of digits', function () { 44 | $numberOption = new NumberOption(); 45 | $numberOption->setMaxValue(3); 46 | expect($numberOption->build())->toBe('\d{0,3}'); 47 | }); 48 | 49 | it('generates correct regex pattern for exact number of digits', function () { 50 | $numberOption = new NumberOption(); 51 | $numberOption->setExactValue(3); 52 | expect($numberOption->build())->toBe('\d{3}'); 53 | }); 54 | 55 | it('matches numbers according to the default regex pattern', function () { 56 | $numberOption = new NumberOption(); 57 | $regex = "/^" . $numberOption->build() . "$/"; 58 | expect(preg_match($regex, '12345'))->toBe(1); 59 | expect(preg_match($regex, 'abc'))->toBe(0); 60 | }); 61 | 62 | it('matches numbers according to the minimum number of digits', function () { 63 | $numberOption = new NumberOption(); 64 | $numberOption->setMinValue(2); 65 | $regex = "/^" . $numberOption->build() . "$/"; 66 | expect(preg_match($regex, '12'))->toBe(1); 67 | expect(preg_match($regex, '1'))->toBe(0); 68 | }); 69 | 70 | it('matches numbers according to the maximum number of digits', function () { 71 | $numberOption = new NumberOption(); 72 | $numberOption->setMaxValue(3); 73 | $regex = "/^" . $numberOption->build() . "$/"; 74 | expect(preg_match($regex, '123'))->toBe(1); 75 | expect(preg_match($regex, '1234'))->toBe(0); 76 | }); 77 | 78 | it('matches numbers according to the exact number of digits', function () { 79 | $numberOption = new NumberOption(); 80 | $numberOption->setExactValue(3); 81 | $regex = "/^" . $numberOption->build() . "$/"; 82 | expect(preg_match($regex, '123'))->toBe(1); 83 | expect(preg_match($regex, '1234'))->toBe(0); 84 | }); 85 | 86 | -------------------------------------------------------------------------------- /tests/Unit/Options/OnlyAlphanumericOptionTest.php: -------------------------------------------------------------------------------- 1 | onlyAlphanumeric(true); 8 | 9 | // Alphanumeric strings 10 | expect($option->validate('abc123'))->toBeTrue(); 11 | expect($option->validate('TestUser2021'))->toBeTrue(); 12 | 13 | // Non-alphanumeric strings 14 | expect($option->validate('abc123!'))->toBeFalse(); 15 | expect($option->validate('hello world'))->toBeFalse(); 16 | }); 17 | 18 | it('allows non-alphanumeric strings when option is disabled', function () { 19 | $option = new OnlyAlphanumericOption(); 20 | $option->onlyAlphanumeric(false); 21 | 22 | expect($option->validate('abc123'))->toBeTrue(); 23 | expect($option->validate('abc123!'))->toBeTrue(); 24 | expect($option->validate('hello world'))->toBeTrue(); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/Unit/Options/PathTypeOption.php: -------------------------------------------------------------------------------- 1 | setPathType(1); 8 | 9 | expect($pathOption->validate('file.txt'))->toBeTrue(); 10 | expect($pathOption->validate('/home/user/file.txt'))->toBeFalse(); // Absolute path 11 | }); 12 | 13 | it('validates absolute paths', function () { 14 | $pathOption = new PathTypeOption(); 15 | $pathOption->setPathType(2); 16 | 17 | expect($pathOption->validate('/home/user/file.txt'))->toBeTrue(); 18 | expect($pathOption->validate('file.txt'))->toBeFalse(); // Relative path 19 | }); 20 | -------------------------------------------------------------------------------- /tests/Unit/Options/ProtocolOptionTest.php: -------------------------------------------------------------------------------- 1 | onlyProtocol('http'); 8 | 9 | expect($protocolOption->validate('http://example.com'))->toBeTrue(); 10 | expect($protocolOption->validate('https://example.com'))->toBeFalse(); 11 | expect($protocolOption->validate('ftp://example.com'))->toBeFalse(); // 'ftp' protocol not allowed 12 | }); 13 | 14 | it('validates URLs with only HTTP protocol', function () { 15 | $protocolOption = new ProtocolOption(); 16 | $protocolOption->onlyHttp(true); 17 | 18 | expect($protocolOption->validate('http://example.com'))->toBeTrue(); 19 | expect($protocolOption->validate('https://example.com'))->toBeFalse(); // Only 'http' allowed 20 | expect($protocolOption->validate('ftp://example.com'))->toBeFalse(); 21 | }); 22 | 23 | it('validates URLs with only HTTPS protocol', function () { 24 | $protocolOption = new ProtocolOption(); 25 | $protocolOption->onlyHttps(true); 26 | 27 | expect($protocolOption->validate('https://example.com'))->toBeTrue(); 28 | expect($protocolOption->validate('http://example.com'))->toBeFalse(); // Only 'https' allowed 29 | expect($protocolOption->validate('ftp://example.com'))->toBeFalse(); 30 | }); 31 | 32 | it('allows multiple protocols', function () { 33 | $protocolOption = new ProtocolOption(); 34 | $protocolOption->onlyProtocol(['http', 'ftp']); 35 | 36 | expect($protocolOption->validate('http://example.com'))->toBeTrue(); 37 | expect($protocolOption->validate('ftp://example.com'))->toBeTrue(); 38 | expect($protocolOption->validate('https://example.com'))->toBeFalse(); // 'https' not in the allowed list 39 | }); 40 | -------------------------------------------------------------------------------- /tests/Unit/Options/SpecificCurrenciesOptionTest.php: -------------------------------------------------------------------------------- 1 | setSpecificCurrencies(['$', '€', '£']); 8 | 9 | expect($currencyOption->validate('$100'))->toBeTrue(); 10 | expect($currencyOption->validate('€200'))->toBeTrue(); 11 | expect($currencyOption->validate('£300'))->toBeTrue(); 12 | expect($currencyOption->validate('₾400'))->toBeFalse(); // Not in the specified currencies 13 | }); 14 | 15 | test('validates input with a single specific currency symbol', function () { 16 | $currencyOption = new SpecificCurrenciesOption(); 17 | $currencyOption->onlyUSD(); 18 | 19 | expect($currencyOption->validate('$100'))->toBeTrue(); 20 | expect($currencyOption->validate('€200'))->toBeFalse(); 21 | }); 22 | 23 | test('allows setting specific currencies as a string', function () { 24 | $currencyOption = new SpecificCurrenciesOption(); 25 | $currencyOption->setSpecificCurrencies('$,€,£'); 26 | 27 | expect($currencyOption->validate('$100'))->toBeTrue(); 28 | expect($currencyOption->validate('€200'))->toBeTrue(); 29 | expect($currencyOption->validate('₾400'))->toBeFalse(); 30 | }); 31 | 32 | test('validates using onlyEUR option', function () { 33 | $currencyOption = new SpecificCurrenciesOption(); 34 | $currencyOption->onlyEUR(); 35 | 36 | expect($currencyOption->validate('€200'))->toBeTrue(); 37 | expect($currencyOption->validate('$100'))->toBeFalse(); 38 | }); 39 | 40 | test('validates using onlyGBP option', function () { 41 | $currencyOption = new SpecificCurrenciesOption(); 42 | $currencyOption->onlyGBP(); 43 | 44 | expect($currencyOption->validate('£300'))->toBeTrue(); 45 | expect($currencyOption->validate('₾400'))->toBeFalse(); 46 | }); 47 | 48 | test('validates using onlyGEL option', function () { 49 | $currencyOption = new SpecificCurrenciesOption(); 50 | $currencyOption->onlyGEL(); 51 | 52 | expect($currencyOption->validate('₾400'))->toBeTrue(); 53 | expect($currencyOption->validate('$100'))->toBeFalse(); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/Unit/Patterns/TextOrNumbersPatternTest.php: -------------------------------------------------------------------------------- 1 | validateInput('abc123'))->toBeTrue(); 11 | expect($pattern->validateInput('###'))->toBeFalse(); 12 | }); 13 | 14 | it('works with length option', function () { 15 | $pattern = new TextOrNumbersPattern(); 16 | $lengthOption = new LengthOption(); 17 | $lengthOption->minLength(3); 18 | 19 | $pattern->setOption($lengthOption); 20 | expect($pattern->validateInput('abcdef'))->toBeTrue(); 21 | expect($pattern->validateInput('ab'))->toBeFalse(); 22 | }); 23 | 24 | it('validates input correctly', function () { 25 | $pattern = new TextOrNumbersPattern(); 26 | $lengthOption = new LengthOption(); 27 | $lengthOption->minLength(3); 28 | 29 | $pattern->setOption($lengthOption); 30 | expect($pattern->validateInput('abc'))->toBeTrue(); 31 | expect($pattern->validateInput('ab'))->toBeFalse(); 32 | }); 33 | 34 | it('integrates number option correctly', function () { 35 | $pattern = new TextOrNumbersPattern(); 36 | $numberOption = new NumberOption(); 37 | $numberOption->setMinValue(2); // At least 2 digits 38 | 39 | $pattern->setOption($numberOption); 40 | 41 | expect($pattern->validateInput('abc123'))->toBeTrue(); // Has at least 2 digits 42 | expect($pattern->validateInput('abc'))->toBeFalse(); // Less than 2 digits 43 | }); 44 | 45 | it('integrates character option correctly', function () { 46 | $pattern = new TextOrNumbersPattern(); 47 | $charOption = new CharacterOption(); 48 | $charOption->allow(['a', 'b', 'c', '1', '2']); 49 | $charOption->minLowercase(2); // At least 2 lowercase letters 50 | 51 | $pattern->setOption($charOption); 52 | 53 | expect($pattern->validateInput('ab12'))->toBeTrue(); // Meets lowercase and allowed character conditions 54 | expect($pattern->validateInput('xyz12'))->toBeFalse(); // 'x', 'y', 'z' not allowed 55 | expect($pattern->validateInput('a1'))->toBeFalse(); // Only 1 lowercase letter 56 | }); 57 | 58 | it('find text and numbers matches', function () { 59 | $pattern = new TextOrNumbersPattern(); 60 | $matches = $pattern->getMatches('<> abc123 ### %%%%% <>'); 61 | expect(count($matches))->toBe(1); 62 | expect($matches[0])->toBe('abc123'); 63 | }); 64 | 65 | it('validates (min 1) match in full string correctly', function () { 66 | $pattern = new TextOrNumbersPattern(); 67 | expect($pattern->validateMatches('<> abc123 ### %%%%% <>'))->toBe(true); 68 | expect($pattern->validateMatches('<> ### %%%%% <>'))->toBe(false); 69 | }); --------------------------------------------------------------------------------