Skip to content

Commit

Permalink
Update DateTimeDiff to generate results with siblings
Browse files Browse the repository at this point in the history
Since I updated the validation engine[1], it became possible to create
results with siblings. This commit changes the "DateTimeDiff", allowing
it to create a result with a sibling when possible. That will improve
the clarity of the error messages.

While at it, I noticed that we were not translating the type of
interval, so I fixed that and improved the documentation.

[1]: 238f2d5
  • Loading branch information
henriquemoody committed Dec 9, 2024
1 parent be72a54 commit d5cf131
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 133 deletions.
57 changes: 50 additions & 7 deletions docs/rules/DateTimeDiff.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- `DateTimeDiff(string $type, Rule $rule)`
- `DateTimeDiff(string $type, Rule $rule, string $format)`
- `DateTimeDiff(string $type, Rule $rule, string $format, DateTimeImmutable $now)`

Validates the difference of date/time against a specific rule.

Expand Down Expand Up @@ -30,20 +31,62 @@ The supported types are:

## Templates

`DateTimeDiff::TEMPLATE_STANDARD`
The first two templates serve as message suffixes:

| Mode | Template |
|------------|--------------------------------------------------------------|
| `default` | The number of {{type|raw}} between {{now|raw}} and |
| `inverted` | The number of {{type|raw}} between {{now|raw}} and |
```php
v::dateTimeDiff('years', v::equals(2))->assert('1 year ago')
// The number of years between now and 1 year ago must be equal to 2

v::not(v::dateTimeDiff('years', v::lessThan(8)))->assert('7 year ago')
// The number of years between now and 7 year ago must not be less than 8
```

### `DateTimeDiff::TEMPLATE_STANDARD`

Used when `$format` and `$now` are not defined.

| Mode | Template |
|------------|---------------------------------------------------|
| `default` | The number of {{type|trans}} between now and |
| `inverted` | The number of {{type|trans}} between now and |

### `DateTimeDiff::TEMPLATE_CUSTOMIZED`

Used when `$format` or `$now` are defined.

| Mode | Template |
|------------|----------------------------------------------------------------|
| `default` | The number of {{type|trans}} between {{now|raw}} and |
| `inverted` | The number of {{type|trans}} between {{now|raw}} and |

### `DateTimeDiff::TEMPLATE_WRONG_FORMAT`

Used when the input cannot be parsed with the given format.

| Mode | Template |
|------------|---------------------------------------------------------------------------------------------------------------|
| `default` | For comparison with {{now|raw}}, {{name}} must be a valid datetime in the format {{sample|raw}} |
| `inverted` | For comparison with {{now|raw}}, {{name}} must not be a valid datetime in the format {{sample|raw}} |

## Template placeholders

| Placeholder | Description |
|-------------|------------------------------------------------------------------|
| `name` | The validated input or the custom validator name (if specified). |
| `now` | |
| `type` | |
| `now` | The date and time that is considered as now. |
| `sample` | A sample of the datetime. |
| `type` | The type of interval (years, months, etc.). |

## Caveats

When using custom templates, the key must be `dateTimeDiff` + name of the rule you passed, for example:

```php
v::dateTimeDiff('years', v::equals(2))->assert('1 year ago', [
'dateTimeDiffEquals' => 'Please enter a date that is 2 years ago'
]);
// Please enter a date that is 2 years ago.
```

## Categorization

Expand Down
55 changes: 0 additions & 55 deletions library/Helpers/CanExtractRules.php

This file was deleted.

63 changes: 46 additions & 17 deletions library/Rules/DateTimeDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,42 @@
use DateTimeImmutable;
use DateTimeInterface;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Helpers\CanExtractRules;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Standard;

use function array_map;
use function in_array;
use function ucfirst;

#[Template(
'The number of {{type|raw}} between {{now|raw}} and',
'The number of {{type|raw}} between {{now|raw}} and',
'The number of {{type|trans}} between now and',
'The number of {{type|trans}} between now and',
self::TEMPLATE_STANDARD
)]
#[Template(
'The number of {{type|trans}} between {{now|raw}} and',
'The number of {{type|trans}} between {{now|raw}} and',
self::TEMPLATE_CUSTOMIZED
)]
#[Template(
'For comparison with {{now|raw}}, {{name}} must be a valid datetime in the format {{sample|raw}}',
'For comparison with {{now|raw}}, {{name}} must not be a valid datetime in the format {{sample|raw}}',
self::TEMPLATE_WRONG_FORMAT
)]
final class DateTimeDiff extends Standard
{
use CanValidateDateTime;
use CanExtractRules;

private readonly Rule $rule;
public const TEMPLATE_CUSTOMIZED = '__customized__';
public const TEMPLATE_WRONG_FORMAT = '__wrong_format__';

/** @param "years"|"months"|"days"|"hours"|"minutes"|"seconds"|"microseconds" $type */
public function __construct(
private readonly string $type,
Rule $rule,
private readonly Rule $rule,
private readonly ?string $format = null,
private readonly ?DateTimeImmutable $now = null,
) {
Expand All @@ -47,27 +59,44 @@ public function __construct(
$availableTypes
);
}
$this->rule = $this->extractSiblingSuitableRule(
$rule,
new InvalidRuleConstructorException('DateTimeDiff must contain exactly one rule')
);
}

public function evaluate(mixed $input): Result
{
$now = $this->now ?? new DateTimeImmutable();
$compareTo = $this->createDateTimeObject($input);
if ($compareTo === null) {
return Result::failed($input, $this);
$parameters = ['sample' => $now->format($this->format ?? 'c'), 'now' => $this->nowParameter($now)];

return Result::failed($input, $this, $parameters, self::TEMPLATE_WRONG_FORMAT)
->withId('dateTimeDiff' . ucfirst($this->rule->evaluate($input)->id));
}

$now = $this->now ?? new DateTimeImmutable();
$nextSibling = $this->rule
->evaluate($this->comparisonValue($now, $compareTo))
->withNameIfMissing($input instanceof DateTimeInterface ? $input->format('c') : $input);
return $this->enrichResult(
$this->nowParameter($now),
$input,
$this->rule->evaluate($this->comparisonValue($now, $compareTo))
);
}

private function enrichResult(string $now, mixed $input, Result $result): Result
{
$name = $input instanceof DateTimeInterface ? $input->format('c') : $input;

if (!$result->isSiblingCompatible()) {
return $result
->withNameIfMissing($name)
->withChildren(
...array_map(fn(Result $child) => $this->enrichResult($now, $input, $child), $result->children)
);
}

$parameters = ['type' => $this->type, 'now' => $this->nowParameter($now)];
$parameters = ['type' => $this->type, 'now' => $now];
$template = $now === 'now' ? self::TEMPLATE_STANDARD : self::TEMPLATE_CUSTOMIZED;

return (new Result($nextSibling->isValid, $input, $this, $parameters))->withNextSibling($nextSibling);
return (new Result($result->isValid, $input, $this, $parameters, $template, id: $result->id))
->withPrefixedId('dateTimeDiff')
->withNextSibling($result->withNameIfMissing($name));
}

private function comparisonValue(DateTimeInterface $now, DateTimeInterface $compareTo): int|float
Expand Down
Loading

0 comments on commit d5cf131

Please sign in to comment.