phpmentors/validator-bundle
A Symfony bundle from PHP Mentors that adds flexible validation support to your application, integrating with Symfony's validation component and offering reusable constraints for validating domain objects, forms, and request data.
This is a thin augmentation layer over Symfony's Validator component, not a standalone validation framework. Treat symfony/validator as the real dependency and this bundle as an optional add-on you evaluate before adopting.
Version context (v1.3.0). The only substantive change in 1.3.0 is a widening of the supported Symfony range to 2.8.0 || 3.0.0 || 4.0.0 and above. There are no new constraints, no new validator factory behaviour, and no PHP 8 attribute support. If you were on Symfony 2.7 or lower, 1.3.0 is the floor-raising release; if you are on Symfony 5+, this release changes nothing for you — you are still outside the supported matrix.
Minimal steps
composer require phpmentors/validator-bundle
// app/AppKernel.php
$bundles = [
// ...
new PHPMentors\ValidatorBundle\PHPMentorsValidatorBundle(),
];
On Symfony 4-style apps:
// config/bundles.php
return [
// ...
PHPMentors\ValidatorBundle\PHPMentorsValidatorBundle::class => ['all' => true],
];
framework.validation is enabled and that validator.validator_factory resolves in the container (see Gotchas — this is the service the bundle typically decorates).Where to look first
README.md — sparse; expect to read source.DependencyInjection/ and Resources/config/ — shows exactly which core validator services are replaced or decorated, and whether 1.3.0's Symfony 3/4 branch changes the service definitions for those versions.Validator/Constraints/ — the annotations/constraints the bundle adds on top of @Assert\*.Tests/ — the most reliable documentation of intended usage for a package this small. After 1.3.0, check whether the CI matrix actually covers Symfony 4 — the release note claims support, the test suite is the proof.First use case
The realistic one is a constraint validator that needs a dependency (repository, HTTP client, policy service) rather than being a stateless new-able class:
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
final class UniqueEmailValidator extends ConstraintValidator
{
public function __construct(private UserRepository $users) {}
public function validate($value, Constraint $constraint): void
{
if (null === $value || '' === $value) {
return;
}
if ($this->users->findOneBy(['email' => $value])) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $value)
->addViolation();
}
}
}
Register it as a service and tag it:
App\Validator\Constraints\UniqueEmailValidator:
tags: [validator.constraint_validator]
That is the pattern the bundle exists to smooth over. With 1.3.0 confirmed on Symfony 3/4, verify against the source whether the bundle's container-aware factory is the thing doing the wiring or whether Symfony's built-in ContainerConstraintValidatorFactory (default since 3.4) already handles it — on Symfony 3.4+ this is the single most important thing to establish before you commit to phpmentors/validator-bundle.
Inject ValidatorInterface, validate at the service boundary. Keep validation out of controllers where possible; validate in command handlers, application services, or a dedicated *Assembler. This makes the bundle's enhanced factory the single wiring point and keeps your domain objects free of container lookups.
final class RegisterUserHandler
{
public function __construct(
private ValidatorInterface $validator,
private UserRepository $users,
) {}
public function __invoke(RegisterUser $command): void
{
$user = User::register($command->email);
$errors = $this->validator->validate($user);
if (count($errors) > 0) {
throw new ValidationFailedException($errors);
}
$this->users->add($user);
}
}
Use validation groups to model lifecycle state. A draft entity and a published entity rarely share the same invariants. Annotate constraints with groups: ['registration'] / groups: ['publish'] and call $validator->validate($obj, null, ['publish']). This is standard Symfony, but it is the pattern that lets you drop the bundle in without rewriting your constraints.
Keep entity annotations stateless, push stateful checks into validators. @Assert\NotBlank, Length, Regex stay on the entity. Anything touching the database, a clock, or an external API becomes a custom Constraint + service-backed validator. The bundle's value is concentrated entirely in that second category.
Forms vs. domain validation. FormType constraints run at the HTTP layer and stop at the DTO. Do not duplicate them on the entity; let the form reject malformed input and let the domain validator enforce invariants. If you validate both, you get double error messages and confusing ViolationList shapes.
Testing. Extend Symfony\Component\Validator\ConstraintValidatorTestCase and call setConstraintValidatorFactory() with your container. This exercises the same factory path the bundle installs, so tests catch wiring regressions that unit tests with new UniqueEmailValidator($repo) would miss. On a Symfony 3/4 stack (the matrix 1.3.0 targets), also boot against each framework version you support — the bundle's service definitions are version-switched, so a green test on 3.4 says nothing about 4.x.
Doctrine integration. Prefer explicit validation over @Gedmo/lifecycle-callback magic. If you must validate on flush, be aware that violations surface as ConstraintViolationListException mid-transaction, which is hard to map back to a user-facing error — validate before persist() in the handler instead.
composer why-not phpmentors/validator-bundle will tell you, and the answer is the same as before 1.3.0. If you are building on a current LTS, plan around this bundle, not with it.doctrine/annotations is itself deprecated. Even on Symfony 4 you are on attribute-less territory.ContainerConstraintValidatorFactory ships with the framework and is the default validator.validator_factory. 1.3.0 does not remove or change this — the release note is purely a dependency-range bump. Before adopting, remove the bundle and confirm your tagged validators still get injected. If they do, you have zero functional reason to carry the dependency; all 1.3.0 buys you is "composes on Symfony 4."bin/console debug:container validator.validator_factory and debug:container --tag=validator.constraint_validator will show whether the bundle replaced the factory and whether your validators are registered. If your validators are missing from the tag list, autoconfiguration is off — add services: _defaults: autoconfigure: true.validator.validator_factory from two places (this bundle plus your own compiler pass) silently loses one. Grep DependencyInjection/Compiler/ in the bundle source before adding your own pass. 1.3.0's version-switched service files make it more likely, not less, that you will have two definitions for the same id on a given Symfony version.bin/console cache:clear after touching bundle registration or compiler passes; stale container XML is the usual cause of "my validator has no constructor args."debug:validator is your friend. bin/console debug:validator 'App\Entity\User' prints the resolved constraint/group matrix including constraints contributed by bundle-provided loaders — useful for confirming the bundle's annotations are actually being read on Symfony 4 specifically.persist() directly.ConstraintValidatorFactoryInterface is the clean seam: implement it, register under the bundle's expected service id, and you own the lifecycle again if the package is abandoned.How can I help you explore Laravel packages today?