gal-digital-gmbh/validation
A Laravel package that extends the validator with reusable custom rules and helpers, making form and request validation cleaner and more consistent. Simple to install and configure, it helps centralize validation logic and reduce duplicated checks across controllers and form requests.
The package is a thin convenience layer over symfony/validator, so the fastest way to get productive is to understand the underlying component first and then see what the wrapper removes. As of 2.0.0 it requires Symfony 6 (PHP 8.1+) and exposes a small, now-typed Validator class that proxies validate, validateProperty, validatePropertyValue, getMetadataFor and hasMetadataFor.
composer require gal-digital-gmbh/validation:^2.0 symfony/validator:^6.0
vendor/gal-digital-gmbh/validation/src/ before writing any code. With zero stars and no documentation surface, the source is the documentation. Look for:
ValidatorInterface (this is what you should reuse, not re-instantiate),Validator class itself — it is the whole public API and mirrors Symfony’s method names one-for-one, so there is little to learn beyond the method signatures,ValidatorBuilder: enable attribute mapping, and register a translator if you want localized violation messages.use Symfony\Component\Validator\Constraints as Assert;
final class CreateUserInput
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Email]
public readonly string $email = '',
#[Assert\NotBlank]
#[Assert\Length(min: 8)]
public readonly string $password = '',
) {}
}
Then pass it through the package’s Validator (or the ValidatorInterface it exposes) and inspect the ConstraintViolationListInterface.The first milestone should be: one DTO, one constraint set, one place in the codebase that turns violations into a 422/400 response. Once that loop works, everything else is repetition.
DTO + attribute constraints, validated at the boundary. This is the highest-leverage pattern. Keep validation declarative on the DTO and let the package handle validator construction. Controllers, console commands, and queue consumers all call the same validator, so rules cannot drift between HTTP and CLI paths.
Reuse a single validator instance. Symfony’s validator caches constraint metadata, and building it per request is wasteful. If the package does not provide a container binding, register the factory output as a singleton in your service container and inject ValidatorInterface everywhere. In Laravel, that means a small service provider binding; in Symfony, an alias to the package’s service.
Normalize violations into a stable response shape early, so API clients do not depend on Symfony’s internal violation structure:
$errors = [];
foreach ($violations as $violation) {
$errors[$violation->getPropertyPath()][] = $violation->getMessage();
}
Do this in one adapter class, not in every controller.
Use validateProperty / validatePropertyValue for partial updates. 2.0.0 makes these first-class typed methods on the package’s Validator, which is the cleanest way to handle PATCH semantics: validate only the fields present in the payload against the constraints declared on the DTO, instead of maintaining a parallel “update” DTO or a separate group set.
Use validation groups for context-dependent rules. A CreateUserInput and UpdateUserInput often share fields but differ on required-ness. Groups (#[Assert\NotBlank(groups: ['create'])]) plus $validator->validate($dto, null, ['create']) keep one DTO instead of two near-duplicates. getMetadataFor() is now typed and useful when you need to introspect which constraints exist for a class/group before validating.
Validate nested objects explicitly. For value objects or child DTOs, Symfony only cascades when the property is annotated with #[Assert\Valid]. If the package adds any “automatic deep validation,” verify it in a test — do not assume it.
Custom constraints for domain rules. When a rule needs a repository (uniqueness, existence), write a Constraint + ConstraintValidator pair and register the validator as a service so it can receive dependencies. This is the intended extension point and keeps the DTO free of service calls.
Validate arrays/maps with Collection and All. For JSON payloads that are not mapped to objects yet, new Assert\Collection([...]) with allowExtraFields: false is a quick way to harden an endpoint before a DTO exists.
Integrate at the edges only. Do not sprinkle $validator->validate() through domain services. Validate once at the entry point, then trust the typed object. Double validation makes failures hard to trace and couples your domain to the validator.
Wrap, don’t extend. Since 2.0.0 the package’s classes are final. Anything you previously did by subclassing Validator must become composition: your own class implementing ValidatorInterface (or a small decorator) that delegates to the package instance.
$request->validate(), no required|email string syntax, and no ValidationException that redirects with old input. You are working with ConstraintViolationListInterface and must map it yourself. If the team expects Laravel semantics, that mismatch will cause friction.final, and adds native parameter types to validate, validateProperty, validatePropertyValue, getMetadataFor and hasMetadataFor. Pin ^2.0 and budget an upgrade task: any subclass of the package’s Validator breaks outright, and callers that previously relied on loose coercion (e.g. passing a string where an object/array is expected) will now get a TypeError instead of a silent pass.final class. If your tests mocked the package’s Validator directly, switch to injecting and mocking Symfony\Component\Validator\Validator\ValidatorInterface, or test through a real validator instance. This is the most likely place the upgrade breaks a green test suite.@Assert\NotBlank in docblocks), they will be silently ignored and validation will always pass. Grep for @Assert during the upgrade.symfony/translation. Without a translator wired into the builder, violation messages fall back to English keys. If the product is German-facing (likely, given the vendor), set the translator’s locale explicitly and test one message end-to-end.Valid is not implicit. Nested DTOs will pass even when their inner fields are invalid unless the parent property carries #[Assert\Valid]. This is the single most common “validation is broken” report with Symfony’s validator.NotBlank rejects empty strings; NotNull does not. Length ignores null unless combined with NotBlank. Get these combinations right in tests, because they behave differently from Laravel’s required.Default. Calling validate($dto, null, ['create']) will not run Default constraints unless you include it. This surprises people constantly.final, and type declarations — not a feature release, which tells you the upstream author is keeping it healthy but not growing it. Read the source, check the composer.json for the supported Symfony/PHP range, and consider whether a ~50-line internal factory around Validation::createValidatorBuilder() gives you the same convenience with one less dependency. If you adopt it, pin the version and add a test that locks in the violation behavior you rely on.validateProperty/validatePropertyValue for partial payloads, and custom ConstraintValidator services. With classes now final, subclassing is no longer an option — anything you need beyond those is usually a sign you should call Symfony’s validator directly.How can I help you explore Laravel packages today?