Accessing additional validation data in Laravel
When developing web applications, handling date inputs is a common task. Consider a scenario where users need to specify a date range by providing both a start date and an end date. As responsible developers, it's essential to validate this input to ensure that it meets specific criteria:
1. Both the start and end dates are provided.
2. The dates are in the correct format.
3. The end date occurs after the start date.
4. Both dates are within the same month and year.
Here's the starting code for these validation rules:
public function rules(): array { return [ 'starts_at' => ['required', 'date'], 'ends_at' => ['required', 'date', 'after:starts_at'], ];}
These rules ensure that the user-provided dates are present, correctly formatted, and that the end date falls after the start date. We already met condition 1 to 3.
In addition, we also want to verify that both the start and end dates occur within the same month. Achieving this requires creating a custom validation rule called SameMonthRule.
class CreatePeriodRequest extends Request{ public function rules(): array { return [ 'starts_at' => ['required', 'date'], 'ends_at' => ['required', 'date', 'after:starts_at', new SameMonthRule()], ]; }}
Custom Validation Rule Implementation
When implementing a standard Rule interface, we don't have any knowledge of other data undergoing validation.
class SameMonthRule implements Rule{ public function passes($attribute, $value): bool { // $value contains the ends_at date, but how do we get starts_at ? } public function message(): string { return 'Both start and end date should be in the same month.'; }
Luckily Laravel provides a convenient solution with the DataAwareRule interface, which allows us to access other data undergoing validation. By defining a setData method within our custom rule, Laravel automatically invokes it before continuing with the validation process. This way we gain access to the 'starts_at' value, allowing us to perform the necessary comparisons during validation.
Here's a simplified version of our SameMonthRule implementation:
class SameMonthRule implements DataAwareRule, Rule{ protected array $data = []; public function setData($data): static { $this->data = $data; return $this; } public function passes($attribute, $value): bool { $endedAt = CarbonImmutable::make($value); $startedAt = CarbonImmutable::make($this->data['starts_at'] ?? null); if ($startedAt === null || $endedAt === null) { return false; } return $startedAt->month === $endedAt->month && $startedAt->year === $endedAt->year; } public function message(): string { return 'Both start and end date should be in the same month and year.'; }