Skip to content

Commit

Permalink
Use PHP attributes to define templates
Browse files Browse the repository at this point in the history
Creating a specific exception for each rule adds a painful overhead. If
you want to make a custom message for your rule, you will need to create
an exception and then register that exception namespace to be able to
use it—all that is just for customizing the message of your rule.

Having different namespaces also implies that you need to fetch the
exception of the rule from another directory to change it. As Uncle Bob
said, "Classes that change together belong together. Classes that are
not reused together should not be grouped."

This commit will drastically change this library, moving all the
templates from the exceptions to the rules. Consequently, the Factory
becomes much simpler, and the library gets a bit smaller, too.

Signed-off-by: Henrique Moody <[email protected]>
  • Loading branch information
henriquemoody committed Jan 29, 2024
1 parent 2b59e3d commit e4f2c81
Show file tree
Hide file tree
Showing 333 changed files with 1,340 additions and 4,515 deletions.
44 changes: 7 additions & 37 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Before writing anything, feature or bug fix:
A common validator (rule) on Respect\Validation is composed of three classes:

* `library/Rules/YourRuleName.php`: the rule itself
* `library/Exceptions/YourRuleNameException.php`: the exception thrown by the rule
* `tests/unit/Rules/YourRuleNameTest.php`: tests for the rule

The classes are pretty straightforward. In the sample below, we're going to
Expand Down Expand Up @@ -62,6 +61,12 @@ declare(strict_types=1);

namespace Respect\Validation\Rules;

use Respect\Validation\Attributes\Template;

#[Template(
'{{name}} must be a Hello World',
'{{name}} must not be a Hello World',
)]
final class HelloWorld extends AbstractRule
{
public function validate(mixed $input): bool
Expand All @@ -71,41 +76,6 @@ final class HelloWorld extends AbstractRule
}
```

### Creating the rule exception

Just that and we're done with the rule code. The Exception requires you to
declare messages used by `assert()` and `check()`. Messages are declared in
affirmative and negative moods, so if anyone calls `v::not(v::helloWorld())` the
library will show the appropriate message.

```php
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Exceptions;

final class HelloWorldException extends ValidationException
{
/**
* @var array<string, array<string, string>>
*/
protected array $defaultTemplates = [
self::MODE_DEFAULT => [
self::STANDARD => '{{name}} must be a Hello World',
],
self::MODE_NEGATIVE => [
self::STANDARD => '{{name}} must not be a Hello World',
]
];
}
```

### Creating unit tests

Finally, we need to test if everything is running smooth. We have `RuleTestCase`
Expand Down Expand Up @@ -173,7 +143,7 @@ for it other than what is covered by `RuleTestCase`.

### Helping us a little bit more

You rule will be accepted only with these 3 files (rule, exception and unit test),
You rule will be accepted only with these 3 files (rule and unit test),
but if you really want to help us, you can follow the example of [ArrayType][] by:

- Adding your new rule on the `Validator`'s class docblock;
Expand Down
49 changes: 8 additions & 41 deletions docs/custom-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,55 +11,23 @@ validate method will be executed. Here's how the class should look:
namespace My\Validation\Rules;

use Respect\Validation\Rules\AbstractRule;
use Respect\Validation\Attributes\Template;

#[Template(
'{{name}} is something',
'{{name}} is not something',
)]
final class Something extends AbstractRule
{
public function validate($input): bool
public function validate(mixed $input): bool
{
// Do something here with the $input and return a boolean value
}
}
```

Each rule must have an Exception to go with it. Exceptions should be named
with the name of the rule followed by the word Exception. The process of creating
an Exception is similar to creating a rule but there are no methods in the
Exception class. Instead, you create one static property that includes an
array with the information below:

```php
namespace My\Validation\Exceptions;

use Respect\Validation\Exceptions\ValidationException;

final class SomethingException extends ValidationException
{
/**
* @var array<string, array<string, string>>
*/
protected array $defaultTemplates = [
self::MODE_DEFAULT => [
self::STANDARD => 'Validation message if Something fails validation.',
],
self::MODE_NEGATIVE => [
self::STANDARD => 'Validation message if the negative of Something is called and fails validation.',
],
];
}
```

So in the end, the folder structure for your Rules and Exceptions should look
something like the structure below. Note that the folders (and namespaces) are
plural but the actual Rules and Exceptions are singular.

```
My
+-- Validation
+-- Exceptions
+-- SomethingException.php
+-- Rules
+-- Something.php
```
The `'{{name}} is not something` message would be used then you call the rule
with the `not()`.

All classes in Validation are created by the `Factory` class. If you want
Validation to execute your rule (or rules) in the chain, you must overwrite the
Expand All @@ -69,7 +37,6 @@ default `Factory`.
Factory::setDefaultInstance(
(new Factory())
->withRuleNamespace('My\\Validation\\Rules')
->withExceptionNamespace('My\\Validation\\Exceptions')
);
v::something(); // Try to load "My\Validation\Rules\Something" if any
```
25 changes: 25 additions & 0 deletions library/Attributes/ExceptionClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Attributes;

use Attribute;
use Respect\Validation\Exceptions\ValidationException;

#[Attribute(Attribute::TARGET_CLASS)]
final class ExceptionClass
{
/**
* @param class-string<ValidationException> $class
*/
public function __construct(
public readonly string $class
) {
}
}
24 changes: 24 additions & 0 deletions library/Attributes/Template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Attributes;

use Attribute;
use Respect\Validation\Validatable;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Template
{
public function __construct(
public readonly string $default,
public readonly string $negative,
public readonly string $id = Validatable::TEMPLATE_STANDARD,
) {
}
}
27 changes: 0 additions & 27 deletions library/Exceptions/AllOfException.php

This file was deleted.

30 changes: 0 additions & 30 deletions library/Exceptions/AlnumException.php

This file was deleted.

30 changes: 0 additions & 30 deletions library/Exceptions/AlphaException.php

This file was deleted.

31 changes: 0 additions & 31 deletions library/Exceptions/AlwaysInvalidException.php

This file was deleted.

27 changes: 0 additions & 27 deletions library/Exceptions/AlwaysValidException.php

This file was deleted.

27 changes: 0 additions & 27 deletions library/Exceptions/AnyOfException.php

This file was deleted.

Loading

0 comments on commit e4f2c81

Please sign in to comment.