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

Validator Laravel Package

symfony/validator

The Symfony Validator component provides tools to validate values following the JSR-303 Bean Validation specification, with constraints, annotations, and attributes for expressive, reusable validation rules.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup in Laravel

  1. Installation:

    composer require symfony/validator
    

    Laravel pulls in Symfony components for several features, so the validator is usually already present in vendor/. Confirm with composer show symfony/validator before adding anything. You only need explicit requires for optional pieces such as symfony/expression-language (for the Expression constraint) or symfony/property-access (for getPropertyPath() on object graphs).

  2. Know Which Line You're On:

    composer show symfony/validator | grep -E '^versions'
    composer audit
    

    Symfony backports security fixes to every supported branch, so the same fix can ship as 5.4.x, 6.4.x, 7.x and 8.1.x. v5.4.54 is a security release (see Gotchas) — if composer show puts you on 5.4, treat that upgrade as mandatory, and if you're on 8.1.x confirm the equivalent patch is in your line before assuming you're covered.

  3. First Use Case: Validate a DTO or request payload in a Laravel controller:

    use Symfony\Component\Validator\Validator\ValidatorInterface;
    
    public function store(Request $request, ValidatorInterface $validator)
    {
        $data = $request->validate([
            'email' => 'required|email',
            'age' => 'integer|min:18',
        ]);
    
        $errors = $validator->validate($data);
        if (count($errors) > 0) {
            return response()->json(['errors' => (string) $errors], 400);
        }
    
        // Proceed with valid data
    }
    
  4. Key Classes to Know:

    • ValidatorInterface: Core validator service (injected via Laravel's container).
    • Constraint: Base class for custom validation rules.
    • ConstraintValidator: Validates a specific constraint.
    • ConstraintViolationListInterface: Holds validation results.
  5. Where to Look First:


Implementation Patterns

1. Form Request Validation (Laravel Integration)

Leverage Laravel's FormRequest to auto-validate with Symfony's constraints:

use Symfony\Component\Validator\Constraints as Assert;

class StoreUserRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'email' => ['required', 'email', new Assert\Email()],
        ];
    }

    public function authorize()
    {
        return true;
    }

    public function validated()
    {
        $violations = app(ValidatorInterface::class)->validate($this->all());

        if ($violations->count() > 0) {
            $messages = [];
            foreach ($violations as $violation) {
                $messages[$violation->getPropertyPath()][] = $violation->getMessage();
            }

            throw \Illuminate\Validation\ValidationException::withMessages($messages);
        }

        return parent::validated();
    }
}

2. Custom Constraints

Create reusable validation rules:

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

#[Attribute]
class ValidPassword extends Constraint
{
    public string $message = 'The password must contain at least one uppercase letter, one lowercase letter, and one number.';
}

class ValidPasswordValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/', $value)) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();
        }
    }
}

Usage:

use App\Validator\Constraints\ValidPassword;

#[ValidPassword]
public string $password;

3. Dynamic Validation Groups

Validate different subsets of data:

$validator = app(ValidatorInterface::class);
$violations = $validator->validate($user, null, ['registration']); // Only validate 'registration' group

4. Validation in Services

Reuse validation logic across services:

class UserService
{
    public function __construct(private ValidatorInterface $validator) {}

    public function createUser(array $data): void
    {
        $violations = $this->validator->validate($data);
        if ($violations->count() > 0) {
            throw new \RuntimeException((string) $violations);
        }
        // Save user
    }
}

5. API Response Handling

Format validation errors for JSON APIs:

$errors = $validator->validate($data);
return response()->json([
    'errors' => collect($errors)->map(fn ($error) => $error->getPropertyPath() . ': ' . $error->getMessage())
]);

6. Laravel Service Provider Configuration

Configure a validator built with explicit cache/metadata settings in AppServiceProvider:

use Symfony\Component\Validator\Validation;

public function register()
{
    $this->app->singleton(ValidatorInterface::class, function () {
        return Validation::createValidatorBuilder()
            ->enableAttributeMapping()
            ->setMappingCache(storage_path('framework/cache/validator'))
            ->getValidator();
    });
}

7. Pinning the Expression Capability

Because Expression pulls in a separate, security-sensitive parser, isolate it:

// config/validator.php or a bound constraint factory
'allow_expression_constraint' => env('VALIDATOR_ALLOW_EXPRESSION', false),

Gate dynamic-expression features behind a flag so a language-component security bump is a one-config-rollback away.


Gotchas and Tips

Pitfalls

  1. Expression Parsing Is Bounded as of 5.4.54 (Security Release):

    • v5.4.54 ships a single fix: "Bound the nesting level of parsed expressions" (#65690). The Expression constraint compiles its expression through symfony/expression-language, and deeply nested expressions can exhaust the stack. Two consequences:
      • Upgrade now if you're on the 5.4 line. Verify the same fix is present in whatever maintained line you actually run (6.4, 7.x, 8.1.x) — composer audit and the Symfony security advisories page are the source of truth.
      • Audit where your expressions come from. Constraint-level expressions are developer-authored, so the practical risk is low. The real exposure is any place you build expressions from user input — dynamic rule builders, admin-configurable filters, saved query definitions, per-tenant validation rules. Never concatenate request data into an expression string; whitelist operators/variables, or replace the dynamic path with a Callback constraint (plain PHP, no parser) which is both safer and easier to reason about.
    • If you cache compiled expressions or rulesets, flush them after upgrading so you're not serving pre-patch compiled artifacts.
  2. Circular References in Validation:

    • Avoid validating deeply nested objects with circular references (e.g., User->posts->author->user). Use @Assert\Valid sparingly or disable it for specific properties.
  3. Performance with Large Datasets:

    • Symfony Validator caches metadata by default (cache_dir in config). For dynamic validation rules, disable caching or use ValidatorBuilder to rebuild the validator:
      $validator = ValidatorBuilder::create()
          ->enableAnnotationMapping()
          ->getValidator();
      
  4. Constraint Overrides:

    • Extending constraints (e.g., #[Assert\Length(min: 5)]) may not work as expected if the parent constraint's options aren't properly inherited. Use #[Assert\Callback] for complex logic:
      #[Assert\Callback]
      public function validate($value, ExecutionContextInterface $context)
      {
          if (!str_contains($value, 'laravel')) {
              $context->buildViolation('Must contain "laravel".')
                  ->addViolation();
          }
      }
      
  5. Validation Groups in Laravel:

    • Laravel's FormRequest doesn't natively support Symfony's validation groups. Use a custom validator or manually pass groups:
      $validator = app(ValidatorInterface::class);
      $validator->validate($data, null, ['group1', 'group2']);
      
  6. Expression Language Quirks:

    • The Expression constraint (@Assert\Expression) requires the symfony/expression-language component. Install it separately:
      composer require symfony/expression-language
      
    • Keep symfony/expression-language pinned in lockstep with symfony/validator — the nesting bound lives in the parser, so a mismatched pair leaves you "on the patched validator" but still running an unpatched parser.
    • Example:
      #[Assert\Expression(
          expression: "this.getAge() >= 18",
          message: "You must be at least 18 years old."
      )]
      
  7. Type Safety:

    • Symfony Validator infers types loosely. For strict typing, use #[Assert\Type(type: 'string')] or #[Assert\Type(type: 'array')].
  8. Week Accepts a Blank String (as of 8.1.9):

    • Week now treats '' as valid, so an optional form field left empty will pass silently. If the field is genuinely required, pair it with #[Assert\NotBlank] or a required rule; if it's optional, you get free "empty means no value" behaviour. Review any existing tests that asserted a violation on an empty week value — those will now fail.
  9. Attribute Changes on Non-Public Members Require a Container/Cache Rebuild:

    • 8.1.9 fixes container rebuilding when attributes on private/protected members change. In practice this means: after moving or editing constraint attributes on non-public properties, run php artisan optimize:clear (or redeploy with a fresh build) rather than assuming the compiled container picks it up. Stale metadata caches are the most common cause of "my new constraint isn't firing" in production.

Debugging Tips

  1. Inspect Violations:

    $violations = $validator->validate($data);
    foreach ($violations as $violation) {
        dump([
            'property' => $violation->getPropertyPath(),
            'message' => $violation->getMessage(),
            'code' => $violation->getCode(),
        ]);
    }
    
  2. Enable Validation Tracing:

    • Wrap the validator in a decorator that logs validate() calls and timing, then bind it in a service provider. This gives you the "traceable validator" behaviour without relying on config keys Laravel doesn't ship.
  3. Confirm Which Version Is Actually Loaded:

    composer show symfony/validator symfony/expression-language
    
    • Multi-branch projects (a package on 5.4, an app on 8.1) are the classic way to believe you're patched when you're not. If you maintain a library, widen the constraint and let the consumer's branch decide — don't hard-pin to 5.4.
  4. Common Error Codes:

    • SYMFONY\Component\Validator\Constraints\Length: Use min/max in error messages:
      #[Assert\Length(
          min: 5,
          minMessage: 'This value should be at least {{ limit }} characters long.',
          max: 20,
          maxMessage: 'This value should be at most {{ limit }} characters long.'
      )]
      
  5. Translation Noise:

    • 8.1.9 is largely a translation sweep (Tagalog, Bulgarian, Greek, Urdu, Japanese, Turkish, Russian/Audio, Persian, Serbian) plus a fix for malformed message placeholders. If you override messages via translations/validators.<locale>.*, re-check your placeholders after upgrading — malformed {{ }} tokens were silently breaking interpolated values before this patch. Note this is orthogonal to the 5.4.54 security fix; if you jump branches, expect both changes at once.

Extension Points

  1. Custom Constraint Validators:

    • Implement ConstraintValidatorInterface for complex rules:
      class CustomValidator extends AbstractConstraintValidator
      {
          public function validate($value, Constraint $constraint)
          {
              if ($value !== $constraint->expected) {
                  $this->context->buildViolation($constraint->message)
                      ->setParameter('{{ value }}', $value)
                      ->addViolation();
              }
          }
      }
      
  2. Validation Event Listeners:

    • Subscribe to validation events via Symfony's event dispatcher (available in Laravel):
      $dispatcher = app(\Symfony\Component\EventDispatcher\EventDispatcherInterface::class);
      $dispatcher->addListener(ConstraintViolationListEvent::class, function ($event) {
          // Log violations or modify them
      });
      
  3. Override Default Constraints:

    • Replace built-in constraints (e.g., Email) by binding a custom validator in Laravel's service container:
      $this->app->bind(EmailValidator::class, function () {
          return new CustomEmailValidator();
      });
      
  4. Validation in Tests:

    • Use ValidatorInterface in PHPUnit tests:
      public function testValidation()
      {
          $validator = $this->app->make(ValidatorInterface::class);
          $violations = $validator->validate(['email' => 'invalid']);
          $this->assertCount(1, $violations);
      }
      
    • Add a regression test for any user-supplied expression path: feed it an absurdly nested expression and assert it's rejected rather than parsed.
  5. Laravel Policy Integration:

    • Combine Symfony Validator with Laravel Policies for authorization — validate the payload with constraints, then gate on ownership/intent with a policy so validation errors and 403s stay distinct in your API responses.
  6. CI Guard for Security Releases:

    • Add composer audit --locked --no-dev to CI and a scheduled weekly job. For a low-churn component like this, a single-fix security release (exactly what 5.4.54 is) is easy to miss between feature upgrades, and it's the kind of change that ships with no new API surface — nothing else in your pipeline will flag it.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package