An invokable validation rule, and the closure it replaced

A closure rule copied into eleven form requests, with two of the copies subtly out of date.

final class ValidVatNumber implements ValidationRule
{
    public function __construct(private VatLookup $lookup) {}

    public function validate(string $attr, mixed $value, Closure $fail): void
    {
        if (! $this->lookup->isValid($value)) {
            $fail('The :attribute is not a valid VAT number.');
        }
    }
}

// resolved from the container, so it can have dependencies
'vat_number' => ['required', app(ValidVatNumber::class)],

The interface is a small improvement over the previous one — a single validate method with a failure closure rather than a passes-and-message pair — and the real gain is that a rule with a dependency is now ordinary. The eleven copies were closures precisely because making a rule class used to mean either a static call or a service locator, and both were unpleasant enough that copying won.