├── .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 | });
--------------------------------------------------------------------------------