Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Extra Validator Bundle Laravel Package

ideup/extra-validator-bundle

Extra Validator Bundle adds ready-made Symfony validation constraints and rules for common cases, helping you validate user input with less custom code. A small, focused package for projects that need extra validators beyond Symfony's built-in set.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony2 bundle, not a Laravel package — there is no service provider, no publishable config, and no ValidationRule contract. "Getting started" here means opening the repository as a reference implementation and porting the four validators into idiomatic Laravel code. Do not composer require it into a Laravel app; the Symfony2 dependency pins will conflict with PHP 8 / Laravel 10+ and the bundle APIs predate Symfony 3.

Minimal path:

  1. Clone and locate the source. Inspect Validator/Constraints/ and Validator/Constraints/*Validator.php. You are looking for four logic pairs:

    • DNI — 8 digits + mod-23 control letter (TRWAGMYFPDXBNJZSQVHLCKE, index = number % 23).
    • NIE — X/Y/Z prefix (mapped to 0/1/2), 7 digits + mod-23 letter.
    • CIF — entity-type letter + 7 digits + control digit/letter (odd/even position weighting, sum, mod 10, then JABCDEFGHI mapping for letter-control classes).
    • Spanish postcode — 5 digits, province prefix 01–52, valid numeric range roughly 01000–52999.
  2. Read the Symfony wiring so you know what to discard. DependencyInjection/Extension.php, Resources/config/*.xml, and getDefaultOption()/setDefaultOption() calls in the constraints are the parts that break on modern Symfony. Note them, ignore them — you are only after the isValid() bodies.

  3. Build a vector table before writing any code. Assemble known-good and known-bad fixtures by hand: a valid DNI (12345678Z), an invalid one (wrong check letter), an NIE with each prefix (X, Y, Z), a CIF from each entity class (A/B/C for digit-control, J/N/P/Q/S for letter-control), and boundary postcodes (01000 valid, 00000 invalid, 52999 valid, 53000 invalid). This table is your test suite and your source of truth — everything else flows from it.

  4. First use case. A SpanishDni rule applied to a signup tax_id field with a Spanish-localized error message. Get that one rule green against the vector table, then clone the pattern for the other three.

Budget roughly half a day to read and derive the algorithms, one to two days to port and test all four, and a review pass by someone with Spanish-market knowledge.

Implementation Patterns

Port to ValidationRule, not Validator::extend. The closure-based Validator::extend approach is fine for a quick prototype but scatters logic across a service provider. Implement each validator as an invokable class implementing Illuminate\Contracts\Validation\ValidationRule, with message() returning a translatable key:

final class SpanishDni implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value) || ! preg_match('/^\d{8}[A-Z]$/i', $value)) {
            $fail('validation.spanish_dni')->translate();
            return;
        }
        $letter = 'TRWAGMYFPDXBNJZSQVHLCKE'[(int) substr($value, 0, 8) % 23];
        if (strtoupper($value[8]) !== $letter) {
            $fail('validation.spanish_dni')->translate();
        }
    }
}

One rule per identifier, not one composite "Spanish ID" rule. DNI, NIE, and CIF have different regex shapes and different check-digit algorithms. Keep them as separate classes and let the caller compose. A "Spanish tax identifier" super-rule is tempting but muddles error messages and makes it impossible to require a specific type (e.g. a personal account must supply DNI or NIE, never CIF).

Compose explicitly with Rule::any() or a wrapper. When a field accepts DNI or NIE, wrap them rather than merging logic:

Rule::any(new SpanishDni(), new SpanishNie())

This keeps each validator independently testable and lets you swap in a stricter variant later.

Pair postcode validation with country selection. Spanish postcode validation is only correct when the address is in Spain. In a form with a country dropdown, gate the rule with Rule::when($country === 'ES', [new SpanishPostcode()]). Applying Spanish rules to a Portuguese or Mexican address will produce false rejections that are hard to trace in support tickets.

Normalize before validating. Users paste identifiers with spaces, hyphens, or lowercase. Run a small prepare()-style normalizer — strtoupper(str_replace([' ', '-'], '', $value)) — either inside the rule or as a form request mutator. Do it once at the boundary; do not scatter normalization across controllers.

Data-provider tests are the contract. Use Pest/PHPUnit data providers backed directly by the vector table you built in step 3. This is the whole maintenance story for a ported rule — the test file is the spec. Include a negative set for whitespace, lowercase, 9-digit inputs, and empty strings.

Publish internally, not to Packagist. Put the four rules in app/Rules or a small first-party package. If any code is vendored verbatim, add a header comment naming the origin (ideup/extra-validator-bundle), linking the repo, and copying the license notice.

Gotchas and Tips

The Symfony APIs in the constraints will not run on Laravel-adjacent Symfony versions. getDefaultOption() and setDefaultOption() were removed in Symfony 3.0. If you attempt a "bridge" by pulling symfony/validator: ^6|^7 and reusing the constraint classes, expect container boot errors at runtime, not at composer install. Do not attempt the bridge without a deliberate spike — the four algorithms are cheaper to reimplement than the dependency tree is to untangle.

Post-2008 CIF rules. The CIF check applies a different branch depending on entity-type letter: A/B/E/H use a numeric control digit, J/N/P/Q/R/S/U/W use a letter from JABCDEFGHI. Some older implementations only handle one branch. Cross-check the source against the current specification and the vector table before trusting it.

NIE prefix mapping is not X=0 by accident. X, Y, Z map to 0, 1, 2 in the mod-23 calculation. If the source silently strips the prefix or substitutes the literal letter, the check letter will be wrong for every NIE.

Canary Islands, Ceuta, and Melilla. Postcode prefixes 35, 38, 51, and 52 are valid Spanish provinces but have different tax treatment (IGIC/IPSI rather than IVA). A postcode-format validator does not and should not enforce tax regime — do not encode business logic into the postcode rule. If you need to branch on regime, derive it from the province prefix in a separate service.

Format validation is not existence validation. A passing DNI/NIE/CIF check confirms the checksum is internally consistent; it does not confirm the entity is registered, active, or current. For B2B invoicing and KYC, this is a first line of defense only. Call out the limitation in the ticket, in the error message copy, and in any downstream compliance documentation.

Store the outcome and the reason. DNI/NIE are personal data under GDPR/LOPDGDD. When a validation rejects a value, persist the validation result and reason alongside the record so a support agent can reproduce the rejection without re-running the rule. Do not log the raw identifier in plaintext.

Localize the messages, not just the field label. Use translation keys like validation.spanish_dni, validation.spanish_nie, validation.spanish_cif, validation.spanish_postcode, with Spanish strings under lang/es/validation.php. The original bundle ships translations under Resources/translations/ — harvest those strings verbatim as a starting point, but verify the grammar; a :attribute placeholder in Spanish often needs the article (el :attribute) that the English string omits.

Silent false negatives are the highest-severity failure. If a valid NIE is rejected because the source predates a regulatory clarification, you block a legitimate Spanish user at signup. Mitigate with a canary cohort or a manual-override path on the validation failure before you roll it out broadly.

Treat the repository as unmaintained. Nine stars, zero dependents, single maintainer, Symfony 2 end-of-life, and a package-name/repository-name mismatch (ideup/extra-validator-bundle vs javiacei/ExtraValidatorBundle) that complicates provenance. Assume no upstream fixes, no PHP 8 patches, and no security responses. The four rules you write are yours to own from day one — assign a named owner and pin the test vector table as the internal spec.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource