pixelavengers/extra-validator-bundle
Extra Validator Bundle extends Laravel's validator with additional custom rules for common validation needs. It helps keep form requests and controllers cleaner by replacing repetitive custom checks with reusable, easy-to-apply validation rules.
First: this is not a Laravel package. pixelavengers/extra-validator-bundle is a Symfony 2-era bundle (ricardclau/PixelavengersExtraValidatorBundle). There is no service provider, no facade, no config file Laravel can publish, and no extra.laravel block for package discovery. You will not be able to composer require it into a modern Laravel app without version gymnastics, because its constraints almost certainly pin Symfony 2.x components and an old PHP range.
So the realistic "getting started" is: evaluate whether to vendor it, fork it, or extract the algorithms.
Validator/Constraints/ and follow the classic Symfony pair pattern: a Constraint subclass plus a ConstraintValidator implementation. The interesting logic (checksum math, regex, normalization) is entirely inside the validator's validate() method.composer require pixelavengers/extra-validator-bundle --dry-run
Expect a wall of conflicts. If it resolves, you are probably on an ancient PHP.validate() body into a native Laravel Rule object and drop the Symfony dependency entirely. That is the highest-value first step and takes minutes.// app/Rules/Nie.php — ported from the bundle's validator
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Nie implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$value = strtoupper(str_replace(['-', ' '], '', (string) $value));
if (! preg_match('/^[XYZ]\d{7}[A-Z]$/', $value)) {
$fail("The {$attribute} is not a valid NIE.");
return;
}
$map = ['X' => 0, 'Y' => 1, 'Z' => 2];
$digits = $map[$value[0]] . substr($value, 1, 7);
$letter = 'TRWAGMYFPDXBNJZSQVHLCKE'[$digits % 23];
if ($letter !== $value[8]) {
$fail("The {$attribute} is not a valid NIE.");
}
}
}
$request->validate([
'nie' => ['required', 'string', new \App\Rules\Nie],
]);
Pattern A — Rule adapter around the Symfony constraint. If you must keep the dependency (e.g. you are porting a Symfony app incrementally), wrap the constraint in a Laravel rule and drive a standalone Symfony validator.
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Symfony\Component\Validator\Validation;
use Pixelavengers\ExtraValidatorBundle\Validator\Constraints as Extra;
class SymfonyConstraintRule implements ValidationRule
{
public function __construct(private object $constraint) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$violations = Validation::createValidator()
->validate($value, $this->constraint);
foreach ($violations as $violation) {
$fail($violation->getMessage());
}
}
}
'id' => ['required', new SymfonyConstraintRule(new Extra\SomeIdConstraint)],
Pattern B — Bind the Symfony validator in a service provider. If you go the adapter route, register a singleton so you are not rebuilding the validator per field, and register the bundle's validators as tagged services. Symfony's ConstraintValidatorFactory expects services tagged validator.constraint_validator; Laravel has no equivalent, so you wire them manually.
$this->app->singleton(\Symfony\Component\Validator\Validator\ValidatorInterface::class, function () {
return \Symfony\Component\Validator\Validation::createValidator();
});
Pattern C — Port to a reusable rule set (recommended). Convert each validator into a dedicated Rule class under app/Rules/, add Pest/PHPUnit coverage with the fixture values from the bundle's tests (there usually are a few, and they are gold for parity), and delete the dependency. You keep the intent, lose the abandoned code.
Pattern D — Batch use in Form Requests. Once ported, these are ordinary rules and compose naturally with nullable, bail, and conditional rules. Chain them in a FormRequest::rules() method so controller code stays clean.
Pattern E — Reuse for outbound data hygiene. These format validators are as useful for sanitising data before persisting (normalising an IBAN or tax ID) as they are for rejecting input. Consider a small NormalizesIdentifier concern shared between the rule and your model mutators.
getName(), setDefaultOption(), getDefaultOption(), and validatedBy(). The first two are removed in Symfony 3+/4+. If you install on anything modern these will fatal, which is the practical reason a direct install usually fails.composer.json will have type: symfony-bundle and a Symfony\Component\HttpKernel\Bundle\Bundle subclass. Composer will happily download it, but Laravel will never load it. Do not expect a facade or config publish to appear — it will not, and there is nothing to debug.message and a translation domain; Laravel's translator is a different API. Messages will come through in whatever the constraint's default string is, not your lang/ files. If you need localised output, translate in the adapter, not by hoping Laravel picks it up.validatedBy() returns Symfony class names. In the adapter pattern, the constraint factory resolves validators by class name from a container that Laravel does not populate. Either instantiate the validator yourself or accept the default no-arg constructor; the latter fails for any validator with dependencies.null and sometimes on non-strings, whereas Laravel's nullable/string order may hand it something unexpected. Add an explicit is_string($value) guard at the top of any ported validate().Validator::extend() in a service provider. It gives you a named rule ('nie') callable everywhere, which is far closer to Laravel ergonomics than the adapter class and hides the Symfony dependency from your controllers.X/Y/Z prefix mapping) before you delete the dependency. Those cases are the only reliable way to prove your port is equivalent.How can I help you explore Laravel packages today?