Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2.5.2 under development

- Enh #709: In date rules use `format` property for formatting dates in error messages when message format properties are not set (@WarLikeLaux)
- Enh #787: Explicitly import classes, functions, and constants in "use" section (@mspirkov)
- Bug #793: Fix translations, broken link in contributing guide, incorrect imports and grammar in documentation (@evilkarter)
- Chg #795: Update Polish translations (@rbrzezinski)
Expand Down
50 changes: 46 additions & 4 deletions src/Rule/Date/BaseDateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
private readonly string $incorrectInputMessage,
private readonly string $tooEarlyMessage,
private readonly string $tooLateMessage,
private readonly bool $messageDateTypeFallbackToRuleType = true,

Check warning on line 46 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ * @psalm-param IntlDateFormatterFormat $timeType * @psalm-param non-empty-string|null $timeZone */ - public function __construct(private readonly int $dateType, private readonly int $timeType, private readonly ?string $timeZone, private readonly ?string $locale, private readonly ?string $messageFormat, private readonly ?int $messageDateType, private readonly ?int $messageTimeType, private readonly string $incorrectInputMessage, private readonly string $tooEarlyMessage, private readonly string $tooLateMessage, private readonly bool $messageDateTypeFallbackToRuleType = true, private readonly bool $messageTimeTypeFallbackToRuleType = true) + public function __construct(private readonly int $dateType, private readonly int $timeType, private readonly ?string $timeZone, private readonly ?string $locale, private readonly ?string $messageFormat, private readonly ?int $messageDateType, private readonly ?int $messageTimeType, private readonly string $incorrectInputMessage, private readonly string $tooEarlyMessage, private readonly string $tooLateMessage, private readonly bool $messageDateTypeFallbackToRuleType = false, private readonly bool $messageTimeTypeFallbackToRuleType = true) { } public function validate(mixed $value, RuleInterface $rule, ValidationContext $context): Result
private readonly bool $messageTimeTypeFallbackToRuleType = true,

Check warning on line 47 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ * @psalm-param IntlDateFormatterFormat $timeType * @psalm-param non-empty-string|null $timeZone */ - public function __construct(private readonly int $dateType, private readonly int $timeType, private readonly ?string $timeZone, private readonly ?string $locale, private readonly ?string $messageFormat, private readonly ?int $messageDateType, private readonly ?int $messageTimeType, private readonly string $incorrectInputMessage, private readonly string $tooEarlyMessage, private readonly string $tooLateMessage, private readonly bool $messageDateTypeFallbackToRuleType = true, private readonly bool $messageTimeTypeFallbackToRuleType = true) + public function __construct(private readonly int $dateType, private readonly int $timeType, private readonly ?string $timeZone, private readonly ?string $locale, private readonly ?string $messageFormat, private readonly ?int $messageDateType, private readonly ?int $messageTimeType, private readonly string $incorrectInputMessage, private readonly string $tooEarlyMessage, private readonly string $tooLateMessage, private readonly bool $messageDateTypeFallbackToRuleType = true, private readonly bool $messageTimeTypeFallbackToRuleType = false) { } public function validate(mixed $value, RuleInterface $rule, ValidationContext $context): Result
) {}

public function validate(mixed $value, RuleInterface $rule, ValidationContext $context): Result
Expand All @@ -67,7 +69,7 @@
'Property' => $context->getCapitalizedTranslatedProperty(),
],
);
return $result;

Check warning on line 72 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $date = $this->prepareValue($value, $rule, $timeZone, false); if ($date === null) { $result->addError($rule->getIncorrectInputMessage() ?? $this->incorrectInputMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty()]); - return $result; + } $min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true); if ($min !== null && $date < $min) {
}

$min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true);
Expand All @@ -81,7 +83,7 @@
'limit' => $this->formatDate($min, $rule, $timeZone),
],
);
return $result;

Check warning on line 86 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true); if ($min !== null && $date < $min) { $result->addError($rule->getTooEarlyMessage() ?? $this->tooEarlyMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty(), 'value' => $this->formatDate($date, $rule, $timeZone), 'limit' => $this->formatDate($min, $rule, $timeZone)]); - return $result; + } $max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true); if ($max !== null && $date > $max) {
}

$max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true);
Expand All @@ -95,7 +97,7 @@
'limit' => $this->formatDate($max, $rule, $timeZone),
],
);
return $result;

Check warning on line 100 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true); if ($max !== null && $date > $max) { $result->addError($rule->getTooLateMessage() ?? $this->tooLateMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty(), 'value' => $this->formatDate($date, $rule, $timeZone), 'limit' => $this->formatDate($max, $rule, $timeZone)]); - return $result; + } return $result; }
}

return $result;
Expand Down Expand Up @@ -187,14 +189,26 @@

private function formatDate(DateTimeInterface $date, Date|DateTime|Time $rule, ?DateTimeZone $timeZone): string
{
$formatterDateType = $this->getMessageDateTypeFromRule($rule)
$ruleMessageDateType = $this->getMessageDateTypeFromRule($rule);
$ruleMessageTimeType = $this->getMessageTimeTypeFromRule($rule);

$formatterDateType = $ruleMessageDateType
?? $this->messageDateType
?? $this->getDateTypeFromRule($rule);
$formatterTimeType = $this->getMessageTimeTypeFromRule($rule)
?? $this->getMessageDateTypeFallback($rule);
$formatterTimeType = $ruleMessageTimeType
?? $this->messageTimeType
?? $this->getTimeTypeFromRule($rule);
?? $this->getMessageTimeTypeFallback($rule);

$format = $rule->getMessageFormat() ?? $this->messageFormat;
if (
$format === null
&& $ruleMessageDateType === null
&& $ruleMessageTimeType === null
&& $this->messageDateType === null
&& $this->messageTimeType === null
) {
$format = $rule->getFormat();
}
Comment thread
WarLikeLaux marked this conversation as resolved.
if (is_string($format) && str_starts_with($format, 'php:')) {
return $date->format(substr($format, 4));
}
Expand Down Expand Up @@ -235,6 +249,34 @@
return (new DateTimeImmutable(timezone: $timeZone))->setTimestamp($timestamp);
}

/**
* @psalm-return IntlDateFormatterFormat
*/
private function getMessageDateTypeFallback(Date|DateTime|Time $rule): int
{
if ($rule instanceof Time) {
return IntlDateFormatter::NONE;
}

return $this->messageDateTypeFallbackToRuleType
? $this->getDateTypeFromRule($rule)
: IntlDateFormatter::SHORT;
}

/**
* @psalm-return IntlDateFormatterFormat
*/
private function getMessageTimeTypeFallback(Date|DateTime|Time $rule): int
{
if ($rule instanceof Date) {
return IntlDateFormatter::NONE;
}

return $this->messageTimeTypeFallbackToRuleType
? $this->getTimeTypeFromRule($rule)
: IntlDateFormatter::SHORT;
}

/**
* @psalm-return IntlDateFormatterFormat
*/
Expand Down
14 changes: 13 additions & 1 deletion src/Rule/Date/DateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use IntlDateFormatter;

use function func_num_args;

/**
* @psalm-import-type IntlDateFormatterFormat from BaseDate
*/
Expand All @@ -25,17 +27,27 @@ public function __construct(
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
) {
$argumentCount = func_num_args();
$messageDateTypeFallbackToRuleType = $messageDateType === null;

// Keep the public default value for BC, but treat it as unset when omitted.
if ($messageDateType === IntlDateFormatter::SHORT && $argumentCount !== 5) {
$messageDateType = null;
$messageDateTypeFallbackToRuleType = false;
}

parent::__construct(
$dateType,
IntlDateFormatter::NONE,
$timeZone,
$locale,
$messageFormat,
$messageDateType,
IntlDateFormatter::NONE,
null,
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
$messageDateTypeFallbackToRuleType,
);
}
}
18 changes: 18 additions & 0 deletions src/Rule/Date/DateTimeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use IntlDateFormatter;

use function func_num_args;

/**
* @psalm-import-type IntlDateFormatterFormat from BaseDate
*/
Expand All @@ -28,6 +30,20 @@ public function __construct(
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
) {
$argumentCount = func_num_args();
$messageDateTypeFallbackToRuleType = $messageDateType === null;
$messageTimeTypeFallbackToRuleType = $messageTimeType === null;

// Keep the public default values for BC, but treat them as unset when omitted.
if ($messageDateType === IntlDateFormatter::SHORT && $argumentCount !== 6) {
$messageDateType = null;
$messageDateTypeFallbackToRuleType = false;
}
if ($messageTimeType === IntlDateFormatter::SHORT && $argumentCount !== 7) {
$messageTimeType = null;
$messageTimeTypeFallbackToRuleType = false;
}

parent::__construct(
$dateType,
$timeType,
Expand All @@ -39,6 +55,8 @@ public function __construct(
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
$messageDateTypeFallbackToRuleType,
$messageTimeTypeFallbackToRuleType,
);
}
}
15 changes: 14 additions & 1 deletion src/Rule/Date/TimeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use IntlDateFormatter;

use function func_num_args;

/**
* @psalm-import-type IntlDateFormatterFormat from BaseDate
*/
Expand All @@ -25,17 +27,28 @@ public function __construct(
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
) {
$argumentCount = func_num_args();
$messageTimeTypeFallbackToRuleType = $messageTimeType === null;

// Keep the public default value for BC, but treat it as unset when omitted.
if ($messageTimeType === IntlDateFormatter::SHORT && $argumentCount !== 5) {
$messageTimeType = null;
$messageTimeTypeFallbackToRuleType = false;
}

parent::__construct(
IntlDateFormatter::NONE,
$timeType,
$timeZone,
$locale,
$messageFormat,
IntlDateFormatter::NONE,
null,
$messageTimeType,
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
true,
$messageTimeTypeFallbackToRuleType,
);
}
}
70 changes: 62 additions & 8 deletions tests/Rule/Date/DateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static function dataValidationFailed(): array
'min' => [
'2024-03-29',
new Date(format: 'yyyy-MM-dd', min: '2025-01-01'),
['' => ['Value must be no earlier than 1/1/25.']],
['' => ['Value must be no earlier than 2025-01-01.']],
],
'min-custom-message' => [
['a' => '2024-03-29'],
Expand All @@ -88,12 +88,12 @@ public static function dataValidationFailed(): array
tooEarlyMessage: 'Prop — {property}. Date — {value}. Min — {limit}.',
),
],
['a' => ['Prop — a. Date — 3/29/24. Min — 1/1/25.']],
['a' => ['Prop — a. Date — 2024-03-29. Min — 2025-01-01.']],
],
'max' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 1/1/24.']],
['' => ['Value must be no later than 2024-01-01.']],
],
'max-custom-message' => [
['a' => '2024-03-29'],
Expand All @@ -104,29 +104,41 @@ public static function dataValidationFailed(): array
tooLateMessage: 'Prop — {property}. Date — {value}. Max — {limit}.',
),
],
['a' => ['Prop — a. Date — 3/29/24. Max — 1/1/24.']],
['a' => ['Prop — a. Date — 2024-03-29. Max — 2024-01-01.']],
],
'rule-and-handler-locales' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', locale: 'ru', max: '2024-01-01'),
['' => ['Value must be no later than 01.01.2024.']],
['' => ['Value must be no later than 2024-01-01.']],
[DateHandler::class => new DateHandler(locale: 'en')],
],
'handler-locale' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 01.01.2024.']],
['' => ['Value must be no later than 2024-01-01.']],
[DateHandler::class => new DateHandler(locale: 'ru')],
],
'handler-custom-message' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Max: 2024-01-01.']],
[DateHandler::class => new DateHandler(tooLateMessage: 'Max: {limit}.')],
],
'handler-message-date-type-null-with-handler-custom-message' => [
'March 29, 2024',
new Date(dateType: IntlDateFormatter::LONG, max: 'January 1, 2024'),
['' => ['Max: January 1, 2024.']],
[DateHandler::class => new DateHandler(messageDateType: null, tooLateMessage: 'Max: {limit}.')],
],
'timestamp' => [
1711705158,
new Date(min: 1711705200),
['' => ['Value must be no earlier than 3/29/24.']],
],
'without-message-date-type' => [
'29*03*2024',
new Date(format: 'php:d*m*Y', max: '11*11*2023', ),
['' => ['Value must be no later than 11/11/23.']],
new Date(format: 'php:d*m*Y', max: '11*11*2023'),
['' => ['Value must be no later than 11*11*2023.']],
[DateHandler::class => new DateHandler(messageDateType: null)],
],
'rule-message-format' => [
Expand All @@ -153,6 +165,48 @@ public static function dataValidationFailed(): array
['' => ['Value must be no later than 10.11.2002.']],
[DateHandler::class => new DateHandler(locale: 'en')],
],
'handler-message-type-overrides-format' => [
'29*03*2024',
new Date(format: 'php:d*m*Y', max: '11*11*2023'),
['' => ['Value must be no later than Saturday, November 11, 2023.']],
[DateHandler::class => new DateHandler(messageDateType: IntlDateFormatter::FULL)],
],
'handler-date-type-does-not-affect-message' => [
'March 29, 2024',
new Date(max: 'January 1, 2024'),
['' => ['Value must be no later than 1/1/24.']],
[DateHandler::class => new DateHandler(dateType: IntlDateFormatter::LONG)],
],
'handler-message-date-type-null-falls-back-to-rule-date-type' => [
'March 29, 2024',
new Date(dateType: IntlDateFormatter::LONG, max: 'January 1, 2024'),
['' => ['Value must be no later than January 1, 2024.']],
[DateHandler::class => new DateHandler(messageDateType: null)],
],
'handler-message-date-type-short-overrides-format' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 1/1/24.']],
[DateHandler::class => new DateHandler(messageDateType: IntlDateFormatter::SHORT)],
],
'format-used-for-message' => [
'01.01.2100',
new Date(
format: 'php:d.m.Y',
min: '19.11.2013',
max: '31.12.2099',
),
['' => ['Value must be no later than 31.12.2099.']],
],
'format-overridden-by-message-format' => [
'01.01.2100',
new Date(
format: 'php:d.m.Y',
max: '31.12.2099',
messageFormat: 'php:Y/m/d',
),
['' => ['Value must be no later than 2099/12/31.']],
],
];
}

Expand Down
Loading
Loading