-
-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document breaking changes to AbstractValidator
Signed-off-by: George Steel <george@net-glue.co.uk>
- Loading branch information
Showing
2 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
151 changes: 151 additions & 0 deletions
151
docs/book/v3/migration/refactoring-legacy-validators.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# Refactoring Legacy Validators | ||
|
||
This document is intended to show an example of refactoring custom validators to remove runtime mutation of options and move option determination to the constructor of your validator. | ||
|
||
## The Old Validator | ||
|
||
The following custom validator is our starting point which relies on now removed methods and behaviour from the `AbstractValidator` in the 2.x series of releases. | ||
|
||
```php | ||
namespace My; | ||
|
||
use Laminas\Validator\AbstractValidator; | ||
|
||
class MuppetNameValidator extends AbstractValidator { | ||
public const KNOWN_MUPPETS = [ | ||
'Kermit', | ||
'Miss Piggy', | ||
'Fozzie Bear', | ||
'Gonzo the Great', | ||
'Scooter', | ||
'Animal', | ||
'Beaker', | ||
]; | ||
|
||
public const ERR_NOT_STRING = 'notString'; | ||
public const ERR_NOT_ALLOWED = 'notAllowed'; | ||
|
||
protected array $messageTemplates = [ | ||
self::ERR_NOT_STRING => 'Please provide a string value', | ||
self::ERR_NOT_ALLOWED => '"%value%" is not an allowed muppet name', | ||
]; | ||
|
||
public function setAllowedMuppets(array $muppets): void { | ||
$this->options['allowed_muppets'] = []; | ||
foreach ($muppets as $muppet) { | ||
$this->addMuppet($muppet); | ||
} | ||
} | ||
|
||
public function addMuppet(string $muppet): void | ||
{ | ||
$this->options['allowed_muppets'][] = $muppet; | ||
} | ||
|
||
public function setCaseSensitive(bool $caseSensitive): void | ||
{ | ||
$this->options['case_sensitive'] = $caseSensitive; | ||
} | ||
|
||
public function isValid(mixed $value): bool { | ||
if (! is_string($value)) { | ||
$this->error(self::ERR_NOT_STRING); | ||
|
||
return false; | ||
} | ||
|
||
$list = $this->options['allowed_muppets']; | ||
if (! $this->options['case_sensitive']) { | ||
$list = array_map('strtolower', $list); | ||
$value = strtolower($value); | ||
} | ||
|
||
if (! in_array($value, $list, true)) { | ||
$this->error(self::ERR_NOT_ALLOWED); | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
``` | ||
|
||
Given an array of options such as `['allowed_muppets' => ['Miss Piggy'], 'caseSensitive' => false]`, previously, the `AbstractValidator` would have "magically" called the setter methods `setAllowedMuppets` and `setCaseSensitive`. The same would be true if you provided these options to the removed `AbstractValidator::setOptions()` method. | ||
|
||
Additionally, with the class above, there is nothing to stop you from creating the validator in an invalid state with: | ||
|
||
```php | ||
$validator = new MuppetNameValidator(); | ||
$validator->isValid('Kermit'); | ||
// false, because the list of allowed muppets has not been initialised | ||
``` | ||
|
||
## The Refactored Validator | ||
|
||
```php | ||
final readonly class MuppetNameValidator extends AbstractValidator { | ||
public const KNOWN_MUPPETS = [ | ||
'Kermit', | ||
'Miss Piggy', | ||
'Fozzie Bear', | ||
'Gonzo the Great', | ||
'Scooter', | ||
'Animal', | ||
'Beaker', | ||
]; | ||
|
||
public const ERR_NOT_STRING = 'notString'; | ||
public const ERR_NOT_ALLOWED = 'notAllowed'; | ||
|
||
protected array $messageTemplates = [ | ||
self::ERR_NOT_STRING => 'Please provide a string value', | ||
self::ERR_NOT_ALLOWED => '"%value%" is not an allowed muppet name', | ||
]; | ||
|
||
private array $allowed; | ||
private bool $caseSensitive; | ||
|
||
/** | ||
* @param array{ | ||
* allowed_muppets: list<non-empty-string>, | ||
* case_sensitive: bool, | ||
* } $options | ||
*/ | ||
public function __construct(array $options) | ||
{ | ||
$this->allowed = $options['allowed_muppets'] ?? self::KNOWN_MUPPETS; | ||
$this->caseSensitive = $options['case_sensitive'] ?? true; | ||
|
||
// Pass options such as the translator, overridden error messages, etc | ||
// to the parent AbstractValidator | ||
parent::__construct($options); | ||
} | ||
|
||
public function isValid(mixed $value): bool { | ||
if (! is_string($value)) { | ||
$this->error(self::ERR_NOT_STRING); | ||
|
||
return false; | ||
} | ||
|
||
$list = $this->allowed; | ||
if (! $this->caseSensitive) { | ||
$list = array_map('strtolower', $list); | ||
$value = strtolower($value); | ||
} | ||
|
||
if (! in_array($value, $list, true)) { | ||
$this->error(self::ERR_NOT_ALLOWED); | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
``` | ||
|
||
With the refactored validator, our options are clearly and obviously declared as class properties, and cannot be changed once they have been set. | ||
|
||
There are fewer methods to test; In your test case you can easily set up data providers with varying options to thoroughly test that your validator behaves in the expected way. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters