From 4ffca1354352a75110e95e2b4f038b5c5e970817 Mon Sep 17 00:00:00 2001 From: phpdevcommunity Date: Sun, 26 Jan 2025 13:45:33 +0000 Subject: [PATCH 1/4] mplement Item and Collection validation features --- README.md | 75 ++++++++++++++++++ src/Assert/AbstractValidator.php | 2 + src/Assert/Boolean.php | 34 ++++++++ src/Assert/Collection.php | 68 ++++++++++++++++ src/Assert/Item.php | 56 +++++++++++++ src/Assert/NotEmpty.php | 26 +++++++ src/Assert/StringLength.php | 2 +- src/Validation.php | 38 ++++----- src/ValidationProcessor.php | 34 ++++++++ tests/ValidationTest.php | 130 +++++++++++++++++++++++++++++++ 10 files changed, 442 insertions(+), 23 deletions(-) create mode 100755 src/Assert/Boolean.php create mode 100755 src/Assert/Collection.php create mode 100755 src/Assert/Item.php create mode 100755 src/Assert/NotEmpty.php create mode 100755 src/ValidationProcessor.php diff --git a/README.md b/README.md index f3a9cbd..0a6d238 100755 --- a/README.md +++ b/README.md @@ -103,6 +103,81 @@ if ($validation->validateArray($data) === true) { } ``` +#### Example: Validating Nested Objects with `Item` + +The `Item` rule allows you to validate nested objects or associative arrays with specific rules for each key. + +```php +use PhpDevCommunity\Validator\Validation; +use PhpDevCommunity\Validator\Rules\NotNull; +use PhpDevCommunity\Validator\Rules\Item; +use PhpDevCommunity\Validator\Rules\StringLength; +use PhpDevCommunity\Validator\Rules\Alphabetic; + +// Define validation rules for a nested object (e.g., a "person" object) +$validation = new Validation([ + 'person' => [new NotNull(), new Item([ + 'first_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], + 'last_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], + ])] +]); + +// Example data +$data = [ + 'person' => [ + 'first_name' => 'John', + 'last_name' => 'Doe' + ] +]; + +// Validate the data +if ($validation->validateArray($data) === true) { + echo "Person object is valid!"; +} else { + $errors = $validation->getErrors(); + echo "Validation errors: " . json_encode($errors, JSON_PRETTY_PRINT); +} +``` + +#### Example: Validating Arrays of Items with `Collection` + +The `Collection` rule is used to validate arrays where each item in the array must satisfy a set of rules. + +```php +use PhpDevCommunity\Validator\Validation; +use PhpDevCommunity\Validator\Rules\NotEmpty; +use PhpDevCommunity\Validator\Rules\Collection; +use PhpDevCommunity\Validator\Rules\Item; +use PhpDevCommunity\Validator\Rules\NotNull; +use PhpDevCommunity\Validator\Rules\StringLength; + +// Define validation rules for a collection of articles +$validation = new Validation([ + 'articles' => [new NotEmpty(), new Collection([ + new Item([ + 'title' => [new NotNull(), (new StringLength())->min(3)], + 'body' => [new NotNull(), (new StringLength())->min(10)], + ]) + ])] +]); + +// Example data +$data = [ + 'articles' => [ + ['title' => 'Article 1', 'body' => 'This is the body of the first article.'], + ['title' => 'Article 2', 'body' => 'Second article body here.'] + ] +]; + +// Validate the data +if ($validation->validateArray($data) === true) { + echo "Articles are valid!"; +} else { + $errors = $validation->getErrors(); + echo "Validation errors: " . json_encode($errors, JSON_PRETTY_PRINT); +} +``` + #### URL Validation Validate a URL to ensure it is not null and is a valid URL format. diff --git a/src/Assert/AbstractValidator.php b/src/Assert/AbstractValidator.php index 72b9303..3f923b4 100755 --- a/src/Assert/AbstractValidator.php +++ b/src/Assert/AbstractValidator.php @@ -37,6 +37,8 @@ protected function error(string $message, array $context): void $value = method_exists($value, '__toString') ? (string)$value : get_class($value); } elseif (is_array($value)) { $value = json_encode($value); + } elseif (is_bool($value)) { + $value = $value ? 'true' : 'false'; } else { $value = (string)$value; } diff --git a/src/Assert/Boolean.php b/src/Assert/Boolean.php new file mode 100755 index 0000000..220e2e2 --- /dev/null +++ b/src/Assert/Boolean.php @@ -0,0 +1,34 @@ +error($this->message, ['value' => $value, 'type' => 'boolean']); + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } +} diff --git a/src/Assert/Collection.php b/src/Assert/Collection.php new file mode 100755 index 0000000..7303576 --- /dev/null +++ b/src/Assert/Collection.php @@ -0,0 +1,68 @@ + $validators + */ + private array $validators; + private array $errors = []; + + /** + * @param array $validators + */ + public function __construct(array $validators) + { + foreach ($validators as $validator) { + if ($validator instanceof ValidatorInterface === false) { + throw new \InvalidArgumentException(sprintf('The validator must be an instance of %s', ValidatorInterface::class)); + } + } + $this->validators = $validators; + } + public function validate($value): bool + { + if ($value === null) { + return true; + } + + if (is_array($value) === false) { + $this->error($this->message, ['value' => $value, 'type' => 'collection']); + return false; + } + + $validationProcessor = new ValidationProcessor(); + $errors = []; + foreach ($value as $key => $element) { + $errors = array_merge($errors, $validationProcessor->process($this->validators, $key, $element)); + } + + if ($errors !== []) { + $this->errors = $errors; + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } + + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Assert/Item.php b/src/Assert/Item.php new file mode 100755 index 0000000..9be87fe --- /dev/null +++ b/src/Assert/Item.php @@ -0,0 +1,56 @@ + $validators + */ + public function __construct(array $validators) + { + $this->validation = new Validation($validators); + } + + public function validate($value): bool + { + if ($value === null) { + return true; + } + + if (is_array($value) === false) { + $this->error($this->message, ['value' => $value, 'type' => 'array']); + return false; + } + + $this->validation->validateArray($value); + if ($this->validation->getErrors() !== []) { + $this->errors = $this->validation->getErrors(); + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } + + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Assert/NotEmpty.php b/src/Assert/NotEmpty.php new file mode 100755 index 0000000..081d1d2 --- /dev/null +++ b/src/Assert/NotEmpty.php @@ -0,0 +1,26 @@ +error($this->message, ['value' => $value]); + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } +} diff --git a/src/Assert/StringLength.php b/src/Assert/StringLength.php index 9e7c378..1b409b8 100755 --- a/src/Assert/StringLength.php +++ b/src/Assert/StringLength.php @@ -9,7 +9,7 @@ final class StringLength extends AbstractValidator { - private string $invalidMessage = 'Invalid type given. String expected.'; + private string $invalidMessage = '{{ value }} is not a valid string.'; private string $minMessage = '{{ value }} must be at least {{ limit }} characters long'; private string $maxMessage = '{{ value }} cannot be longer than {{ limit }} characters'; private ?int $min = null; diff --git a/src/Validation.php b/src/Validation.php index 303fabf..1d8d4fc 100755 --- a/src/Validation.php +++ b/src/Validation.php @@ -2,6 +2,8 @@ namespace PhpDevCommunity\Validator; +use PhpDevCommunity\Validator\Assert\Collection; +use PhpDevCommunity\Validator\Assert\Item; use PhpDevCommunity\Validator\Assert\ValidatorInterface; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; @@ -17,6 +19,8 @@ final class Validation { + private ValidationProcessor $processor; + /** * @var array */ @@ -31,6 +35,8 @@ final class Validation public function __construct(array $fieldValidators) { + $this->processor = new ValidationProcessor(); + foreach ($fieldValidators as $field => $validators) { if (!is_array($validators)) { $validators = [$validators]; @@ -66,23 +72,23 @@ public function validate(ServerRequestInterface $request): bool public function validateArray(array $data): bool { $this->data = $data; + $this->executeValidators($this->validators, $this->data); + return $this->getErrors() === []; + } + private function executeValidators(array $validatorsByField, array &$data): void + { /** * @var $validators array */ - foreach ($this->validators as $field => $validators) { - if (!isset($this->data[$field])) { - $this->data[$field] = null; - } - - foreach ($validators as $validator) { - if ($validator->validate($this->data[$field]) === false) { - $this->addError($field, (string)$validator->getError()); - } + foreach ($validatorsByField as $field => $validators) { + if (!isset($data[$field])) { + $data[$field] = null; } + $errors = $this->processor->process($validators, $field, $data[$field]); + $this->errors = array_merge($this->errors, $errors); } - return $this->getErrors() === []; } /** @@ -101,18 +107,6 @@ public function getData(): array return $this->data; } - /** - * Add an error for a specific field. - * - * @param string $field The field for which the error occurred - * @param string $message The error message - * @return void - */ - private function addError(string $field, string $message): void - { - $this->errors[$field][] = $message; - } - /** * Add a validator for a specific field. * diff --git a/src/ValidationProcessor.php b/src/ValidationProcessor.php new file mode 100755 index 0000000..22ae408 --- /dev/null +++ b/src/ValidationProcessor.php @@ -0,0 +1,34 @@ +validate($value) === true) { + continue; + } + if ($validator->getError()) { + $errors[$field][] = $validator->getError(); + } + + if ($validator instanceof Item || $validator instanceof Collection) { + foreach ($validator->getErrors() as $key => $error) { + $fullKey = sprintf('%s.%s', $field, $key); + $errors[$fullKey] = $error; + } + } + } + + return $errors; + } + +} \ No newline at end of file diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index e076597..f9d42ca 100755 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -5,9 +5,12 @@ use PhpDevCommunity\UniTester\TestCase; use PhpDevCommunity\Validator\Assert\Alphabetic; use PhpDevCommunity\Validator\Assert\Choice; +use PhpDevCommunity\Validator\Assert\Collection; use PhpDevCommunity\Validator\Assert\Custom; use PhpDevCommunity\Validator\Assert\Email; use PhpDevCommunity\Validator\Assert\Integer; +use PhpDevCommunity\Validator\Assert\Item; +use PhpDevCommunity\Validator\Assert\NotEmpty; use PhpDevCommunity\Validator\Assert\NotNull; use PhpDevCommunity\Validator\Assert\Numeric; use PhpDevCommunity\Validator\Assert\StringLength; @@ -32,6 +35,10 @@ protected function execute(): void { $this->testOk(); $this->testError(); + $this->testPersonValidation(); + $this->testEmailValidation(); + $this->testTagsValidation(); + $this->testArticlesValidation(); } public function testOk() @@ -95,5 +102,128 @@ public function testError() $this->assertStrictEquals($errors['email'][0], 'dev@phpdevcommunity is not a valid email address.'); $this->assertStrictEquals($errors['active'][0], '"yes" is not valid'); } + public function testPersonValidation(): void + { + $validation = new Validation([ + 'person' => [new NotEmpty(), new Item([ + 'first_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], + 'last_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], + ])] + ]); + + $input = [ + 'person' => [ + 'first_name' => 'John', + 'last_name' => 'Doe' + ] + ]; + + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = [ + 'person' => [ + 'first_name' => '', + 'last_name' => null + ] + ]; + + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + } + + public function testEmailValidation(): void + { + $validation = new Validation([ + 'email' => [new NotNull(), new Email()] + ]); + + $input = ['email' => 'test@example.com']; + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = ['email' => 'invalid-email']; + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + } + + public function testTagsValidation(): void + { + $validation = new Validation([ + 'tags' => [new NotEmpty(), new Collection([ + (new StringLength())->min(3) + ])] + ]); + + $input = ['tags' => ['tag1', 'tag2', 'tag3']]; + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = ['tags' => ['a', 'bc']]; + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + $this->assertStrictEquals(2, count($validation->getErrors())); + + } + + public function testArticlesValidation(): void + { + $validation = new Validation([ + 'articles' => [new NotEmpty(), new Collection([ + new Item([ + 'title' => [new NotEmpty(), (new StringLength())->min(3)], + 'body' => [new NotNull(), (new StringLength())->min(3)], + 'user' => new Item([ + 'email' => [new Email()] + ]) + ]) + ])] + ]); + + $input = [ + 'articles' => [ + [ + 'title' => 'Article 1', + 'body' => 'This is the body of the article.', + 'user' => ['email' => 'user1@example.com'] + ], + [ + 'title' => 'Article 2', + 'body' => 'Another body.', + 'user' => ['email' => 'user2@example.com'] + ] + ] + ]; + + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = [ + 'articles' => [ + [ + 'title' => '', + 'body' => '', + 'user' => ['email' => 'invalid-email'] + ], + [ + 'title' => '', + 'body' => '', + 'user' => ['email' => 'invalid-email'] + ] + ] + ]; + + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + $this->assertStrictEquals(6, count($validation->getErrors())); + } } From 11bf0c404f098e430e766863a80d1b5e24ce5fa7 Mon Sep 17 00:00:00 2001 From: phpdevcommunity Date: Sun, 26 Jan 2025 14:34:15 +0000 Subject: [PATCH 2/4] Added validation helpers: created shortcut functions for validators (alphabetic, alphanumeric, boolean, choice, etc.). Each helper allows easy instantiation of validators with an optional custom message. --- composer.json | 5 +- functions/helpers.php | 205 +++++++++++++++++++++++++++++++ src/Assert/AbstractValidator.php | 3 +- src/Assert/Boolean.php | 0 src/Validation.php | 3 + tests/HelperTest.php | 180 +++++++++++++++++++++++++++ 6 files changed, 393 insertions(+), 3 deletions(-) create mode 100644 functions/helpers.php mode change 100755 => 100644 src/Assert/Boolean.php create mode 100644 tests/HelperTest.php diff --git a/composer.json b/composer.json index 7f1d0ef..eadc2d3 100755 --- a/composer.json +++ b/composer.json @@ -13,7 +13,10 @@ "psr-4": { "PhpDevCommunity\\Validator\\": "src", "Test\\PhpDevCommunity\\Validator\\": "tests" - } + }, + "files": [ + "functions/helpers.php" + ] }, "minimum-stability": "alpha", "require": { diff --git a/functions/helpers.php b/functions/helpers.php new file mode 100644 index 0000000..5068945 --- /dev/null +++ b/functions/helpers.php @@ -0,0 +1,205 @@ +message($message); + } + return $validator; + } +} + +if (!function_exists('v_alphanumeric')) { + function v_alphanumeric(string $message = null): Alphanumeric + { + $validator = new Alphanumeric(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_boolean')) { + function v_boolean(string $message = null): PhpDevCommunity\Validator\Assert\Boolean + { + $validator = new Boolean(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + + +if (!function_exists('v_choice')) { + function v_choice(array $choices, string $message = null): Choice + { + $validator = new Choice($choices); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_collection')) { + function v_collection(array $validators, string $message = null): Collection + { + $validator = new Collection($validators); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_custom')) { + function v_custom(callable $validate, string $message = null): Custom + { + $validator = new Custom($validate); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + + +if (!function_exists('v_email')) { + function v_email(string $message = null): Email + { + $validator = new Email(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_integer')) { + function v_integer(?int $min, ?int $max = null,string $invalidMessage = null): PhpDevCommunity\Validator\Assert\Integer + { + $validator = new Integer(); + if ($invalidMessage !== null) { + $validator->invalidMessage($invalidMessage); + } + + if ($min !== null) { + $validator->min($min); + } + if ($max !== null) { + $validator->max($max); + } + + return $validator; + } +} + +if (!function_exists('v_item')) { + function v_item(array $validators, string $message = null): Item + { + $validator = new Item($validators); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_not_empty')) { + function v_not_empty(string $message = null): NotEmpty + { + $validator = new NotEmpty(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_not_null')) { + function v_not_null(string $message = null): NotNull + { + $validator = new NotNull(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + + +if (!function_exists('v_numeric')) { + function v_numeric(string $message = null): Numeric + { + $validator = new Numeric(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} + +if (!function_exists('v_psr7_upload_file')) { + function v_psr7_upload_file(?int $maxSize, array $allowedMimeTypes, string $message = null): Psr7UploadFile + { + $validator = new Psr7UploadFile(); + if ($message !== null) { + $validator->message($message); + } + if ($maxSize !== null) { + $validator->maxSize($maxSize); + } + $validator->mimeTypes($allowedMimeTypes); + return $validator; + } +} + +if (!function_exists('v_string_length')) { + function v_string_length(?int $min, ?int $max = null, string $invalidMessage = null): StringLength + { + $validator = new StringLength(); + if ($invalidMessage !== null) { + $validator->invalidMessage($invalidMessage); + } + + if ($min !== null) { + $validator->min($min); + } + if ($max !== null) { + $validator->max($max); + } + + return $validator; + } +} + +if (!function_exists('v_url')) { + function v_url(string $message = null): Url + { + $validator = new Url(); + if ($message !== null) { + $validator->message($message); + } + return $validator; + } +} diff --git a/src/Assert/AbstractValidator.php b/src/Assert/AbstractValidator.php index 3f923b4..95ab3f9 100755 --- a/src/Assert/AbstractValidator.php +++ b/src/Assert/AbstractValidator.php @@ -20,8 +20,7 @@ public function getError(): ?string { return $this->error; } - - + /** * Set the error message by replacing placeholders with values from the context array * diff --git a/src/Assert/Boolean.php b/src/Assert/Boolean.php old mode 100755 new mode 100644 diff --git a/src/Validation.php b/src/Validation.php index 1d8d4fc..e9cb97b 100755 --- a/src/Validation.php +++ b/src/Validation.php @@ -41,6 +41,9 @@ public function __construct(array $fieldValidators) if (!is_array($validators)) { $validators = [$validators]; } + if (!is_string($field)) { + throw new InvalidArgumentException('The field name must be a string'); + } $this->addValidator($field, $validators); } } diff --git a/tests/HelperTest.php b/tests/HelperTest.php new file mode 100644 index 0000000..26b107a --- /dev/null +++ b/tests/HelperTest.php @@ -0,0 +1,180 @@ +testVAlphabetic(); + $this->testVAlphanumeric(); + $this->testVBoolean(); + $this->testVChoice(); + $this->testVCollection(); + $this->testVCustom(); + $this->testVEmail(); + $this->testVInteger(); + $this->testVItem(); + $this->testVNotEmpty(); + $this->testVNotNull(); + $this->testVNumeric(); + $this->testVPsr7UploadFile(); + $this->testVStringLength(); + $this->testVUrl(); + } + + public function testVAlphabetic() + { + $validator = v_alphabetic('Custom error message'); + $validator->validate('123456'); + $this->assertInstanceOf(Alphabetic::class, $validator); + $this->assertEquals('Custom error message', $validator->getError()); + } + + public function testVAlphanumeric() + { + $validator = v_alphanumeric('Custom message'); + $validator->validate('ue$ueue'); + $this->assertInstanceOf(Alphanumeric::class, $validator); + $this->assertEquals('Custom message', $validator->getError()); + } + + public function testVBoolean() + { + $validator = v_boolean('Invalid boolean'); + $validator->validate(-1); + $this->assertInstanceOf(Boolean::class, $validator); + $this->assertEquals('Invalid boolean', $validator->getError()); + } + public function testVChoice() + { + $choices = ['yes', 'no']; + $validator = v_choice($choices, 'Invalid choice'); + $validator->validate('non'); + $this->assertInstanceOf(Choice::class, $validator); + $this->assertEquals('Invalid choice', $validator->getError()); + } + + + public function testVCollection() + { + $validator = v_collection([v_alphabetic(), v_numeric()], 'Invalid collection'); + $validator->validate('["123", "456"]'); + $this->assertInstanceOf(Collection::class, $validator); + $this->assertEquals('Invalid collection', $validator->getError()); + } + + public function testVCustom() + { + $validator = v_custom(function($value) { return is_string($value); }, 'Invalid custom validation'); + $validator->validate([]); + $this->assertInstanceOf(Custom::class, $validator); + $this->assertEquals('Invalid custom validation', $validator->getError()); + } + + public function testVEmail() + { + $validator = v_email('Invalid email'); + $validator->validate('testnote@.com'); + $this->assertInstanceOf(Email::class, $validator); + $this->assertEquals('Invalid email', $validator->getError()); + } + + public function testVInteger() + { + $validator = v_integer(10, 100, 'Invalid integer'); + $validator->validate(100.25); + $this->assertInstanceOf(Integer::class, $validator); + $this->assertEquals('Invalid integer', $validator->getError()); + } + + public function testVItem() + { + $validator = v_item([ + 'email' => v_alphabetic(), + 'password' => v_numeric() + ], 'Invalid item'); + $validator->validate(''); + $this->assertInstanceOf(Item::class, $validator); + $this->assertEquals('Invalid item', $validator->getError()); + } + + public function testVNotEmpty() + { + $validator = v_not_empty('Cannot be empty'); + $validator->validate(''); + $this->assertInstanceOf(NotEmpty::class, $validator); + $this->assertEquals('Cannot be empty', $validator->getError()); + } + + public function testVNotNull() + { + $validator = v_not_null('Cannot be null'); + $validator->validate(null); + $this->assertInstanceOf(NotNull::class, $validator); + $this->assertEquals('Cannot be null', $validator->getError()); + } + + public function testVNumeric() + { + $validator = v_numeric('Invalid numeric value'); + $validator->validate('100.25€'); + $this->assertInstanceOf(Numeric::class, $validator); + $this->assertEquals('Invalid numeric value', $validator->getError()); + } + + public function testVPsr7UploadFile() + { + $validator = v_psr7_upload_file(100000, ['image/jpeg'], 'Invalid file upload instance'); + $validator->validate('test.jpg'); + $this->assertInstanceOf(Psr7UploadFile::class, $validator); + $this->assertEquals('Invalid file upload instance', $validator->getError()); + } + + public function testVStringLength() + { + $validator = v_string_length(5, 100, 'String length invalid'); + $validator->validate(12345); + $this->assertInstanceOf(StringLength::class, $validator); + $this->assertEquals('String length invalid', $validator->getError()); + } + + public function testVUrl() + { + $validator = v_url('Invalid URL'); + $validator->validate('www.phpdevcommunity.com'); + $this->assertInstanceOf(Url::class, $validator); + $this->assertEquals('Invalid URL', $validator->getError()); + } + +} From 8c2d317b91517f4c70366780ad482c5403aa4067 Mon Sep 17 00:00:00 2001 From: phpdevcommunity Date: Sun, 26 Jan 2025 14:36:43 +0000 Subject: [PATCH 3/4] improve readme --- README.md | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 0a6d238..282f758 100755 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Validate an email address to ensure it is not null and matches the standard emai ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Email; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Email; // Instantiate Validation object for email validation $validation = new Validation([ @@ -52,8 +52,8 @@ Validate the age to ensure it is a non-null integer and is 18 or older. ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Integer; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Integer; // Instantiate Validation object for age validation $validation = new Validation([ @@ -82,9 +82,9 @@ Ensure that a username is not null, has a minimum length of 3 characters, and co ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Alphanumeric; -use PhpDevCommunity\Validator\Rules\StringLength; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Alphanumeric; +use PhpDevCommunity\Validator\Assert\StringLength; // Instantiate Validation object for username validation $validation = new Validation([ @@ -109,10 +109,10 @@ The `Item` rule allows you to validate nested objects or associative arrays with ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Item; -use PhpDevCommunity\Validator\Rules\StringLength; -use PhpDevCommunity\Validator\Rules\Alphabetic; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Item; +use PhpDevCommunity\Validator\Assert\StringLength; +use PhpDevCommunity\Validator\Assert\Alphabetic; // Define validation rules for a nested object (e.g., a "person" object) $validation = new Validation([ @@ -145,11 +145,11 @@ The `Collection` rule is used to validate arrays where each item in the array mu ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotEmpty; -use PhpDevCommunity\Validator\Rules\Collection; -use PhpDevCommunity\Validator\Rules\Item; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\StringLength; +use PhpDevCommunity\Validator\Assert\NotEmpty; +use PhpDevCommunity\Validator\Assert\Collection; +use PhpDevCommunity\Validator\Assert\Item; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\StringLength; // Define validation rules for a collection of articles $validation = new Validation([ @@ -184,8 +184,8 @@ Validate a URL to ensure it is not null and is a valid URL format. ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Url; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Url; // Instantiate Validation object for URL validation $validation = new Validation([ @@ -210,8 +210,8 @@ Validate a numeric value to ensure it is not null and represents a valid numeric ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Numeric; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Numeric; // Instantiate Validation object for numeric value validation $validation = new Validation([ @@ -236,8 +236,8 @@ Implement a custom validation rule using a callback function. ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Custom; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Custom; // Custom validation function to check if the value is a boolean $isBoolean = function ($value) { @@ -273,9 +273,9 @@ Suppose you have a user registration form with fields like `username`, `email`, ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Email; -use PhpDevCommunity\Validator\Rules\Integer; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Email; +use PhpDevCommunity\Validator\Assert\Integer; // Define validation rules for each field $validation = new Validation([ @@ -315,8 +315,8 @@ Consider validating input data received via an API endpoint. Here's how you can ```php use PhpDevCommunity\Validator\Validation; -use PhpDevCommunity\Validator\Rules\NotNull; -use PhpDevCommunity\Validator\Rules\Numeric; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Numeric; // Define validation rules for API input data $validation = new Validation([ @@ -351,7 +351,7 @@ In this example: ### Additional Features - **Simple Interface**: Easily define validation rules using a straightforward interface. -- **Extensible**: Extend the library with custom validation rules by implementing the `RuleInterface`. +- **Extensible**: Extend the library with custom validation rules by implementing the `ValidatorInterface`. - **Error Handling**: Retrieve detailed validation errors for each field. --- From df1406d9fbcfd78b04c8771f06e1013dfeca910a Mon Sep 17 00:00:00 2001 From: phpdevcommunity Date: Wed, 17 Sep 2025 06:35:28 +0000 Subject: [PATCH 4/4] feat: add convertEmptyToNull support for validateArray() --- .gitignore | 3 +- README.md | 47 ++++ src/Assert/Collection.php | 150 ++++++----- src/Assert/Item.php | 124 +++++---- src/Assert/StringLength.php | 142 +++++----- src/Validation.php | 273 +++++++++---------- src/ValidationProcessor.php | 93 ++++--- tests/Helper/Request.php | 348 ++++++++++++------------ tests/HelperTest.php | 360 ++++++++++++------------- tests/ValidationTest.php | 508 ++++++++++++++++++++---------------- 10 files changed, 1102 insertions(+), 946 deletions(-) diff --git a/.gitignore b/.gitignore index 8f0082a..6db3ec6 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ -/.idea/ \ No newline at end of file +/.idea/ +composer.lock diff --git a/README.md b/README.md index 282f758..e4ab562 100755 --- a/README.md +++ b/README.md @@ -72,6 +72,53 @@ if ($validation->validateArray($data) === true) { } ``` +### Example 3: Converting Empty Strings to `null` + +By default, when using `validate(ServerRequestInterface $request)`, the **convertEmptyToNull** option is automatically enabled. +This ensures that all empty strings (`""`) are converted to `null` before validation. + +When using `validateArray(array $data)` directly, you need to enable this option manually: + +```php +use PhpDevCommunity\Validator\Validation; +use PhpDevCommunity\Validator\Assert\NotNull; +use PhpDevCommunity\Validator\Assert\Item; +use PhpDevCommunity\Validator\Assert\Alphabetic; + +// Define validation rules +$validation = new Validation([ + 'person' => [new NotNull(), new Item([ + 'first_name' => [new NotNull(), new Alphabetic()], + 'last_name' => [new NotNull(), new Alphabetic()], + ])] +]); + +// Example input with an empty string +$validInput = [ + 'person' => [ + 'first_name' => '', // will be converted to null + 'last_name' => 'Doe' + ] +]; + +// Manually enable conversion of "" to null +$validation->convertEmptyToNull(); + +if ($validation->validateArray($validInput) === true) { + echo "Data is valid!"; +} else { + $errors = $validation->getErrors(); + echo "Validation errors: " . json_encode($errors, JSON_PRETTY_PRINT); +} +``` + +In this example: + +* Before validation, empty strings are converted to `null`. +* This prevents validators such as `NotNull()` from failing because of an empty string. +* When using `validate($request)` with a PSR-7 request, this option is automatically enabled. + + ### Additional Examples Let's explore more examples covering various validators: diff --git a/src/Assert/Collection.php b/src/Assert/Collection.php index 7303576..3d9763c 100755 --- a/src/Assert/Collection.php +++ b/src/Assert/Collection.php @@ -1,68 +1,82 @@ - $validators - */ - private array $validators; - private array $errors = []; - - /** - * @param array $validators - */ - public function __construct(array $validators) - { - foreach ($validators as $validator) { - if ($validator instanceof ValidatorInterface === false) { - throw new \InvalidArgumentException(sprintf('The validator must be an instance of %s', ValidatorInterface::class)); - } - } - $this->validators = $validators; - } - public function validate($value): bool - { - if ($value === null) { - return true; - } - - if (is_array($value) === false) { - $this->error($this->message, ['value' => $value, 'type' => 'collection']); - return false; - } - - $validationProcessor = new ValidationProcessor(); - $errors = []; - foreach ($value as $key => $element) { - $errors = array_merge($errors, $validationProcessor->process($this->validators, $key, $element)); - } - - if ($errors !== []) { - $this->errors = $errors; - return false; - } - - return true; - } - - public function message(string $message): self - { - $this->message = $message; - return $this; - } - - public function getErrors(): array - { - return $this->errors; - } -} + $validators + */ + private array $validators; + + private ValidationProcessor $validationProcessor; + private array $errors = []; + + /** + * @param array $validators + */ + public function __construct(array $validators) + { + foreach ($validators as $validator) { + if ($validator instanceof ValidatorInterface === false) { + throw new \InvalidArgumentException(sprintf('The validator must be an instance of %s', ValidatorInterface::class)); + } + } + $this->validators = $validators; + $this->validationProcessor = new ValidationProcessor(); + } + public function convertEmptyToNull(): self + { + $this->validationProcessor->convertEmptyToNull(); + return $this; + } + + public function noConvertEmptyToNull(): self + { + $this->validationProcessor->noConvertEmptyToNull(); + return $this; + } + + public function validate($value): bool + { + if ($value === null) { + return true; + } + + if (is_array($value) === false) { + $this->error($this->message, ['value' => $value, 'type' => 'collection']); + return false; + } + + $errors = []; + foreach ($value as $key => $element) { + $errors = array_merge($errors, $this->validationProcessor->process($this->validators, $key, $element)); + } + + if ($errors !== []) { + $this->errors = $errors; + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } + + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Assert/Item.php b/src/Assert/Item.php index 9be87fe..541877c 100755 --- a/src/Assert/Item.php +++ b/src/Assert/Item.php @@ -1,56 +1,68 @@ - $validators - */ - public function __construct(array $validators) - { - $this->validation = new Validation($validators); - } - - public function validate($value): bool - { - if ($value === null) { - return true; - } - - if (is_array($value) === false) { - $this->error($this->message, ['value' => $value, 'type' => 'array']); - return false; - } - - $this->validation->validateArray($value); - if ($this->validation->getErrors() !== []) { - $this->errors = $this->validation->getErrors(); - return false; - } - - return true; - } - - public function message(string $message): self - { - $this->message = $message; - return $this; - } - - public function getErrors(): array - { - return $this->errors; - } -} + $validators + */ + public function __construct(array $validators) + { + $this->validation = new Validation($validators); + } + + public function convertEmptyToNull(): self + { + $this->validation->convertEmptyToNull(); + return $this; + } + + public function noConvertEmptyToNull(): self + { + $this->validation->noConvertEmptyToNull(); + return $this; + } + + public function validate($value): bool + { + if ($value === null) { + return true; + } + + if (is_array($value) === false) { + $this->error($this->message, ['value' => $value, 'type' => 'array']); + return false; + } + + $this->validation->validateArray($value); + if ($this->validation->getErrors() !== []) { + $this->errors = $this->validation->getErrors(); + return false; + } + + return true; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } + + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Assert/StringLength.php b/src/Assert/StringLength.php index 1b409b8..f533225 100755 --- a/src/Assert/StringLength.php +++ b/src/Assert/StringLength.php @@ -1,71 +1,71 @@ -error($this->invalidMessage, ['value' => $value]); - return false; - } - - if (is_int($this->min) && strlen($value) < $this->min) { - $this->error($this->minMessage, ['value' => $value, 'limit' => $this->min]); - return false; - } - - if (is_int($this->max) && strlen($value) > $this->max) { - $this->error($this->maxMessage, ['value' => $value, 'limit' => $this->max]); - return false; - } - - return true; - } - - public function invalidMessage(string $invalidMessage): self - { - $this->invalidMessage = $invalidMessage; - return $this; - } - - public function minMessage(string $minMessage): self - { - $this->minMessage = $minMessage; - return $this; - } - - public function maxMessage(string $maxMessage): self - { - $this->maxMessage = $maxMessage; - return $this; - } - - public function min(int $min): self - { - $this->min = $min; - return $this; - } - - public function max(int $max): self - { - $this->max = $max; - return $this; - } -} +error($this->invalidMessage, ['value' => $value]); + return false; + } + + if (is_int($this->min) && strlen($value) < $this->min) { + $this->error($this->minMessage, ['value' => $value, 'limit' => $this->min]); + return false; + } + + if (is_int($this->max) && strlen($value) > $this->max) { + $this->error($this->maxMessage, ['value' => $value, 'limit' => $this->max]); + return false; + } + + return true; + } + + public function invalidMessage(string $invalidMessage): self + { + $this->invalidMessage = $invalidMessage; + return $this; + } + + public function minMessage(string $minMessage): self + { + $this->minMessage = $minMessage; + return $this; + } + + public function maxMessage(string $maxMessage): self + { + $this->maxMessage = $maxMessage; + return $this; + } + + public function min(int $min): self + { + $this->min = $min; + return $this; + } + + public function max(int $max): self + { + $this->max = $max; + return $this; + } +} diff --git a/src/Validation.php b/src/Validation.php index e9cb97b..5c909f9 100755 --- a/src/Validation.php +++ b/src/Validation.php @@ -1,133 +1,140 @@ - - */ - private array $validators; - - /** - * @var array - */ - private array $errors = []; - - private array $data = []; - - public function __construct(array $fieldValidators) - { - $this->processor = new ValidationProcessor(); - - foreach ($fieldValidators as $field => $validators) { - if (!is_array($validators)) { - $validators = [$validators]; - } - if (!is_string($field)) { - throw new InvalidArgumentException('The field name must be a string'); - } - $this->addValidator($field, $validators); - } - } - - /** - * Validate the server request data. - * - * @param ServerRequestInterface $request The server request to validate - * @return bool - */ - public function validate(ServerRequestInterface $request): bool - { - $data = array_map(function ($value) { - if (is_string($value) && empty(trim($value))) { - return null; - } - return $value; - }, array_merge($request->getParsedBody(), $request->getUploadedFiles())); - - return $this->validateArray($data); - } - - /** - * Validate an array of data using a set of validators. - * - * @param array $data The array of data to be validated - * @return bool - */ - public function validateArray(array $data): bool - { - $this->data = $data; - $this->executeValidators($this->validators, $this->data); - return $this->getErrors() === []; - } - - private function executeValidators(array $validatorsByField, array &$data): void - { - /** - * @var $validators array - */ - foreach ($validatorsByField as $field => $validators) { - if (!isset($data[$field])) { - $data[$field] = null; - } - - $errors = $this->processor->process($validators, $field, $data[$field]); - $this->errors = array_merge($this->errors, $errors); - } - } - - /** - * @return array - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return array - */ - public function getData(): array - { - return $this->data; - } - - /** - * Add a validator for a specific field. - * - * @param string $field The field to validate - * @param array $validators The validators to apply - * @return void - */ - private function addValidator(string $field, array $validators): void - { - foreach ($validators as $validator) { - if (!$validator instanceof ValidatorInterface) { - throw new InvalidArgumentException(sprintf( - $field . ' validator must be an instance of ValidatorInterface, "%s" given.', - is_object($validator) ? get_class($validator) : gettype($validator) - )); - } - - $this->validators[$field][] = $validator; - } - } -} + + */ + private array $validators; + + /** + * @var array + */ + private array $errors = []; + + private array $data = []; + + public function __construct(array $fieldValidators) + { + $this->processor = new ValidationProcessor(); + + foreach ($fieldValidators as $field => $validators) { + if (!is_array($validators)) { + $validators = [$validators]; + } + if (!is_string($field)) { + throw new InvalidArgumentException('The field name must be a string'); + } + $this->addValidator($field, $validators); + } + } + + public function convertEmptyToNull(): void + { + $this->processor->convertEmptyToNull(); + } + + public function noConvertEmptyToNull(): void + { + $this->processor->noConvertEmptyToNull(); + } + + /** + * Validate the server request data. + * + * @param ServerRequestInterface $request The server request to validate + * @return bool + */ + public function validate(ServerRequestInterface $request): bool + { + $this->convertEmptyToNull(); + $result = $this->validateArray(array_merge($request->getParsedBody(), $request->getUploadedFiles())); + $this->noConvertEmptyToNull(); + return $result; + } + + /** + * Validate an array of data using a set of validators. + * + * @param array $data The array of data to be validated + * @return bool + */ + public function validateArray(array $data): bool + { + $this->data = $data; + $this->executeValidators($this->validators, $this->data); + return $this->getErrors() === []; + } + + private function executeValidators(array $validatorsByField, array &$data): void + { + $this->errors = []; + /** + * @var $validators array + */ + foreach ($validatorsByField as $field => $validators) { + if (!isset($data[$field])) { + $data[$field] = null; + } + + $errors = $this->processor->process($validators, $field, $data[$field]); + $this->errors = array_merge($this->errors, $errors); + } + } + + /** + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * Add a validator for a specific field. + * + * @param string $field The field to validate + * @param array $validators The validators to apply + * @return void + */ + private function addValidator(string $field, array $validators): void + { + foreach ($validators as $validator) { + if (!$validator instanceof ValidatorInterface) { + throw new InvalidArgumentException(sprintf( + $field . ' validator must be an instance of ValidatorInterface, "%s" given.', + is_object($validator) ? get_class($validator) : gettype($validator) + )); + } + + $this->validators[$field][] = $validator; + } + } +} diff --git a/src/ValidationProcessor.php b/src/ValidationProcessor.php index 22ae408..57818ce 100755 --- a/src/ValidationProcessor.php +++ b/src/ValidationProcessor.php @@ -1,34 +1,59 @@ -validate($value) === true) { - continue; - } - if ($validator->getError()) { - $errors[$field][] = $validator->getError(); - } - - if ($validator instanceof Item || $validator instanceof Collection) { - foreach ($validator->getErrors() as $key => $error) { - $fullKey = sprintf('%s.%s', $field, $key); - $errors[$fullKey] = $error; - } - } - } - - return $errors; - } - -} \ No newline at end of file +convertEmptyToNull === true) { + if ( empty($value) ) { + $value = null; + } + } + $validatorIsItemOrCollection = ($validator instanceof Item || $validator instanceof Collection); + if ($validatorIsItemOrCollection) { + if ($this->convertEmptyToNull === true) { + $validator->convertEmptyToNull(); + }else { + $validator->noConvertEmptyToNull(); + } + } + + if ($validator->validate($value) === true) { + continue; + } + + if ($validator->getError()) { + $errors[$field][] = $validator->getError(); + } + + if ($validatorIsItemOrCollection) { + foreach ($validator->getErrors() as $key => $error) { + $fullKey = sprintf('%s.%s', $field, $key); + $errors[$fullKey] = $error; + } + } + } + + return $errors; + } + + public function convertEmptyToNull(): void + { + $this->convertEmptyToNull = true; + } + + public function noConvertEmptyToNull(): void + { + $this->convertEmptyToNull = false; + } +} diff --git a/tests/Helper/Request.php b/tests/Helper/Request.php index 62c9428..2103956 100755 --- a/tests/Helper/Request.php +++ b/tests/Helper/Request.php @@ -1,174 +1,174 @@ -postData; - } - - public function withParsedBody($data): \Psr\Http\Message\ServerRequestInterface - { - $this->postData = $data; - return clone $this; - } - - public function getProtocolVersion(): string - { - // TODO: Implement getProtocolVersion() method. - } - - public function withProtocolVersion(string $version): MessageInterface - { - // TODO: Implement withProtocolVersion() method. - } - - public function getHeaders(): array - { - // TODO: Implement getHeaders() method. - } - - public function hasHeader(string $name): bool - { - // TODO: Implement hasHeader() method. - } - - public function getHeader(string $name): array - { - // TODO: Implement getHeader() method. - } - - public function getHeaderLine(string $name): string - { - // TODO: Implement getHeaderLine() method. - } - - public function withHeader(string $name, $value): MessageInterface - { - // TODO: Implement withHeader() method. - } - - public function withAddedHeader(string $name, $value): MessageInterface - { - // TODO: Implement withAddedHeader() method. - } - - public function withoutHeader(string $name): MessageInterface - { - // TODO: Implement withoutHeader() method. - } - - public function getBody(): StreamInterface - { - // TODO: Implement getBody() method. - } - - public function withBody(StreamInterface $body): MessageInterface - { - // TODO: Implement withBody() method. - } - - public function getRequestTarget(): string - { - // TODO: Implement getRequestTarget() method. - } - - public function withRequestTarget(string $requestTarget): RequestInterface - { - // TODO: Implement withRequestTarget() method. - } - - public function getMethod(): string - { - // TODO: Implement getMethod() method. - } - - public function withMethod(string $method): RequestInterface - { - // TODO: Implement withMethod() method. - } - - public function getUri(): UriInterface - { - // TODO: Implement getUri() method. - } - - public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface - { - // TODO: Implement withUri() method. - } - - public function getServerParams(): array - { - // TODO: Implement getServerParams() method. - } - - public function getCookieParams(): array - { - // TODO: Implement getCookieParams() method. - } - - public function withCookieParams(array $cookies): ServerRequestInterface - { - // TODO: Implement withCookieParams() method. - } - - public function getQueryParams(): array - { - // TODO: Implement getQueryParams() method. - } - - public function withQueryParams(array $query): ServerRequestInterface - { - // TODO: Implement withQueryParams() method. - } - - public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface - { - // TODO: Implement withUploadedFiles() method. - } - - public function getAttributes(): array - { - // TODO: Implement getAttributes() method. - } - - public function getAttribute(string $name, $default = null) - { - // TODO: Implement getAttribute() method. - } - - public function withAttribute(string $name, $value): ServerRequestInterface - { - // TODO: Implement withAttribute() method. - } - - public function withoutAttribute(string $name): ServerRequestInterface - { - // TODO: Implement withoutAttribute() method. - } - }; - $request->withParsedBody($postData); - return $request; - } -} +postData; + } + + public function withParsedBody($data): \Psr\Http\Message\ServerRequestInterface + { + $this->postData = $data; + return clone $this; + } + + public function getProtocolVersion(): string + { + // TODO: Implement getProtocolVersion() method. + } + + public function withProtocolVersion(string $version): MessageInterface + { + // TODO: Implement withProtocolVersion() method. + } + + public function getHeaders(): array + { + // TODO: Implement getHeaders() method. + } + + public function hasHeader(string $name): bool + { + // TODO: Implement hasHeader() method. + } + + public function getHeader(string $name): array + { + // TODO: Implement getHeader() method. + } + + public function getHeaderLine(string $name): string + { + // TODO: Implement getHeaderLine() method. + } + + public function withHeader(string $name, $value): MessageInterface + { + // TODO: Implement withHeader() method. + } + + public function withAddedHeader(string $name, $value): MessageInterface + { + // TODO: Implement withAddedHeader() method. + } + + public function withoutHeader(string $name): MessageInterface + { + // TODO: Implement withoutHeader() method. + } + + public function getBody(): StreamInterface + { + // TODO: Implement getBody() method. + } + + public function withBody(StreamInterface $body): MessageInterface + { + // TODO: Implement withBody() method. + } + + public function getRequestTarget(): string + { + // TODO: Implement getRequestTarget() method. + } + + public function withRequestTarget(string $requestTarget): RequestInterface + { + // TODO: Implement withRequestTarget() method. + } + + public function getMethod(): string + { + // TODO: Implement getMethod() method. + } + + public function withMethod(string $method): RequestInterface + { + // TODO: Implement withMethod() method. + } + + public function getUri(): UriInterface + { + // TODO: Implement getUri() method. + } + + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface + { + // TODO: Implement withUri() method. + } + + public function getServerParams(): array + { + // TODO: Implement getServerParams() method. + } + + public function getCookieParams(): array + { + // TODO: Implement getCookieParams() method. + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + // TODO: Implement withCookieParams() method. + } + + public function getQueryParams(): array + { + // TODO: Implement getQueryParams() method. + } + + public function withQueryParams(array $query): ServerRequestInterface + { + // TODO: Implement withQueryParams() method. + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + // TODO: Implement withUploadedFiles() method. + } + + public function getAttributes(): array + { + // TODO: Implement getAttributes() method. + } + + public function getAttribute(string $name, $default = null) + { + // TODO: Implement getAttribute() method. + } + + public function withAttribute(string $name, $value): ServerRequestInterface + { + // TODO: Implement withAttribute() method. + } + + public function withoutAttribute(string $name): ServerRequestInterface + { + // TODO: Implement withoutAttribute() method. + } + }; + $request->withParsedBody($postData); + return $request; + } +} diff --git a/tests/HelperTest.php b/tests/HelperTest.php index 26b107a..c3b7273 100644 --- a/tests/HelperTest.php +++ b/tests/HelperTest.php @@ -1,180 +1,180 @@ -testVAlphabetic(); - $this->testVAlphanumeric(); - $this->testVBoolean(); - $this->testVChoice(); - $this->testVCollection(); - $this->testVCustom(); - $this->testVEmail(); - $this->testVInteger(); - $this->testVItem(); - $this->testVNotEmpty(); - $this->testVNotNull(); - $this->testVNumeric(); - $this->testVPsr7UploadFile(); - $this->testVStringLength(); - $this->testVUrl(); - } - - public function testVAlphabetic() - { - $validator = v_alphabetic('Custom error message'); - $validator->validate('123456'); - $this->assertInstanceOf(Alphabetic::class, $validator); - $this->assertEquals('Custom error message', $validator->getError()); - } - - public function testVAlphanumeric() - { - $validator = v_alphanumeric('Custom message'); - $validator->validate('ue$ueue'); - $this->assertInstanceOf(Alphanumeric::class, $validator); - $this->assertEquals('Custom message', $validator->getError()); - } - - public function testVBoolean() - { - $validator = v_boolean('Invalid boolean'); - $validator->validate(-1); - $this->assertInstanceOf(Boolean::class, $validator); - $this->assertEquals('Invalid boolean', $validator->getError()); - } - public function testVChoice() - { - $choices = ['yes', 'no']; - $validator = v_choice($choices, 'Invalid choice'); - $validator->validate('non'); - $this->assertInstanceOf(Choice::class, $validator); - $this->assertEquals('Invalid choice', $validator->getError()); - } - - - public function testVCollection() - { - $validator = v_collection([v_alphabetic(), v_numeric()], 'Invalid collection'); - $validator->validate('["123", "456"]'); - $this->assertInstanceOf(Collection::class, $validator); - $this->assertEquals('Invalid collection', $validator->getError()); - } - - public function testVCustom() - { - $validator = v_custom(function($value) { return is_string($value); }, 'Invalid custom validation'); - $validator->validate([]); - $this->assertInstanceOf(Custom::class, $validator); - $this->assertEquals('Invalid custom validation', $validator->getError()); - } - - public function testVEmail() - { - $validator = v_email('Invalid email'); - $validator->validate('testnote@.com'); - $this->assertInstanceOf(Email::class, $validator); - $this->assertEquals('Invalid email', $validator->getError()); - } - - public function testVInteger() - { - $validator = v_integer(10, 100, 'Invalid integer'); - $validator->validate(100.25); - $this->assertInstanceOf(Integer::class, $validator); - $this->assertEquals('Invalid integer', $validator->getError()); - } - - public function testVItem() - { - $validator = v_item([ - 'email' => v_alphabetic(), - 'password' => v_numeric() - ], 'Invalid item'); - $validator->validate(''); - $this->assertInstanceOf(Item::class, $validator); - $this->assertEquals('Invalid item', $validator->getError()); - } - - public function testVNotEmpty() - { - $validator = v_not_empty('Cannot be empty'); - $validator->validate(''); - $this->assertInstanceOf(NotEmpty::class, $validator); - $this->assertEquals('Cannot be empty', $validator->getError()); - } - - public function testVNotNull() - { - $validator = v_not_null('Cannot be null'); - $validator->validate(null); - $this->assertInstanceOf(NotNull::class, $validator); - $this->assertEquals('Cannot be null', $validator->getError()); - } - - public function testVNumeric() - { - $validator = v_numeric('Invalid numeric value'); - $validator->validate('100.25€'); - $this->assertInstanceOf(Numeric::class, $validator); - $this->assertEquals('Invalid numeric value', $validator->getError()); - } - - public function testVPsr7UploadFile() - { - $validator = v_psr7_upload_file(100000, ['image/jpeg'], 'Invalid file upload instance'); - $validator->validate('test.jpg'); - $this->assertInstanceOf(Psr7UploadFile::class, $validator); - $this->assertEquals('Invalid file upload instance', $validator->getError()); - } - - public function testVStringLength() - { - $validator = v_string_length(5, 100, 'String length invalid'); - $validator->validate(12345); - $this->assertInstanceOf(StringLength::class, $validator); - $this->assertEquals('String length invalid', $validator->getError()); - } - - public function testVUrl() - { - $validator = v_url('Invalid URL'); - $validator->validate('www.phpdevcommunity.com'); - $this->assertInstanceOf(Url::class, $validator); - $this->assertEquals('Invalid URL', $validator->getError()); - } - -} +testVAlphabetic(); + $this->testVAlphanumeric(); + $this->testVBoolean(); + $this->testVChoice(); + $this->testVCollection(); + $this->testVCustom(); + $this->testVEmail(); + $this->testVInteger(); + $this->testVItem(); + $this->testVNotEmpty(); + $this->testVNotNull(); + $this->testVNumeric(); + $this->testVPsr7UploadFile(); + $this->testVStringLength(); + $this->testVUrl(); + } + + public function testVAlphabetic() + { + $validator = v_alphabetic('Custom error message'); + $validator->validate('123456'); + $this->assertInstanceOf(Alphabetic::class, $validator); + $this->assertEquals('Custom error message', $validator->getError()); + } + + public function testVAlphanumeric() + { + $validator = v_alphanumeric('Custom message'); + $validator->validate('ue$ueue'); + $this->assertInstanceOf(Alphanumeric::class, $validator); + $this->assertEquals('Custom message', $validator->getError()); + } + + public function testVBoolean() + { + $validator = v_boolean('Invalid boolean'); + $validator->validate(-1); + $this->assertInstanceOf(Boolean::class, $validator); + $this->assertEquals('Invalid boolean', $validator->getError()); + } + public function testVChoice() + { + $choices = ['yes', 'no']; + $validator = v_choice($choices, 'Invalid choice'); + $validator->validate('non'); + $this->assertInstanceOf(Choice::class, $validator); + $this->assertEquals('Invalid choice', $validator->getError()); + } + + + public function testVCollection() + { + $validator = v_collection([v_alphabetic(), v_numeric()], 'Invalid collection'); + $validator->validate('["123", "456"]'); + $this->assertInstanceOf(Collection::class, $validator); + $this->assertEquals('Invalid collection', $validator->getError()); + } + + public function testVCustom() + { + $validator = v_custom(function($value) { return is_string($value); }, 'Invalid custom validation'); + $validator->validate([]); + $this->assertInstanceOf(Custom::class, $validator); + $this->assertEquals('Invalid custom validation', $validator->getError()); + } + + public function testVEmail() + { + $validator = v_email('Invalid email'); + $validator->validate('testnote@.com'); + $this->assertInstanceOf(Email::class, $validator); + $this->assertEquals('Invalid email', $validator->getError()); + } + + public function testVInteger() + { + $validator = v_integer(10, 100, 'Invalid integer'); + $validator->validate(100.25); + $this->assertInstanceOf(Integer::class, $validator); + $this->assertEquals('Invalid integer', $validator->getError()); + } + + public function testVItem() + { + $validator = v_item([ + 'email' => v_alphabetic(), + 'password' => v_numeric() + ], 'Invalid item'); + $validator->validate(''); + $this->assertInstanceOf(Item::class, $validator); + $this->assertEquals('Invalid item', $validator->getError()); + } + + public function testVNotEmpty() + { + $validator = v_not_empty('Cannot be empty'); + $validator->validate(''); + $this->assertInstanceOf(NotEmpty::class, $validator); + $this->assertEquals('Cannot be empty', $validator->getError()); + } + + public function testVNotNull() + { + $validator = v_not_null('Cannot be null'); + $validator->validate(null); + $this->assertInstanceOf(NotNull::class, $validator); + $this->assertEquals('Cannot be null', $validator->getError()); + } + + public function testVNumeric() + { + $validator = v_numeric('Invalid numeric value'); + $validator->validate('100.25€'); + $this->assertInstanceOf(Numeric::class, $validator); + $this->assertEquals('Invalid numeric value', $validator->getError()); + } + + public function testVPsr7UploadFile() + { + $validator = v_psr7_upload_file(100000, ['image/jpeg'], 'Invalid file upload instance'); + $validator->validate('test.jpg'); + $this->assertInstanceOf(Psr7UploadFile::class, $validator); + $this->assertEquals('Invalid file upload instance', $validator->getError()); + } + + public function testVStringLength() + { + $validator = v_string_length(5, 100, 'String length invalid'); + $validator->validate(12345); + $this->assertInstanceOf(StringLength::class, $validator); + $this->assertEquals('String length invalid', $validator->getError()); + } + + public function testVUrl() + { + $validator = v_url('Invalid URL'); + $validator->validate('www.phpdevcommunity.com'); + $this->assertInstanceOf(Url::class, $validator); + $this->assertEquals('Invalid URL', $validator->getError()); + } + +} diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index f9d42ca..1c5d440 100755 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -1,229 +1,279 @@ -testOk(); - $this->testError(); - $this->testPersonValidation(); - $this->testEmailValidation(); - $this->testTagsValidation(); - $this->testArticlesValidation(); - } - - public function testOk() - { - $validation = new Validation([ - 'email' => [new NotNull(), new Email()], - 'password' => new NotNull(), - 'firstname' => [new NotNull(), (new StringLength())->min(3), new Alphabetic()], - 'lastname' => [(new StringLength())->min(3)], - 'gender' => new Choice(['Mme', 'Mr', null]), - 'website' => [new NotNull(), new Url()], - 'age' => [new NotNull(), (new Integer())->min(18)], - 'invoice_total' => [new NotNull(), new Numeric()], - 'active' => [new NotNull(), new Custom(function ($value) { - return is_bool($value); - })] - ]); - - $this->assertTrue($validation->validate(Request::create([ - 'email' => 'dev@phpdevcommunity.com', - 'password' => 'Mypassword', - 'firstname' => 'phpdevcommunity', - 'lastname' => null, - 'gender' => 'Mr', - 'website' => 'https://www.phpdevcommunity.com', - 'age' => 20, - 'invoice_total' => '2000.25', - 'active' => true, - ]))); - } - - public function testError() - { - $validation = new Validation([ - 'email' => [new NotNull(), new Email()], - 'password' => new NotNull(), - 'firstname' => [new NotNull(), (new StringLength())->min(3), new Alphabetic()], - 'lastname' => [(new StringLength())->min(3)], - 'website' => [new NotNull(), new Url()], - 'invoice_total' => [new NotNull(), new Numeric()], - 'active' => [new NotNull(), new Custom(function ($value) { - return is_bool($value); - })] - ]); - - $this->assertFalse($validation->validate(Request::create([ - 'email' => 'dev@phpdevcommunity', - 'password' => null, - 'firstname' => '12', - 'lastname' => '12', - 'website' => 'www.phpdevcommunity', - 'invoice_total' => 'test2000.25', - 'active' => 'yes', - ]))); - - - $this->assertStrictEquals(7, count($validation->getErrors())); - $errors = $validation->getErrors(); - $this->assertStrictEquals(2, count($errors['firstname'])); - - $this->assertStrictEquals($errors['email'][0], 'dev@phpdevcommunity is not a valid email address.'); - $this->assertStrictEquals($errors['active'][0], '"yes" is not valid'); - } - public function testPersonValidation(): void - { - $validation = new Validation([ - 'person' => [new NotEmpty(), new Item([ - 'first_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], - 'last_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], - ])] - ]); - - $input = [ - 'person' => [ - 'first_name' => 'John', - 'last_name' => 'Doe' - ] - ]; - - $result = $validation->validate(Request::create($input)); - $this->assertTrue($result); - $this->assertEmpty($validation->getErrors()); - - $invalidInput = [ - 'person' => [ - 'first_name' => '', - 'last_name' => null - ] - ]; - - $result = $validation->validate(Request::create($invalidInput)); - $this->assertFalse($result); - $this->assertNotEmpty($validation->getErrors()); - } - - public function testEmailValidation(): void - { - $validation = new Validation([ - 'email' => [new NotNull(), new Email()] - ]); - - $input = ['email' => 'test@example.com']; - $result = $validation->validate(Request::create($input)); - $this->assertTrue($result); - $this->assertEmpty($validation->getErrors()); - - $invalidInput = ['email' => 'invalid-email']; - $result = $validation->validate(Request::create($invalidInput)); - $this->assertFalse($result); - $this->assertNotEmpty($validation->getErrors()); - } - - public function testTagsValidation(): void - { - $validation = new Validation([ - 'tags' => [new NotEmpty(), new Collection([ - (new StringLength())->min(3) - ])] - ]); - - $input = ['tags' => ['tag1', 'tag2', 'tag3']]; - $result = $validation->validate(Request::create($input)); - $this->assertTrue($result); - $this->assertEmpty($validation->getErrors()); - - $invalidInput = ['tags' => ['a', 'bc']]; - $result = $validation->validate(Request::create($invalidInput)); - $this->assertFalse($result); - $this->assertNotEmpty($validation->getErrors()); - $this->assertStrictEquals(2, count($validation->getErrors())); - - } - - public function testArticlesValidation(): void - { - $validation = new Validation([ - 'articles' => [new NotEmpty(), new Collection([ - new Item([ - 'title' => [new NotEmpty(), (new StringLength())->min(3)], - 'body' => [new NotNull(), (new StringLength())->min(3)], - 'user' => new Item([ - 'email' => [new Email()] - ]) - ]) - ])] - ]); - - $input = [ - 'articles' => [ - [ - 'title' => 'Article 1', - 'body' => 'This is the body of the article.', - 'user' => ['email' => 'user1@example.com'] - ], - [ - 'title' => 'Article 2', - 'body' => 'Another body.', - 'user' => ['email' => 'user2@example.com'] - ] - ] - ]; - - $result = $validation->validate(Request::create($input)); - $this->assertTrue($result); - $this->assertEmpty($validation->getErrors()); - - $invalidInput = [ - 'articles' => [ - [ - 'title' => '', - 'body' => '', - 'user' => ['email' => 'invalid-email'] - ], - [ - 'title' => '', - 'body' => '', - 'user' => ['email' => 'invalid-email'] - ] - ] - ]; - - $result = $validation->validate(Request::create($invalidInput)); - $this->assertFalse($result); - $this->assertNotEmpty($validation->getErrors()); - $this->assertStrictEquals(6, count($validation->getErrors())); - } - -} +testOk(); + $this->testError(); + $this->testPersonValidation(); + $this->testEmailValidation(); + $this->testTagsValidation(); + $this->testArticlesValidation(); + } + + public function testOk() + { + $validation = new Validation([ + 'email' => [new NotNull(), new Email()], + 'password' => new NotNull(), + 'firstname' => [new NotNull(), (new StringLength())->min(3), new Alphabetic()], + 'lastname' => [(new StringLength())->min(3)], + 'gender' => new Choice(['Mme', 'Mr', null]), + 'website' => [new NotNull(), new Url()], + 'age' => [new NotNull(), (new Integer())->min(18)], + 'invoice_total' => [new NotNull(), new Numeric()], + 'active' => [new NotNull(), new Custom(function ($value) { + return is_bool($value); + })] + ]); + + $this->assertTrue($validation->validate(Request::create([ + 'email' => 'dev@phpdevcommunity.com', + 'password' => 'Mypassword', + 'firstname' => 'phpdevcommunity', + 'lastname' => null, + 'gender' => 'Mr', + 'website' => 'https://www.phpdevcommunity.com', + 'age' => 20, + 'invoice_total' => '2000.25', + 'active' => true, + ]))); + + $this->assertFalse($validation->validateArray([ + 'email' => 'dev@phpdevcommunity.com', + 'password' => 'Mypassword', + 'firstname' => 'phpdevcommunity', + 'lastname' => '', + 'gender' => 'Mr', + 'website' => 'https://www.phpdevcommunity.com', + 'age' => 20, + 'invoice_total' => '2000.25', + 'active' => true, + ])); + + $validation->convertEmptyToNull(); + $this->assertTrue($validation->validateArray([ + 'email' => 'dev@phpdevcommunity.com', + 'password' => 'Mypassword', + 'firstname' => 'phpdevcommunity', + 'lastname' => '', + 'gender' => 'Mr', + 'website' => 'https://www.phpdevcommunity.com', + 'age' => 20, + 'invoice_total' => '2000.25', + 'active' => true, + ])); + } + + public function testError() + { + $validation = new Validation([ + 'email' => [new NotNull(), new Email()], + 'password' => new NotNull(), + 'firstname' => [new NotNull(), (new StringLength())->min(3), new Alphabetic()], + 'lastname' => [(new StringLength())->min(3)], + 'website' => [new NotNull(), new Url()], + 'invoice_total' => [new NotNull(), new Numeric()], + 'active' => [new NotNull(), new Custom(function ($value) { + return is_bool($value); + })] + ]); + + $this->assertFalse($validation->validate(Request::create([ + 'email' => 'dev@phpdevcommunity', + 'password' => null, + 'firstname' => '12', + 'lastname' => '12', + 'website' => 'www.phpdevcommunity', + 'invoice_total' => 'test2000.25', + 'active' => 'yes', + ]))); + + + $this->assertStrictEquals(7, count($validation->getErrors())); + $errors = $validation->getErrors(); + /** @var string[] $errorsFirstName */ + $errorsFirstName = $errors['firstname']; + $this->assertStrictEquals(2, count($errorsFirstName)); + + $this->assertStrictEquals($errors['email'][0], 'dev@phpdevcommunity is not a valid email address.'); + $this->assertStrictEquals($errors['active'][0], '"yes" is not valid'); + } + public function testPersonValidation(): void + { + $validation = new Validation([ + 'person' => [new NotEmpty(), new Item([ + 'first_name' => [new Alphabetic(), (new StringLength())->min(3)], + 'last_name' => [new NotNull(), new Alphabetic(), (new StringLength())->min(3)], + ])] + ]); + + $input = [ + 'person' => [ + 'first_name' => 'John', + 'last_name' => 'Doe' + ] + ]; + + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = [ + 'person' => [ + 'first_name' => '', + 'last_name' => 'Doe' + ] + ]; + + $result = $validation->validateArray($invalidInput); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + + + $validInput = [ + 'person' => [ + 'first_name' => '', + 'last_name' => 'Doe' + ] + ]; + + $validation->convertEmptyToNull(); + $result = $validation->validateArray($validInput); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + } + + public function testEmailValidation(): void + { + $validation = new Validation([ + 'email' => [new NotNull(), new Email()] + ]); + + $input = ['email' => 'test@example.com']; + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = ['email' => 'invalid-email']; + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + } + + public function testTagsValidation(): void + { + $validation = new Validation([ + 'tags' => [new NotEmpty(), new Collection([ + (new StringLength())->min(3) + ])] + ]); + + $input = ['tags' => ['tag1', 'tag2', 'tag3']]; + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $invalidInput = ['tags' => ['a', 'bc']]; + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + $this->assertStrictEquals(2, count($validation->getErrors())); + + } + + public function testArticlesValidation(): void + { + $validation = new Validation([ + 'articles' => [new NotEmpty(), new Collection([ + new Item([ + 'title' => [new NotEmpty(), (new StringLength())->min(3)], + 'body' => [new NotNull(), (new StringLength())->min(3)], + 'user' => new Item([ + 'email' => [new Email()] + ]) + ]) + ])] + ]); + + $input = [ + 'articles' => [ + [ + 'title' => 'Article 1', + 'body' => 'This is the body of the article.', + 'user' => ['email' => 'user1@example.com'] + ], + [ + 'title' => 'Article 2', + 'body' => 'Another body.', + 'user' => ['email' => 'user2@example.com'] + ] + ] + ]; + + $result = $validation->validate(Request::create($input)); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $result = $validation->validateArray($input); + $this->assertTrue($result); + $this->assertEmpty($validation->getErrors()); + + $input['articles'][1]['user']['email'] = ''; + $this->assertFalse($validation->validateArray($input)); + $validation->convertEmptyToNull(); + $this->assertTrue($validation->validateArray($input)); + + $invalidInput = [ + 'articles' => [ + [ + 'title' => '', + 'body' => '', + 'user' => ['email' => 'invalid-email'] + ], + [ + 'title' => '', + 'body' => '', + 'user' => ['email' => 'invalid-email'] + ] + ] + ]; + + $result = $validation->validate(Request::create($invalidInput)); + $this->assertFalse($result); + $this->assertNotEmpty($validation->getErrors()); + $this->assertStrictEquals(6, count($validation->getErrors())); + + } + +}