sparkling/vat-bundle
sparkling/vat-bundle is a Symfony bundle for validating EU VAT numbers through the VIES service, with ready-to-use validators and services that make it easy to add VAT checks to checkout, onboarding, or other PHP application workflows.
The first thing to understand is the mismatch: sparkling/vat-bundle is a Symfony 2 bundle (archived since 2018), and its only real job is validating European VAT numbers against VIES (the EU's VAT Information Exchange System). There is no Laravel service provider, no facade, and nothing that will auto-discover. Treat it as a small library you wire up yourself.
Minimal steps:
composer require sparkling/vat-bundle
Run it against your PHP version before committing. The package predates PHP 7.2+, so expect deprecation noise or a hard failure on PHP 8. If it won't install, stop here and use a maintained alternative (see Gotchas) — do not vendor-patch it in.Ruudk\VATBundle\Validator\Constraints\VATNumber) and its validator class. They tell you the exact contract — what input it expects (a normalized number vs. a country code + number pair) and what it considers "valid."A pragmatic Laravel wiring if you keep the bundle:
// app/Services/Vat/VatValidator.php
use Ruudk\VATBundle\Validator\Constraints\VATNumber;
class VatValidator
{
public function isValid(string $number, string $country): bool
{
$constraint = new VATNumber(['country' => $country]);
// Instantiate the bundle's validator with its dependencies manually,
// or call the underlying VIES client directly.
}
}
If instantiation is painful (it usually is for a Symfony 2 bundle), skip the constraint entirely and reach into the client class — that is the pragmatic day-to-day move.
1. Constraint-on-entity (native Symfony style). If you are genuinely on an old Symfony app, this is the intended usage — annotate the property and let the validator run:
use Ruudk\VATBundle\Validator\Constraints as VatAssert;
class Company
{
/** @VatAssert\VATNumber */
protected $vatNumber;
}
2. Adapter into Laravel validation. The most useful pattern: wrap the bundle in a custom rule so the rest of your app never sees it.
class ValidVatNumber implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
[$country, $number] = $this->split($value); // "NL123456789B01" -> NL + rest
if (! app(VatValidator::class)->isValid($number, $country)) {
$fail('The VAT number could not be verified with VIES.');
}
}
}
Use it as ['required', new ValidVatNumber] on the billing form.
3. Cache aggressively, keyed by normalized number. VIES is a slow, rate-limited SOAP service. Every check should go through:
return Cache::remember("vies:{$country}:{$number}", now()->addDay(), function () use (...) {
return $this->client->check($number, $country);
});
Cache positive and negative results, but give negatives a shorter TTL (an hour vs. a day) — a number can become valid after registration.
4. Async / soft-validation at checkout. Do not block a checkout on a live VIES call. Pattern: accept the order, queue a VerifyVatNumber job, and mark the invoice as "VAT pending" until verified. This avoids losing sales to VIES downtime.
5. Normalize before validating. EU VAT numbers arrive with spaces, dots, and dashes. Strip everything non-alphanumeric and uppercase before hitting the client, and store the normalized form alongside the raw input.
6. Test with a fake client. Bind a stub implementation of the client in your test container so tests never touch the network; assert the validator rejects invalid/malformed input and that caching works.
composer why after install, and budget a migration path away from it.SoapFault, and treat errors as "unknown" — never as "invalid." Rejecting a legitimate business because VIES was down is a real support-ticket generator.VATNumber constraint name collides conceptually with Symfony's own VatNumber/other packages. Be explicit with aliases when annotating.dragonbe/vies-style client wrapped in your own VatValidator service; you'll get PHP 8 support, testability, and no dead-bundle risk — usually in under an hour.How can I help you explore Laravel packages today?