Weave Code
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

Laravel Ruleset Validation Laravel Package

craftcms/laravel-ruleset-validation

Validate Laravel request data against Craft CMS field rulesets. Map Craft-style constraints (required, min/max, regex, etc.) into Laravel’s validator, keeping validation logic consistent between Craft and Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require craftcms/laravel-ruleset-validation
    

    No service provider registration needed (auto-discovery).

  2. First use case: Validate a DTO Create a data object with validation rules:

    // app/Data/CreateUserData.php
    use CraftCms\RulesetValidation\Attributes\Ruleset;
    use CraftCms\RulesetValidation\Concerns\HasRuleset;
    use CraftCms\RulesetValidation\Contracts\ValidatesWithRuleset;
    
    #[Ruleset(CreateUserRuleset::class)]
    class CreateUserData implements ValidatesWithRuleset
    {
        use HasRuleset;
    
        public function __construct(
            public string $name,
            public string $email,
        ) {}
    
        public function validationData(): array
        {
            return [
                'name'  => $this->name,
                'email' => $this->email,
            ];
        }
    }
    
  3. Define the ruleset:

    // app/Rulesets/CreateUserRuleset.php
    use CraftCms\RulesetValidation\Ruleset;
    
    class CreateUserRuleset extends Ruleset
    {
        public function rules(): array
        {
            return [
                'name'  => ['required', 'string', 'max:255'],
                'email' => ['required', 'email'],
            ];
        }
    }
    
  4. Validate in a controller:

    public function store(CreateUserData $data)
    {
        $validated = $data->ruleset->validate();
        // $validated contains ['name' => '...', 'email' => '...']
    }
    

Where to Look First

  • For DTO validation: Focus on ValidatesWithRuleset, HasRuleset, and #[Ruleset].
  • For request validation: Treat rulesets like FormRequest but without subclassing.
  • For dynamic rules: Explore Rule::when() and scenarios (useScenario()).
  • For debugging: Check getValidator() to inspect errors.

Implementation Patterns

1. DTO Validation Workflow

// 1. Define data object
class UpdateProfileData implements ValidatesWithRuleset
{
    use HasRuleset;

    public function __construct(
        public ?string $name,
        public ?string $email,
    ) {}

    public function validationData(): array
    {
        return [
            'name'  => $this->name,
            'email' => $this->email,
        ];
    }
}

// 2. Attach ruleset (via attribute or method)
#[Ruleset(UpdateProfileRuleset::class)]
class UpdateProfileData { ... }

// 3. Validate in controller/service
public function update(UpdateProfileData $data)
{
    $validated = $data->ruleset->validate();
    // Use $validated['name'] or $validated['email']
}

2. Request Validation Without FormRequest

// 1. Define ruleset
class StorePostRuleset extends Ruleset
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['nullable', 'string'],
        ];
    }
}

// 2. Inject into controller (Laravel resolves it)
public function store(StorePostRuleset $ruleset)
{
    $validated = $ruleset->validate();
    // $validated is the cleaned input
}

3. Dynamic Rules with Scenarios

class UserRuleset extends Ruleset
{
    public const SCENARIO_ADMIN = 'admin';

    public function rules(): array
    {
        return [
            'email' => [
                'required',
                Rule::unique('users')->ignore($this->subject->id),
                Rule::when(
                    $this->inScenarios(self::SCENARIO_ADMIN),
                    fn() => Rule::exists('admin_users')
                ),
            ],
        ];
    }
}

// Usage
$userRuleset = new UserRuleset($user);
$validated = $userRuleset
    ->useScenario(UserRuleset::SCENARIO_ADMIN)
    ->validate();

4. Partial Validation

// Validate only 'email' field
$emailValidated = $data->ruleset
    ->only(['email'])
    ->validate();

// Non-throwing validation
if ($data->ruleset->only(['email'])->fails()) {
    $errors = $data->ruleset->getValidator()->errors();
}

5. Integration with Laravel Features

  • Authorization: Override authorize() in Ruleset:
    public function authorize(): bool
    {
        return auth()->check();
    }
    
  • Custom Messages: Use messages():
    public function messages(): array
    {
        return [
            'email.required' => 'We need your email to proceed.',
        ];
    }
    
  • Precognition: Use #[StopOnFirstFailure] or #[RedirectTo] attributes (same as FormRequest).

Gotchas and Tips

Pitfalls

  1. Ruleset Resolution Conflicts

    • If a class has both a #[Ruleset] attribute and a ruleset() method, the method takes precedence (fixed in v1.0.1).
    • Fix: Remove the attribute if dynamic ruleset selection is needed.
  2. Scenario State Leaks

    • useScenario() persists across method calls. Use withScenario() for temporary changes:
      $validated = $ruleset->withScenario(
          'admin',
          fn() => $ruleset->validate()
      );
      
  3. Request vs. Object Validation Quirks

    • For request-backed rulesets, subject is the Request object.
    • For object-backed rulesets, subject is the validated object (e.g., CreateUserData).
    • Tip: Access the subject via $this->subject in rules:
      'email' => [
          Rule::unique('users')->ignore($this->subject->id),
      ],
      
  4. Validation Data Mismatch

    • Ensure validationData() returns keys that match your rules() array. Typos here cause silent failures.
    • Debug: Use $ruleset->getValidator()->validate() to inspect raw data.
  5. Serialization Issues

    • Rulesets are not serializable by default (fixed in v1.0.2). Avoid passing them to queues/jobs directly.

Debugging Tips

  • Inspect the validator:
    $validator = $data->ruleset->getValidator();
    dd($validator->errors()); // Debug errors
    dd($validator->validated()); // Debug validated data
    
  • Validate manually:
    $validator = $data->ruleset->validator();
    if ($validator->fails()) {
        // Handle errors
    }
    
  • Check scenario:
    $currentScenario = $data->ruleset->getScenario();
    

Extension Points

  1. Custom Validation Logic Override prepareForValidation() to modify data before validation:

    public function prepareForValidation(): void
    {
        $this->data['full_name'] = strtoupper($this->data['name']);
    }
    
  2. Post-Validation Hooks Use after() (like FormRequest):

    protected function after(): void
    {
        $this->data['processed_at'] = now();
    }
    
  3. Conditional Rules Leverage Rule::when() or scenarios for dynamic validation:

    'password' => [
        'required_if' => 'is_new_user',
        Rule::when(
            fn() => $this->inScenarios('admin'),
            fn() => Rule::min(12)
        ),
    ],
    
  4. Testing Mock rulesets in tests:

    $ruleset = $this->createMock(Ruleset::class);
    $ruleset->method('validate')->willReturn(['key' => 'value']);
    $this->app->instance(Ruleset::class, $ruleset);
    

Performance Considerations

  • Avoid over-scoping: Chaining only() multiple times creates new validator instances.
  • Reuse rulesets: Instantiate rulesets once (e.g., in a service container) and reuse them.
  • Lazy validation: For non-critical paths, use passes()/fails() instead of validate() to avoid throwing exceptions.

Configuration Quirks

  • No global config: The package uses Laravel’s native validation system (no extra config files).
  • Attribute parsing: Ensure #[Ruleset] attributes are parsed by Laravel (works out-of-the-box in Laravel 8+).
  • Namespace collisions: Prefix custom ruleset classes (e.g., App\Rulesets\) to avoid conflicts.
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.
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager