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 f3a9cbd..e4ab562 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([ @@ -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: @@ -82,9 +129,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([ @@ -103,14 +150,89 @@ 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\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([ + '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\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([ + '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. ```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([ @@ -135,8 +257,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([ @@ -161,8 +283,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) { @@ -198,9 +320,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([ @@ -240,8 +362,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([ @@ -276,7 +398,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. --- 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 72b9303..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 * @@ -37,6 +36,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 100644 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..3d9763c --- /dev/null +++ b/src/Assert/Collection.php @@ -0,0 +1,82 @@ + $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 new file mode 100755 index 0000000..541877c --- /dev/null +++ b/src/Assert/Item.php @@ -0,0 +1,68 @@ + $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/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..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 303fabf..5c909f9 100755 --- a/src/Validation.php +++ b/src/Validation.php @@ -1,136 +1,140 @@ - - */ - private array $validators; - - /** - * @var array - */ - private array $errors = []; - - private array $data = []; - - public function __construct(array $fieldValidators) - { - foreach ($fieldValidators as $field => $validators) { - if (!is_array($validators)) { - $validators = [$validators]; - } - $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; - - /** - * @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()); - } - } - - } - return $this->getErrors() === []; - } - - /** - * @return array - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @return array - */ - 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. - * - * @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 new file mode 100755 index 0000000..57818ce --- /dev/null +++ b/src/ValidationProcessor.php @@ -0,0 +1,59 @@ +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 new file mode 100644 index 0000000..c3b7273 --- /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()); + } + +} diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index e076597..1c5d440 100755 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -1,99 +1,279 @@ -testOk(); - $this->testError(); - } - - 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'); - } - -} +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())); + + } + +}