Skip to content

Commit

Permalink
Merge pull request #61 from programmatordev/YAPV-66-create-optional-rule
Browse files Browse the repository at this point in the history
Create Optional rule
  • Loading branch information
andrepimpao authored Apr 1, 2024
2 parents 120ee88 + 0dfd63f commit 2db6382
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 10 deletions.
7 changes: 6 additions & 1 deletion docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Date Rules](#date-rules)
- [Choice Rules](#choice-rules)
- [Iterable Rules](#iterable-rules)
- [Other Rules](#other-rules)

## Basic Rules

Expand Down Expand Up @@ -43,4 +44,8 @@

- [Collection](03-rules_collection.md)
- [EachValue](03-rules_each-value.md)
- [EachKey](03-rules_each-key.md)
- [EachKey](03-rules_each-key.md)

## Other Rules

- [Optional](03-rules_optional.md)
24 changes: 23 additions & 1 deletion docs/03-rules_collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ Validator::collection(fields: [
'age' => 25
]); // false ("name" is blank)

// by default, unknown keys are not allowed
///////////////////////////////

// by default, extra fields are not allowed
Validator::collection(fields: [
'name' => Validator::notBlank(),
'age' => Validator::type('int')->greaterThanOrEqual(18)
Expand All @@ -54,6 +56,26 @@ Validator::collection(
'age' => 25,
'email' => 'mail@example.com'
]); // true

///////////////////////////////

// by default, missing fields are not allowed
Validator::collection(fields: [
'name' => Validator::notBlank(),
'age' => Validator::type('int')->greaterThanOrEqual(18)
])->validate([
'age' => 25
]); // false ("name" is missing)

// but it is possible to use the Optional validation for optiona fields
Validator::collection(fields: [
'name' => Validator::optional(
Validator::notBlank()
),
'age' => Validator::type('int')->greaterThanOrEqual(18)
])->validate([
'age' => 25
]); // true ("name" is optional)
```

> [!NOTE]
Expand Down
37 changes: 37 additions & 0 deletions docs/03-rules_optional.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Optional

Validates only if value is *not* `null`.

```php
Optional(
Validator $validator,
);
```

## Basic Usage

```php
Validator::optional(
Validator::type('int')->greaterThanEqualTo(18)
)->validate(null); // true

Validator::optional(
Validator::type('int')->greaterThanEqualTo(18)
)->validate(20); // true

Validator::optional(
Validator::type('int')->greaterThanEqualTo(18)
)->validate(16); // false
```

## Options

### `validator`

type: `Validator` `required`

Validator that will validate the input value only when it is *not* `null`.

## Changelog

- `1.0.0` Created
4 changes: 4 additions & 0 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ public function notBlank(
?string $message = null
): ChainedValidatorInterface&Validator;

public function optional(
Validator $validator
): ChainedValidatorInterface&Validator;

public function passwordStrength(
string $minStrength = 'medium',
?string $message = null
Expand Down
10 changes: 9 additions & 1 deletion src/Rule/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public function assert(mixed $value, ?string $name = null): void
}

foreach ($this->fields as $field => $validator) {
if (!isset($value[$field])) {
// find if validation is optional
$isOptional = $validator->getRules()[0] instanceof Optional;

if (!isset($value[$field]) && !$isOptional) {
throw new CollectionException(
message: $this->missingFieldsMessage,
parameters: [
Expand All @@ -55,6 +58,11 @@ public function assert(mixed $value, ?string $name = null): void
);
}

// if value is not set but field is optional
if (!isset($value[$field]) && $isOptional) {
$value[$field] = null;
}

try {
$validator->assert($value[$field], \sprintf('"%s"', $field));
}
Expand Down
22 changes: 22 additions & 0 deletions src/Rule/Optional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ProgrammatorDev\Validator\Rule;

use ProgrammatorDev\Validator\Validator;

class Optional extends AbstractRule implements RuleInterface
{
public function __construct(
private readonly Validator $validator
) {}

public function assert(mixed $value, ?string $name = null): void
{
// validate only if value is not null
if ($value === null) {
return;
}

$this->validator->assert($value, $name);
}
}
4 changes: 4 additions & 0 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ public static function notBlank(
?string $message = null
): ChainedValidatorInterface&Validator;

public static function optional(
Validator $validator
): ChainedValidatorInterface&Validator;

public static function passwordStrength(
string $minStrength = 'medium',
?string $message = null
Expand Down
42 changes: 35 additions & 7 deletions tests/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ public static function provideRuleUnexpectedValueData(): \Generator
public static function provideRuleFailureConditionData(): \Generator
{
$exception = CollectionException::class;
$notBlankMessage = '/The "(.*)" value should not be blank, "" given\./';
$extraFieldsMessage = '/The (.*) field is not allowed\./';
$missingFieldsMessage = '/The (.*) field is missing\./';

yield 'invalid field' => [
new Collection(fields: ['field' => Validator::notBlank()]),
['field' => ''],
$exception,
'/The "(.*)" value should not be blank, "" given\./'
$notBlankMessage
];
yield 'extra fields' => [
new Collection(fields: ['field' => Validator::notBlank()]),
Expand All @@ -53,16 +54,25 @@ public static function provideRuleFailureConditionData(): \Generator
$extraFieldsMessage
];
yield 'missing fields' => [
new Collection(
fields: [
'field1' => Validator::notBlank(),
'field2' => Validator::notBlank()
]
),
new Collection(fields: [
'field1' => Validator::notBlank(),
'field2' => Validator::notBlank()
]),
['field1' => 'value1'],
$exception,
$missingFieldsMessage
];
yield 'optional' => [
new Collection(fields: [
'field' => Validator::notBlank(),
'optional' => Validator::optional(
Validator::notBlank()
)
]),
['field' => 'value', 'optional' => ''],
$exception,
$notBlankMessage
];
}

public static function provideRuleSuccessConditionData(): \Generator
Expand All @@ -82,6 +92,24 @@ public static function provideRuleSuccessConditionData(): \Generator
),
['field' => 'value', 'extrafield' => 'extravalue']
];
yield 'optional' => [
new Collection(fields: [
'field' => Validator::notBlank(),
'optional' => Validator::optional(
Validator::notBlank()
)
]),
['field' => 'value']
];
yield 'optional null' => [
new Collection(fields: [
'field' => Validator::notBlank(),
'optional' => Validator::optional(
Validator::notBlank()
)
]),
['field' => 'value', 'optional' => null]
];
}

public static function provideRuleMessageOptionData(): \Generator
Expand Down
38 changes: 38 additions & 0 deletions tests/OptionalTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace ProgrammatorDev\Validator\Test;

use ProgrammatorDev\Validator\Exception\NotBlankException;
use ProgrammatorDev\Validator\Rule\NotBlank;
use ProgrammatorDev\Validator\Rule\Optional;
use ProgrammatorDev\Validator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\Validator\Validator;

class OptionalTest extends AbstractTest
{
use TestRuleFailureConditionTrait;
use TestRuleSuccessConditionTrait;

public static function provideRuleFailureConditionData(): \Generator
{
yield 'default' => [
new Optional(
new Validator(new NotBlank())
),
'',
NotBlankException::class,
'/The (.*) value should not be blank, (.*) given\./'
];
}

public static function provideRuleSuccessConditionData(): \Generator
{
yield 'default' => [
new Optional(
new Validator(new NotBlank())
),
null
];
}
}

0 comments on commit 2db6382

Please sign in to comment.