Skip to content

Commit

Permalink
Merge pull request #11 from alleyinteractive/feature/freeform-validator
Browse files Browse the repository at this point in the history
Add `FreeformValidator` abstract class
  • Loading branch information
dlh01 authored Dec 15, 2022
2 parents 2a34167 + c715274 commit ef78011
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a C

### Added

- `FreeformValidator` abstract class.
- `ContainsString`, `DivisibleBy`, `FastFailValidatorChain`, `ValidatorByOperator`, and `WithMessage` validators.

### Changed

- `BaseValidator` was renamed `ExtendedAbstractValidator` to emphasize its connection to the Laminas `AbstractValidator` now that this library has multiple base validators.
- The failure message returned by `Not::getMessages()` now has the identifier `notValid`.

### Fixed
Expand Down
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ $ composer require alleyinteractive/laminas-validator-extensions

For more information about what validators do, how to use them, and how to write your own, [visit the Laminas documentation](https://docs.laminas.dev/laminas-validator/).

## Base validator
## Base validators

The abstract `Alley\Validator\BaseValidator` class standardizes the implementation of custom validators with `\Laminas\Validator\AbstractValidator`.
### `ExtendedAbstractValidator`

When extending `BaseValidator`, validation logic goes into a new `testValue()` method, which is responsible only for applying the logic and adding any validation errors. It's no longer necessary to call `setValue()` prior to evaluating the input, and `isValid()` will return `true` if there are no error messages after evaluating the input and `false` if there are any messages.
The abstract `Alley\Validator\ExtendedAbstractValidator` class standardizes the implementation of custom validators with `\Laminas\Validator\AbstractValidator`.

When extending `ExtendedAbstractValidator`, validation logic goes into a new `testValue()` method, which is responsible only for applying the logic and adding any validation errors. It's no longer necessary to call `setValue()` prior to evaluating the input, and `isValid()` will return `true` if there are no error messages after evaluating the input and `false` if there are any messages.

Before:

Expand Down Expand Up @@ -52,7 +54,7 @@ After:
```php
<?php

class Float extends \Alley\Validator\BaseValidator
class Float extends \Alley\Validator\ExtendedAbstractValidator
{
const FLOAT = 'float';

Expand All @@ -69,6 +71,28 @@ class Float extends \Alley\Validator\BaseValidator
}
```

### `FreeformValidator`

The standalone, abstract `Alley\Validator\FreeformValidator` class leaves most of the implementation details to your discretion, but it's often easier to use for validators that are project-specific or not ready for wider distribution.

Like the `ExtendedAbstractValidator` class, the `FreeformValidator` expects that validation logic goes into a `testValue()` method, and `isValid()` will return `true` or `false` based on whether there are error messages.

Validation errors can be added using the `error()` method, which accepts the message key and text.

```php
<?php

class Float extends \Alley\Validator\FreeformValidator
{
public function testValue($value): void
{
if (! is_float($value)) {
$this->error('float', 'Please enter a floating point value');
}
}
}
```

## "Any Validator" chains

`\Alley\Validator\AnyValidator` is like a [Laminas validator chain](https://docs.laminas.dev/laminas-validator/validator-chains/) except that it connects the validators with "OR," marking input as valid as soon it passes one of the given validators.
Expand Down
2 changes: 1 addition & 1 deletion src/Alley/Validator/Comparison.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Laminas\Validator\Exception\InvalidArgumentException;
use Laminas\Validator\ValidatorInterface;

final class Comparison extends BaseValidator
final class Comparison extends ExtendedAbstractValidator
{
private const SUPPORTED_OPERATORS = [
'==',
Expand Down
2 changes: 1 addition & 1 deletion src/Alley/Validator/ContainsString.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Laminas\Validator\IsInstanceOf;
use Laminas\Validator\ValidatorInterface;

final class ContainsString extends BaseValidator
final class ContainsString extends ExtendedAbstractValidator
{
public const NOT_CONTAINS_STRING = 'notContainsString';

Expand Down
2 changes: 1 addition & 1 deletion src/Alley/Validator/DivisibleBy.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Laminas\Validator\Exception\InvalidArgumentException;
use Laminas\Validator\ValidatorInterface;

final class DivisibleBy extends BaseValidator
final class DivisibleBy extends ExtendedAbstractValidator
{
public const NOT_DIVISIBLE_BY = 'notDivisibleBy';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

use Laminas\Validator\AbstractValidator;

abstract class BaseValidator extends AbstractValidator
abstract class ExtendedAbstractValidator extends AbstractValidator
{
final public function isValid($value): bool
{
Expand Down
54 changes: 54 additions & 0 deletions src/Alley/Validator/FreeformValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the laminas-validator-extensions package.
*
* (c) Alley <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Alley\Validator;

use Laminas\Validator\ValidatorInterface;
use Stringable;

abstract class FreeformValidator implements ValidatorInterface
{
private array $messages = [];

/**
* Apply validation logic and add any validation errors.
*
* @param mixed $value
* @return void
*/
abstract protected function testValue($value): void;

final public function isValid($value): bool
{
$this->messages = [];
$this->testValue($value);
return \count($this->getMessages()) === 0;
}

final public function getMessages()
{
return $this->messages;
}

/**
* Add validation error.
*
* @param string $key Error key.
* @param string $message Message text.
* @return void
*/
final protected function error(string $key, $message): void
{
$this->messages[$key] = (string) $message;
}
}
2 changes: 1 addition & 1 deletion src/Alley/Validator/OneOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use Laminas\Validator\Explode;
use Laminas\Validator\ValidatorInterface;

final class OneOf extends BaseValidator
final class OneOf extends ExtendedAbstractValidator
{
public const NOT_ONE_OF = 'notOneOf';

Expand Down
2 changes: 1 addition & 1 deletion src/Alley/Validator/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Laminas\Validator\Exception\InvalidArgumentException;
use Laminas\Validator\ValidatorInterface;

final class Type extends BaseValidator
final class Type extends ExtendedAbstractValidator
{
public const NOT_OF_TYPE = 'notOfType';

Expand Down
52 changes: 52 additions & 0 deletions tests/Alley/Validator/FreeformValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

/*
* This file is part of the laminas-validator-extensions package.
*
* (c) Alley <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Alley\Validator;

use PHPUnit\Framework\TestCase;

final class FreeformValidatorTest extends TestCase
{
public function testSampleImplementation()
{
$actual = new class () extends FreeformValidator {
protected function testValue($value): void
{
$fizz = new DivisibleBy(['divisor' => 3]);
$buzz = new DivisibleBy(['divisor' => 5]);
$fizzbuzz = new FastFailValidatorChain([$fizz, $buzz]);

if ($fizz->isValid($value)) {
$this->error('fizz', 'Fizz');
}

if ($buzz->isValid($value)) {
$this->error('buzz', 'Buzz');
}

if ($fizzbuzz->isValid($value)) {
$this->error('fizzbuzz', 'FizzBuzz');
}
}
};

$this->assertFalse($actual->isValid(9));
$this->assertSame(['fizz' => 'Fizz'], $actual->getMessages());

$this->assertTrue($actual->isValid(1));
$this->assertSame([], $actual->getMessages());

$this->assertFalse($actual->isValid(15));
$this->assertSame(['fizz' => 'Fizz', 'buzz' => 'Buzz', 'fizzbuzz' => 'FizzBuzz'], $actual->getMessages());
}
}

0 comments on commit ef78011

Please sign in to comment.