From eb2d2f03c4a6077a80e755b0eed8091b3d197e14 Mon Sep 17 00:00:00 2001 From: lucamattosinho Date: Sun, 30 Aug 2026 11:15:46 -0300 Subject: [PATCH 1/2] Add password validation rules --- README.md | 14 +++++ src/DependencyInjection/TraitRule.php | 4 +- src/DependencyInjection/TraitRuleString.php | 21 +++++++ tests/StringTest.php | 69 +++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5611bd2..084e7fe 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,20 @@ Complete list of available validators in the library. Use them in your validatio | regex | Custom regular expression validation | | upper | All uppercase characters | +### Password Validators + +| Validator | Description | +| --------------- | ------------------------------------------------------------------ | +| password | Requires uppercase, lowercase, number and special character | +| numericPassword | Requires only numeric characters, suitable for PIN-style passwords | + +```php +$rules = [ + 'password' => 'required|min:8|max:64|password', + 'pin' => 'required|min:4|max:6|numericPassword', +]; +``` + ### Brazilian Data Validators | Validator | Description | diff --git a/src/DependencyInjection/TraitRule.php b/src/DependencyInjection/TraitRule.php index bf8e69f..23f7b8a 100644 --- a/src/DependencyInjection/TraitRule.php +++ b/src/DependencyInjection/TraitRule.php @@ -15,7 +15,7 @@ private function methodsNoRuleValue(): array 'validateFileUploadMandatory', 'validateDateBrazil', 'validateDateAmerican', 'validateHour', 'validateTimestamp', 'validateWeekend', 'validateArray', 'validateFieldMandatory', 'validateBoolean', 'validateFloating', 'validateJson', 'validateDateNotFuture', 'validateDateIso8601', - 'validateDateUTCWithoutTimezone', + 'validateDateUTCWithoutTimezone', 'validatePassword', 'validateNumericPassword', ]; } @@ -70,6 +70,7 @@ private static function functionsValidationMtoN(): array 'notSpace' => 'validateSpace', 'noWeekend' => 'validateWeekend', 'numeric' => 'validateNumeric', + 'numericPassword' => 'validateNumericPassword', 'numMax' => 'validateNumMax', 'numMin' => 'validateNumMin', 'numMonth' => 'validateNumMonth', @@ -81,6 +82,7 @@ private static function functionsValidationOtoZ(): array return [ 'optional' => 'validateOptional', 'phone' => 'validatePhone', + 'password' => 'validatePassword', 'ddd' => 'validateDdd', 'plate' => 'validatePlate', 'regex' => 'validateRegex', diff --git a/src/DependencyInjection/TraitRuleString.php b/src/DependencyInjection/TraitRuleString.php index cbc35df..d6ebb97 100644 --- a/src/DependencyInjection/TraitRuleString.php +++ b/src/DependencyInjection/TraitRuleString.php @@ -268,6 +268,27 @@ protected function validatePhone(string $field = '', string $value = '', ?string } } + protected function validatePassword(string $field = '', string $value = '', ?string $message = ''): void + { + if ( + !preg_match('@[A-Z]@', $value) + || !preg_match('@[a-z]@', $value) + || !preg_match('@[0-9]@', $value) + || !preg_match('/[^A-Za-z0-9]/', $value) + ) { + $this->errors[$field] = !empty($message) ? + $message : "O campo $field precisa conter maiúscula, minúscula, número e símbolo!"; + } + } + + protected function validateNumericPassword(string $field = '', string $value = '', ?string $message = ''): void + { + if (!ctype_digit($value)) { + $this->errors[$field] = !empty($message) ? + $message : "O campo $field precisa conter apenas números!"; + } + } + protected function validateRegex( string $rule = '', string $field = '', diff --git a/tests/StringTest.php b/tests/StringTest.php index 4a300a7..28ce3ed 100644 --- a/tests/StringTest.php +++ b/tests/StringTest.php @@ -341,6 +341,75 @@ public function testRegex(): void self::assertValidatorErrorCount(1, $array, $rules); } + public function testPassword(): void + { + $array = [ + 'testValid' => 'Password1!', + 'testErrorNoUppercase' => 'password1!', + 'testErrorNoLowercase' => 'PASSWORD1!', + 'testErrorNoNumber' => 'Password!', + 'testErrorNoSymbol' => 'Password1', + ]; + $rules = array_fill_keys(array_keys($array), 'password'); + + self::assertValidatorErrorCount(4, $array, $rules); + } + + public function testNumericPassword(): void + { + $array = [ + 'testValidWithLeadingZero' => '012345', + 'testValid' => '123456', + 'testErrorLetters' => '123a56', + 'testErrorDecimal' => '123.56', + 'testErrorWhitespace' => '123 56', + 'testErrorEmpty' => '', + ]; + $rules = array_fill_keys(array_keys($array), 'numericPassword'); + + self::assertValidatorErrorCount(4, $array, $rules); + } + + public function testPasswordWithCustomMessage(): void + { + $message = 'A senha não atende aos requisitos de segurança.'; + $validator = $this->validate( + ['password' => 'password1!'], + ['password' => 'password, ' . $message] + ); + + self::assertSame($message, $validator->getErros()['password']); + } + + public function testNumericPasswordWithCustomMessage(): void + { + $message = 'O PIN deve conter apenas números.'; + $validator = $this->validate( + ['pin' => '12a4'], + ['pin' => 'numericPassword, ' . $message] + ); + + self::assertSame($message, $validator->getErros()['pin']); + } + + public function testPasswordValidatorsWithLengthRules(): void + { + $array = [ + 'validPassword' => 'Password1!', + 'shortPassword' => 'Aa1!', + 'validPin' => '0123', + 'longPin' => '1234567', + ]; + $rules = [ + 'validPassword' => 'required|min:8|max:64|password', + 'shortPassword' => 'required|min:8|max:64|password', + 'validPin' => 'required|min:4|max:6|numericPassword', + 'longPin' => 'required|min:4|max:6|numericPassword', + ]; + + self::assertValidatorErrorCount(2, $array, $rules); + } + public function testNotSpace(): void { $array = [ From fcaaf09bb19e7a3ebcba3dd841898327b4464725 Mon Sep 17 00:00:00 2001 From: lucamattosinho Date: Sun, 30 Aug 2026 11:26:02 -0300 Subject: [PATCH 2/2] =?UTF-8?q?add=20colabora=C3=A7=C3=A3o=20composer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 8aca96a..f350eb2 100644 --- a/composer.json +++ b/composer.json @@ -80,6 +80,11 @@ "name": "Thales Santos", "email": "thales_sblue@hotmail.com", "role": "Developer" + }, + { + "name": "Luca Mattosinho Teixeira", + "email": "lucamattosinho@gmail.com", + "role": "Developer" } ], "support": {