112 lines
3.2 KiB
PHP
112 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Model;
|
|
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
|
|
final readonly class ContingentCalendarQuery
|
|
{
|
|
private const int MAX_RANGE_DAYS = 366;
|
|
|
|
public function __construct(
|
|
#[Assert\NotBlank]
|
|
#[Assert\Length(max: 16)]
|
|
#[Assert\Regex(pattern: '/^[A-Za-z0-9_-]+$/')]
|
|
public string $hotelCode,
|
|
|
|
#[Assert\Date]
|
|
public ?string $dateFrom = null,
|
|
|
|
#[Assert\Date]
|
|
public ?string $dateTo = null,
|
|
) {
|
|
}
|
|
|
|
#[Assert\Callback]
|
|
public function validateRange(ExecutionContextInterface $context): void
|
|
{
|
|
$from = $this->dateFrom;
|
|
$to = $this->dateTo;
|
|
|
|
$hasFrom = null !== $from && '' !== $from;
|
|
$hasTo = null !== $to && '' !== $to;
|
|
|
|
// Neither supplied: the caller wants the full priced span, which the persisted prices define.
|
|
if (!$hasFrom && !$hasTo) {
|
|
return;
|
|
}
|
|
|
|
// A lone dateFrom would have to mean "from there to the end of the prices", a third mode
|
|
// nobody asked for. Demand the pair instead of inventing a meaning for half of it.
|
|
if (!$hasFrom || !$hasTo) {
|
|
$context->buildViolation('dateFrom and dateTo must be supplied together.')
|
|
->atPath($hasFrom ? 'dateTo' : 'dateFrom')
|
|
->addViolation();
|
|
|
|
return;
|
|
}
|
|
|
|
$dateFrom = self::parseDate($from);
|
|
$dateTo = self::parseDate($to);
|
|
|
|
if (null === $dateFrom || null === $dateTo) {
|
|
return;
|
|
}
|
|
|
|
if ($dateTo < $dateFrom) {
|
|
$context->buildViolation('dateTo must not be before dateFrom.')
|
|
->atPath('dateTo')
|
|
->addViolation();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($dateFrom->diff($dateTo)->days > self::MAX_RANGE_DAYS) {
|
|
$context->buildViolation(sprintf('The date range must not exceed %d days.', self::MAX_RANGE_DAYS))
|
|
->atPath('dateTo')
|
|
->addViolation();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Null when the parameter was not supplied, which puts the request into full-span mode.
|
|
*/
|
|
public function dateFromDate(): ?\DateTimeImmutable
|
|
{
|
|
return $this->suppliedDate($this->dateFrom);
|
|
}
|
|
|
|
/**
|
|
* Null when the parameter was not supplied, which puts the request into full-span mode.
|
|
*/
|
|
public function dateToDate(): ?\DateTimeImmutable
|
|
{
|
|
return $this->suppliedDate($this->dateTo);
|
|
}
|
|
|
|
private function suppliedDate(?string $value): ?\DateTimeImmutable
|
|
{
|
|
if (null === $value || '' === $value) {
|
|
return null;
|
|
}
|
|
|
|
return self::parseDate($value)
|
|
?? throw new \LogicException('ContingentCalendarQuery must be validated before use.');
|
|
}
|
|
|
|
private static function parseDate(string $value): ?\DateTimeImmutable
|
|
{
|
|
$date = \DateTimeImmutable::createFromFormat('!Y-m-d', $value);
|
|
$errors = \DateTimeImmutable::getLastErrors();
|
|
|
|
if (false === $date || (false !== $errors && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))) {
|
|
return null;
|
|
}
|
|
|
|
return $date->format('Y-m-d') === $value ? $date : null;
|
|
}
|
|
}
|