diff --git a/README.md b/README.md index 8da23b9..f19393f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Yet Another PHP Validator -Versatile library focused on validating development code with expressive error messages. +PHP validator focused on validating development code with expressive error messages. + +> **Note** +> This library is not in version 1.x mainly because there are few available rules. +> Hopefully, that will change in the near future. ## Requirements @@ -44,15 +48,23 @@ $validator->assert(16, 'Age'); // throws exception: The "Age" value should be gr ## Documentation +- [Get Started](docs/01-get-started.md) +- [Usage](docs/02-usage.md) + - [Usage](docs/02-usage.md#usage) + - [Methods](docs/02-usage.md#methods) + - [Error Handling](docs/02-usage.md#error-handling) + - [Custom Error Messages](docs/02-usage.md#custom-error-messages) +- [Rules](docs/03-rules.md) +- [Custom Rules](docs/04-custom-rules.md) ## Contributing -Any form of contribution to improve this library will be welcome and appreciated. +Any form of contribution to improve this library (including requests) will be welcome and appreciated. Make sure to open a pull request or issue. ## Acknowledgments -This library is inspired by [Respect's Validation](https://github.com/Respect/Validation) and [Symfony's Validator](https://symfony.com/doc/current/validation.html). +This library is inspired by [respect/validation](https://github.com/Respect/Validation) and [symfony/validator](https://github.com/symfony/validator). ## License diff --git a/composer.json b/composer.json index 847b778..761a36a 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "programmatordev/yet-another-php-validator", - "description": "PHP Validator", + "description": "PHP validator focused on validating development code with expressive error messages", "type": "library", - "keywords": ["PHP", "Validator", "Validation"], + "keywords": ["PHP", "PHP8", "Validator", "Validation"], "license": "MIT", "authors": [ { diff --git a/docs/01-get-started.md b/docs/01-get-started.md index f319fd0..7ce068a 100644 --- a/docs/01-get-started.md +++ b/docs/01-get-started.md @@ -33,18 +33,13 @@ use ProgrammatorDev\YetAnotherPhpValidator\Validator; // Do this... $validator = Validator::notBlank()->greaterThanOrEqual(18); -// ...or this... +// Or this... $validator = new Validator( new Rule\NotBlank(), new Rule\GreaterThanOrEqual(18) ); -// ...or even this... -$validator = (new Validator()) - ->addRule(new Rule\NotBlank()) - ->addRule(new Rule\GreaterThanOrEqual(18)); - -// ...and validate with these: +// Validate with these: $validator->validate(16); // returns bool: false $validator->assert(16, 'Age'); // throws exception: The "Age" value should be greater than or equal to "18", "16" given. ``` \ No newline at end of file diff --git a/docs/02-usage.md b/docs/02-usage.md index 6cc92bc..dedfb86 100644 --- a/docs/02-usage.md +++ b/docs/02-usage.md @@ -1,19 +1,19 @@ # Using Yet Another PHP Validator -- Usage - - Fluent - - Dependency Injection -- Methods - - assert - - validate - - getRules - - addRule -- Exception Handling -- Custom Exception Messages +- [Usage](#usage) + - [Fluent](#fluent) + - [Dependency Injection](#dependency-injection) +- [Methods](#methods) + - [assert](#assert) + - [validate](#validate) + - [getRules](#getrules) + - [addRule](#addrule) +- [Error Handling](#error-handling) +- [Custom Error Messages](#custom-error-messages) ## Usage -This library allows you to use validate data in two different ways: +This library allows you to validate data in two different ways: - In a fluent way, making use of magic methods. The goal is to be able to create a set of rules with minimum setup; - In a traditional way, making use of dependency injection. You may not like the fluent approach, and prefer to work this way. @@ -71,7 +71,7 @@ This method throws a `ValidationException` when a rule fails, otherwise nothing assert(mixed $value, string $name): void; ``` -An example on how to handle an exception: +An example on how to handle an error: ```php use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException; @@ -93,9 +93,11 @@ catch (ValidationException $exception) { echo $exception->getMessage(); // The "Latitude" value should be between "-90" and "90", "100" given. } ``` +> **Note** +> Check the [Error Handling](#error-handling) section for more information. > **Note** -> The example only shows one usage approach, but both Fluent and Dependency Injection usage approaches should work the same. +> The example only shows one usage approach, but both Fluent and Dependency Injection should work the same. > Check the [Usage](#usage) section for more information. ### `validate` @@ -117,7 +119,7 @@ if (!Validator::range(-90, 90)->validate($latitude)) { ``` > **Note** -> The example only shows one usage approach, but both Fluent and Dependency Injection usage approaches should work the same. +> The example only shows one usage approach, but both Fluent and Dependency Injection should work the same. > Check the [Usage](#usage) section for more information. ### `getRules` @@ -148,7 +150,7 @@ print_r($validator->getRules()); ``` > **Note** -> The example only shows one usage approach, but both Fluent and Dependency Injection usage approaches should work the same. +> The example only shows one usage approach, but both Fluent and Dependency Injection should work the same. > Check the [Usage](#usage) section for more information. ### `addRule` @@ -180,16 +182,75 @@ function calculateDiscount(float $price, float $discount, string $type): float ``` > **Note** -> The example only shows one usage approach, but both Fluent and Dependency Injection usage approaches should work the same. +> The example only shows one usage approach, but both Fluent and Dependency Injection should work the same. > Check the [Usage](#usage) section for more information. -## Exception Handling +## Error Handling + +When using the [`assert`](#assert) method, an exception is thrown when a rule fails. + +Each rule has a unique exception, formed by the name of the rule followed by the word Exception, like `RuleNameException`. +The following shows an example: + +```php +use ProgrammatorDev\YetAnotherPhpValidator\Exception; +use ProgrammatorDev\YetAnotherPhpValidator\Validator; + +try { + Validator::range(-90, 90)->assert($latitude, 'Latitude'); + Validator::range(-180, 180)->assert($longitude, 'Longitude'); + Validator::notBlank()->choice(['METRIC', 'IMPERIAL'])->assert($unitSystem, 'Unit System'); +} +catch (Exception\RangeException $exception) { + // Do something when Range fails +} +catch (Exception\NotBlankException $exception) { + // Do something when NotBlank fails +} +catch (Exception\ChoiceException $exception) { + // Do something when Choice fails +} +``` + +To catch all errors with a single exception, you can use the `ValidationException`: + +```php +use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException; +use ProgrammatorDev\YetAnotherPhpValidator\Validator; + +try { + Validator::range(-90, 90)->assert($latitude, 'Latitude'); + Validator::range(-180, 180)->assert($longitude, 'Longitude'); + Validator::notBlank()->choice(['METRIC', 'IMPERIAL'])->assert($unitSystem, 'Unit System'); +} +catch (ValidationException $exception) { + // Do something when a rule fails + echo $exception->getMessage(); +} +``` + +When using both the [`assert`](#assert) or [`validate`](#validate) methods, +an `UnexpectedValueException` is thrown when the provided input data is not valid to perform the validation. + +For example, when trying to compare a date with a string: + +```php +use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException; +use ProgrammatorDev\YetAnotherPhpValidator\Validator; + +try { + Validator::greaterThanOrEqual(new DateTime('today'))->validate('alpha'); +} +catch (UnexpectedValueException $exception) { + echo $exception->getMessage(); // Cannot compare a type "string" with a type "DateTime". +} +``` -## Custom Exception Messages +## Custom Error Messages All rules have at least one error message that can be customized (some rules have more than one error message for different case scenarios). -Every message has a list of dynamic parameters to help create an intuitive error text (like the invalid value, constraints, names, and others). +Every message has a list of dynamic parameters to help create an intuitive error (like the invalid value, constraints, names, and others). To check what parameters and messages are available, look into the Options section in the page of a rule. Go to [Rules](03-rules.md) to see all available rules. @@ -203,5 +264,5 @@ Validator::choice( message: '"{{ value }}" is not a valid {{ name }}! You must select one of {{ constraints }}.' )->assert('yellow', 'color'); -// "yellow" is not a valid color! You must select one of [red, green, blue]. +// Throws: "yellow" is not a valid color! You must select one of [red, green, blue]. ``` \ No newline at end of file diff --git a/docs/03-rules.md b/docs/03-rules.md index 9327d44..2cf68da 100644 --- a/docs/03-rules.md +++ b/docs/03-rules.md @@ -3,7 +3,7 @@ - [Basic Rules](#basic-rules) - [Comparison Rules](#comparison-rules) - [Choice Rules](#choice-rules) -- [Array Rules](#array-rules) +- [Other Rules](#other-rules) ## Basic Rules @@ -21,6 +21,6 @@ - [Choice](03x-rules-choice.md) -## Array Rules +## Other Rules - [All](03x-rules-all.md) \ No newline at end of file diff --git a/docs/03x-rules-all.md b/docs/03x-rules-all.md index 268d287..68e6152 100644 --- a/docs/03x-rules-all.md +++ b/docs/03x-rules-all.md @@ -54,6 +54,6 @@ The following parameters are available: | Parameter | Description | |-----------------|-----------------------------------------------| | `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | +| `{{ name }}` | Name of the invalid value | | `{{ key }}` | The array key of the invalid array element | | `{{ message }}` | The rule message of the invalid array element | \ No newline at end of file diff --git a/docs/03x-rules-choice.md b/docs/03x-rules-choice.md index 2be51f8..6b47f8e 100644 --- a/docs/03x-rules-choice.md +++ b/docs/03x-rules-choice.md @@ -51,7 +51,7 @@ Validator::choice(['red', 'green', 'blue'], multiple: true, minConstraint: 2, ma type: `array` `required` -Collection of choices to be validated against the input value. +Collection of constraint choices to be validated against the input value. ### `multiple` @@ -84,11 +84,11 @@ Message that will be shown if input value is not a valid choice. The following parameters are available: -| Parameter | Description | -|---------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraints }}` | The array of valid choices | +| Parameter | Description | +|---------------------|----------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraints }}` | The array of valid choices | ### `multipleMessage` @@ -98,11 +98,11 @@ Message that will be shown when `multiple` is `true` and at least one of the inp The following parameters are available: -| Parameter | Description | -|---------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraints }}` | The array of valid choices | +| Parameter | Description | +|---------------------|----------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraints }}` | The array of valid choices | ### `minMessage` @@ -116,7 +116,7 @@ The following parameters are available: |-----------------------|--------------------------------------| | `{{ value }}` | The current invalid value | | `{{ numValues }}` | The current invalid number of values | -| `{{ name }}` | Name of the value being validated | +| `{{ name }}` | Name of the invalid value | | `{{ constraints }}` | The array of valid choices | | `{{ minConstraint }}` | The minimum number of valid choices | | `{{ maxConstraint }}` | The maximum number of valid choices | @@ -133,7 +133,7 @@ The following parameters are available: |-----------------------|--------------------------------------| | `{{ value }}` | The current invalid value | | `{{ numValues }}` | The current invalid number of values | -| `{{ name }}` | Name of the value being validated | +| `{{ name }}` | Name of the invalid value | | `{{ constraints }}` | The array of valid choices | | `{{ minConstraint }}` | The minimum number of valid choices | | `{{ maxConstraint }}` | The maximum number of valid choices | \ No newline at end of file diff --git a/docs/03x-rules-greater-than-or-equal.md b/docs/03x-rules-greater-than-or-equal.md index a8cb9d7..cea63fb 100644 --- a/docs/03x-rules-greater-than-or-equal.md +++ b/docs/03x-rules-greater-than-or-equal.md @@ -27,7 +27,7 @@ Validator::greaterThanOrEqual(new DateTime('today'))->validate(new DateTime('tod ``` > **Note** -> String comparison is case-sensitive, meaning that comparing `'hello'` with `'Hello'` is different. +> String comparison is case-sensitive, meaning that comparing `"hello"` with `"Hello"` is different. > Check [`strcmp`](https://www.php.net/manual/en/function.strcmp.php) for more information. > **Note** @@ -50,8 +50,8 @@ Message that will be shown if the value is not greater than or equal to the cons The following parameters are available: -| Parameter | Description | -|--------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraint }}` | The comparison value | \ No newline at end of file +| Parameter | Description | +|--------------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraint }}` | The comparison value | \ No newline at end of file diff --git a/docs/03x-rules-greater-than.md b/docs/03x-rules-greater-than.md index 2f4f019..097379d 100644 --- a/docs/03x-rules-greater-than.md +++ b/docs/03x-rules-greater-than.md @@ -27,7 +27,7 @@ Validator::greaterThan(new DateTime('today'))->validate(new DateTime('today')); ``` > **Note** -> String comparison is case-sensitive, meaning that comparing `'hello'` with `'Hello'` is different. +> String comparison is case-sensitive, meaning that comparing `"hello"` with `"Hello"` is different. > Check [`strcmp`](https://www.php.net/manual/en/function.strcmp.php) for more information. > **Note** @@ -50,8 +50,8 @@ Message that will be shown if the value is not greater than the constraint value The following parameters are available: -| Parameter | Description | -|--------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraint }}` | The comparison value | \ No newline at end of file +| Parameter | Description | +|--------------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraint }}` | The comparison value | \ No newline at end of file diff --git a/docs/03x-rules-less-than-or-equal.md b/docs/03x-rules-less-than-or-equal.md index 34a3e6c..1acfa8c 100644 --- a/docs/03x-rules-less-than-or-equal.md +++ b/docs/03x-rules-less-than-or-equal.md @@ -27,7 +27,7 @@ Validator::lessThanOrEqual(new DateTime('today'))->validate(new DateTime('today' ``` > **Note** -> String comparison is case-sensitive, meaning that comparing `'hello'` with `'Hello'` is different. +> String comparison is case-sensitive, meaning that comparing `"hello"` with `"Hello"` is different. > Check [`strcmp`](https://www.php.net/manual/en/function.strcmp.php) for more information. > **Note** @@ -50,8 +50,8 @@ Message that will be shown if the value is not less than or equal to the constra The following parameters are available: -| Parameter | Description | -|--------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraint }}` | The comparison value | \ No newline at end of file +| Parameter | Description | +|--------------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraint }}` | The comparison value | \ No newline at end of file diff --git a/docs/03x-rules-less-than.md b/docs/03x-rules-less-than.md index a01b52f..8100282 100644 --- a/docs/03x-rules-less-than.md +++ b/docs/03x-rules-less-than.md @@ -27,7 +27,7 @@ Validator::lessThan(new DateTime('today'))->validate(new DateTime('today')); // ``` > **Note** -> String comparison is case-sensitive, meaning that comparing `'hello'` with `'Hello'` is different. +> String comparison is case-sensitive, meaning that comparing `"hello"` with `"Hello"` is different. > Check [`strcmp`](https://www.php.net/manual/en/function.strcmp.php) for more information. > **Note** @@ -50,8 +50,8 @@ Message that will be shown if the value is not less than the constraint value. The following parameters are available: -| Parameter | Description | -|--------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ constraint }}` | The comparison value | \ No newline at end of file +| Parameter | Description | +|--------------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ constraint }}` | The comparison value | \ No newline at end of file diff --git a/docs/03x-rules-not-blank.md b/docs/03x-rules-not-blank.md index c77f476..e2cde02 100644 --- a/docs/03x-rules-not-blank.md +++ b/docs/03x-rules-not-blank.md @@ -11,8 +11,8 @@ NotBlank( ## Basic Usage -Bellow are the only cases where the rule will fail, everything else is considered valid -(you may want to check the [`normalizer`](#normalizer) option for a different behaviour): +Bellow are the only cases where the rule will fail, +everything else is considered valid (you may want to check the [`normalizer`](#normalizer) option for a different behaviour): ```php Validator::notBlank()->validate(''); // false @@ -29,7 +29,7 @@ type: `callable` default: `null` Allows to define a `callable` that will be applied to the value before checking if it is valid. -For example, use `trim`, or pass your own function, to now allow a string with whitespaces only: +For example, use `trim`, or pass your own function, to not allow a string with whitespaces only: ```php // Existing PHP callables @@ -47,7 +47,7 @@ Message that will be shown if the value is blank. The following parameters are available: -| Parameter | Description | -|---------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | \ No newline at end of file +| Parameter | Description | +|---------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | \ No newline at end of file diff --git a/docs/03x-rules-range.md b/docs/03x-rules-range.md index dccc32d..6212f9f 100644 --- a/docs/03x-rules-range.md +++ b/docs/03x-rules-range.md @@ -13,7 +13,7 @@ Range( ## Basic Usage -Values equal to the defined minimum and maximum constraints are considered valid. +Values equal to the defined minimum and maximum values are considered valid. ```php Validator::range(1, 20)->validate(10); // true @@ -30,7 +30,7 @@ Validator::range(new DateTime('yesterday'), new DateTime('tomorrow'))->validate( ``` > **Note** -> String comparison is case-sensitive, meaning that comparing `'hello'` with `'Hello'` is different. +> String comparison is case-sensitive, meaning that comparing `"hello"` with `"Hello"` is different. > Check [`strcmp`](https://www.php.net/manual/en/function.strcmp.php) for more information. > **Note** @@ -60,13 +60,12 @@ Can be a `string`, `int`, `float` or `DateTimeInterface` object. type: `string` default: `The "{{ name }}" value should be between "{{ minConstraint }}" and "{{ maxConstraint }}", "{{ value }}" given.` Message that will be shown if the value is not between the minimum and maximum constraint values. -Check the [Custom Messages]() section for more information. The following parameters are available: -| Parameter | Description | -|-----------------------|-----------------------------------| -| `{{ value }}` | The current invalid value | -| `{{ name }}` | Name of the value being validated | -| `{{ minConstraint }}` | The minimum range value | -| `{{ maxConstraint }}` | The maximum range value | \ No newline at end of file +| Parameter | Description | +|-----------------------|---------------------------| +| `{{ value }}` | The current invalid value | +| `{{ name }}` | Name of the invalid value | +| `{{ minConstraint }}` | The minimum range value | +| `{{ maxConstraint }}` | The maximum range value | \ No newline at end of file diff --git a/docs/04-custom-rules.md b/docs/04-custom-rules.md new file mode 100644 index 0000000..e6644a5 --- /dev/null +++ b/docs/04-custom-rules.md @@ -0,0 +1,118 @@ +# Custom Rules + +- [Create a Rule](#create-a-rule) +- [Message Template](#message-template) + +## Create a Rule + +You can create your own rules to use with this library. + +For that, you need to create an exception that extends the `ValidationException` and a rule that extends the `AbstractRule` and implements the `RuleInterface`. + +First, create your custom rule exception... + +```php +namespace My\Project\Exception; + +use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException; + +class CustomRuleException extends ValidationException {} +``` + +...then create your custom rule class... + +```php +namespace My\Project\Rule; + +use ProgrammatorDev\YetAnotherPhpValidator\Rule\AbstractRule; +use ProgrammatorDev\YetAnotherPhpValidator\Rule\RuleInterface; + +class CustomRule extends AbstractRule implements RuleInterface +{ + public function assert(mixed $value, string $name): void + { + // Do validation + } +} +``` + +...and finally, you need to throw your custom exception when your validation fails, so it looks something like the following: + +```php +namespace My\Project\Rule; + +use ProgrammatorDev\YetAnotherPhpValidator\Rule\AbstractRule; +use ProgrammatorDev\YetAnotherPhpValidator\Rule\RuleInterface; +use My\Project\Exception\CustomRuleException; + +class CustomRule extends AbstractRule implements RuleInterface +{ + public function assert(mixed $value, string $name): void + { + if ($value === 0) { + throw new CustomRuleException( + message: 'The "{{ name }}" value cannot be zero!', + parameters: [ + 'name' => $name + ] + ); + } + } +} +``` + +In the example above, a new custom rule was created that validates if the input value is not zero. + +To use your new custom rule, simply do the following: + +```php +// Fluent way, notice the rule() method +$validator = Validator::rule(new CustomRule()); +// With multiple rules +$validator = Validator::range(-10, 10)->rule(new CustomRule()); + +// Dependency injection way +$validator = new Validator(new CustomRule()); +// With multiple rules +$validator = new Validator(new Range(-10, 10), new CustomRule()); + +$validator->assert(0, 'test'); // throws: The "test" value cannot be zero! +$validator->validate(0); // false +``` + +## Message Template + +When an exception extends the `ValidationException`, you're able to create error message templates. +This means that you can have dynamic content in your messages. + +To make it work, just pass an associative array with the name and value of your parameters, and they will be available in the message: + +```php +// Exception +class FavoriteException extends ValidationException {} + +// Rule +class Favorite extends AbstractRule implements RuleInterface +{ + public function __construct( + private readonly string $favorite + ) + + public function assert(mixed $value, string $name): void + { + if ($this->favorite !== $value) { + throw new FavoriteException( + message: 'My favorite {{ name }} is "{{ favorite }}", not "{{ value }}"!', + parameters: [ + 'name' => $name, // {{ name }} + 'favorite' => $this->favorite, // {{ favorite }} + 'value' => $value // {{ value }} + ] + ) + } + } +} + +// Throws: My favorite animal is "cat", not "human"! +Validator::rule(new Favorite('cat'))->assert('human', 'animal'); +``` \ No newline at end of file