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

Symfony Validator Laravel Package

jackai/symfony-validator

Integrates Symfony Validator with Laravel, letting you use Symfony constraints and validation rules in Laravel apps, reuse validator services, and share validation logic across Symfony and Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package gives you a typed, constraint-based validation layer on top of Laravel's native validator. As of v1.09 it explicitly supports PHP 8.0, so before anything else confirm your runtime and composer.json platform constraint match (php: ^8.0). If you are still on PHP 7.x, pin to the previous release.

  1. Install and discover the service provider. The package registers through Laravel's package auto-detection; no manual providers entry should be needed unless your app uses a custom bootstrap. Run php artisan vendor:publish --tag=<package>-config if a config file ships with it, then php artisan config:clear.
  2. Read the rule table first. The README's list of built-in/default types is your index of what you can use without writing code. Map each type to the Laravel rule you would otherwise reach for (in:, enum, typed scalar checks) so you know what to replace.
  3. Look at src/Constraints/ (or equivalent) for the base constraint class. This is the extension point introduced/expanded in this release. Understanding the constructor signature here is the difference between a five-minute rule and an afternoon of debugging.
  4. First use case — smallest possible win. Take one FormRequest and replace a hand-rolled Rule::in([...]) or a custom closure with the package's default type rule. Verify with a Pest/PHPUnit feature test that the validation message is what you expect before touching anything else.

Start with a single FormRequest, not a shared base request class, so you can compare native and package behaviour side by side.

Implementation Patterns

Default types in FormRequests. The new default types are designed to be dropped into the rules() array like any string rule. Prefer them over inline in: lists because they keep the set of allowed values in one place:

public function rules(): array
{
    return [
        'status'  => ['required', new OrderStatusType()],   // default type, no manual in: list
        'channel' => ['required', 'string', new SalesChannelType()],
    ];
}

Custom constraints with field-name injection. The headline feature in v1.09 is that a custom constraint receives the name of the field currently being validated. This removes the most common reason people abandon constraint classes and go back to closures — you no longer need to hardcode the attribute name or pass it through the constructor:

class UniqueWithinTenant extends Constraint
{
    public function __construct(protected string $fieldName) {}
    // $this->fieldName is resolved per-attribute, so one class
    // can serve 'email', 'phone', 'tax_id' without duplication
}

Register it once and reuse it across many attributes; the field name is supplied by the validator, not by you. This is the pattern to reach for whenever a rule's message or query depends on which attribute is being checked.

Two-tier validation strategy. Use the package's default types for the "shape" of a value (enumerations, typed identifiers) and reserve custom constraints for the "context" checks that need the field name or the request payload. Mixing both in one rules() array is the intended usage; do not try to express a cross-field rule as a default type.

Integration with Validator::make(). For non-HTTP validation (console commands, imports), the same constraints instantiate cleanly:

$validator = Validator::make($payload, [
    'role' => ['required', new UserRoleType()],
]);

Keep a single rules() definition in the FormRequest and call the FormRequest from the command if you can, rather than duplicating rule arrays.

Centralise messages. Pair each constraint with a messages() entry keyed by attribute.rule so your API error format stays stable when you swap a native rule for a package constraint. Attribute-name translation via attributes() matters more now that field names flow into constraints automatically.

Testing pattern. Extend your base TestCase with a helper that asserts a payload fails with a specific rule key. Because field names are injected, a test that passes email and one that passes phone through the same constraint class are both worth having — that is the whole point of the feature.

Gotchas and Tips

  • PHP 8.0 is now a real floor. "Support for PHP 8.0" can mean either "added" or "now requires." Check the composer.json require block before upgrading; if it moved to ^8.0, your CI matrix and any platform overrides in composer.json must move with it or composer update will silently resolve an older tag.
  • Constructor signature drift on custom constraints. Adding field-name injection changes how the base constraint is constructed. Any constraint you wrote against the previous version that passes its own attribute name will now receive two sources of truth and may double up or conflict. Audit existing custom constraints after the upgrade; the fix is usually deleting the now-redundant constructor argument.
  • Default types colliding with native rule names. If a default type's string alias matches a Laravel built-in (required, string, in), the native rule wins or the behaviour becomes ambiguous. Namespace or prefix your type names, and prefer object instances (new FooType()) over bare strings to make resolution explicit.
  • Config caching hides rule registration. After publishing config or registering new constraints, php artisan config:cache will freeze the old set. If a new default type "doesn't exist," clear config before debugging the class itself.
  • Message keys are the fastest debug lever. When a constraint fails unexpectedly, dump the failing rule key rather than the value — field-name injection means the same constraint can produce different messages per attribute, so a passing test on one field tells you nothing about another.
  • Attribute naming and nested fields. Injected field names for nested array validation (items.*.sku) arrive with the wildcard expanded or not depending on the validator path. Assert on the exact string in a test; do not assume items.0.sku and items.*.sku are interchangeable in your message templates.
  • Extension point for the future. The base constraint class is the stable surface. Keep your custom constraints thin — parse input, delegate the actual check to a small injectable service — so the next release's signature changes only touch the constraint, not your business logic.
  • Upgrade order. Bump PHP first, then the package, then run the full validation test suite. Validation regressions are quiet: requests keep succeeding, just with the wrong acceptance set.
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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect