# Search Results

## Components Pagination > Large result set
Components Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux

Large result set

When working with large result sets, the pagination component automatically adapts to show a reasonable number of page links. It shows the first and last pages, along with a window of pages around the current page, and adds ellipses for any gaps to ensure efficient navigation through numerous pages.

<flux:pagination :paginator="$orders" />

---

## Testing See also
Testing | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing

## See also

- **[Actions](/docs/4.x/actions)** — Test component actions and interactions
- **[Forms](/docs/4.x/forms)** — Test form submissions and validation
- **[Events](/docs/4.x/events)** — Test event dispatching and listening
- **[Components](/docs/4.x/components)** — Create testable component structure

---

## Components Radio > flux:radio
Components Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux

flux:radio

<ui-table-scroll-area>

Prop

Description

    label

Label text displayed above the radio button. When provided, wraps the radio button in a flux:field component with an adjacent flux:label component. See the field component.

    description

Help text displayed below the radio button. When provided alongside label, appears between the label and radio button within the flux:field wrapper. See the field component.

    value

Value associated with the radio button when used in a group.

    checked

If true, the radio button is selected by default.

    disabled

Prevents user interaction with the radio button.

    icon

Name of the icon to display (for segmented variant).

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

    default

Custom content for card variant.

</ui-table-scroll-area>

<ui-table-scroll-area>

Attribute

Description

    data-flux-radio

Applied to the root element for styling and identification.

    data-checked

Applied when the radio button is selected.

</ui-table-scroll-area>

---

## Http Tests Validation Assertions assertInvalid
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertInvalid

Assert that the response has validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session:

```php
$response->assertInvalid(['name', 'email']);
```

You may also assert that a given key has a particular validation error message. When doing so, you may provide the entire message or only a small portion of the message:

```php
$response->assertInvalid([
    'name' => 'The name field is required.',
    'email' => 'valid email address',
]);
```

---

## Components Radio > Buttons
Components Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux

Buttons

Button-style radio options that look like a toolbar. Perfect for action selections and grouped controls where you want a more prominent visual style.

<flux:radio.group variant="buttons" class="w-full *:flex-1" label="Feedback type">    <flux:radio icon="bug-ant" checked>Bug report</flux:radio>    <flux:radio icon="light-bulb">Suggestion</flux:radio>    <flux:radio icon="question-mark-circle">Question</flux:radio></flux:radio.group>

---

## Attribute Validate
Attribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate

The `#[Validate]` attribute associates validation rules with component properties, enabling automatic real-time validation and clean rule declaration.

---

## Redirecting Redirecting to full-page components
Redirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting

## Redirecting to full-page components

Because Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.

For example, if you are using a Livewire component as a full-page component for a route like so:

```php
Route::livewire('/posts', 'pages::show-posts');
```

You can redirect to it simply by using the route path:

```php
public function save()
{
    // ...

    $this->redirect('/posts');
}
```

---

## Quickstart Test it out
Quickstart | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/quickstart

## Test it out

With everything in place, let's test the component!

Start your Laravel development server if it's not already running:

```shell
php artisan serve
```

Visit `http://localhost:8000/post/create` in your browser (or `http://yourapp.test/post/create` if using Valet, Herd, or a similar tool).

You should see a simple form with two fields and a submit button.

**Try the following:**

1. **Test validation:** Click "Save Post" without filling in any fields. You'll see red error messages appear instantly below each field—no page reload required.

2. **Test submission:** Fill in both fields and click "Save Post". You should see a debug screen showing the values you entered.

This demonstrates Livewire's core power: reactive data binding, real-time validation, and form handling—all written in PHP without touching JavaScript.

---

## Lazy Full-page lazy loading
Lazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy

## Full-page lazy loading

You can lazy load or defer full-page Livewire components using route methods.

### Lazy loading full pages

Use `->lazy()` to load the component when it enters the viewport:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy();
```

### Deferring full pages

Use `->defer()` to load the component immediately after the page loads:

```php
Route::livewire('/dashboard', 'pages::dashboard')->defer();
```

### Disabling lazy/defer loading

If a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);
Route::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);
```

---

## Validation See also
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## See also

- **[Forms](/docs/4.x/forms)** — Validate form inputs with real-time feedback
- **[Properties](/docs/4.x/properties)** — Validate property values before persisting
- **[Validate Attribute](/docs/4.x/attribute-validate)** — Use #[Validate] for property validation
- **[Actions](/docs/4.x/actions)** — Validate data in action methods

---

## Components Checkbox > Buttons
Components Checkbox | livewire/flux@2.x | Source: https://fluxui.dev/components/checkbox#livewire/flux

Buttons

Button-style checkbox options that look like a toolbar. Perfect for toggle controls and feature selections where you want a more prominent visual style.

<flux:checkbox.group wire:model="features" label="Features" variant="buttons">    <flux:checkbox value="notifications" icon="bell" label="Notifications" />    <flux:checkbox value="analytics" icon="chart-bar" label="Analytics" />    <flux:checkbox value="backups" icon="cloud-arrow-up" label="Backups" /></flux:checkbox.group>

---

## Validation Available Validation Rules enum
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### enum

The `Enum` rule is a class-based rule that validates whether the field under validation contains a valid enum value. The `Enum` rule accepts the name of the enum as its only constructor argument. When validating primitive values, a backed Enum should be provided to the `Enum` rule:

```php
use App\Enums\ServerStatus;
use Illuminate\Validation\Rule;

$request->validate([
    'status' => [Rule::enum(ServerStatus::class)],
]);
```

The `Enum` rule's `only` and `except` methods may be used to limit which enum cases should be considered valid:

```php
Rule::enum(ServerStatus::class)
    ->only([ServerStatus::Pending, ServerStatus::Active]);

Rule::enum(ServerStatus::class)
    ->except([ServerStatus::Pending, ServerStatus::Active]);
```

The `when` method may be used to conditionally modify the `Enum` rule:

```php
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;

Rule::enum(ServerStatus::class)
    ->when(
        Auth::user()->isAdmin(),
        fn ($rule) => $rule->only(...),
        fn ($rule) => $rule->only(...),
    );
```

---

## Attribute Title Only for full-page components
Attribute Title | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-title

## Only for full-page components

> [!info] Full-page components only
> The `#[Title]` attribute only works for full-page components accessed via routes. Regular components rendered within other views don't use titles—they inherit the parent page's title.

---

## Quickstart Write the component
Quickstart | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/quickstart

## Write the component

Open `resources/views/pages/post/⚡create.blade.php` and replace its contents with the following:

```blade
<?php

use Livewire\Component;

new class extends Component {
    public string $title = '';

    public string $content = '';

    public function save()
    {
        $this->validate([
            'title' => 'required|max:255',
            'content' => 'required',
        ]);

        dd($this->title, $this->content);
    }
};
?>

<form wire:submit="save">
    <label>
        Title
        <input type="text" wire:model="title">
        @error('title') <span style="color: red;">{{ $message }}</span> @enderror
    </label>

    <label>
        Content
        <textarea wire:model="content" rows="5"></textarea>
        @error('content') <span style="color: red;">{{ $message }}</span> @enderror
    </label>

    <button type="submit">Save Post</button>
</form>
```

> [!info] Don't worry about styling
> This form is intentionally unstyled so we can focus on Livewire's functionality. In a real application, you'd add CSS or use a framework like Tailwind.

Here's what's happening in the code above:

**Component properties:**
- `public string $title = '';` — Declares a public property for the post title
- `public string $content = '';` — Declares a public property for the post content

**Component methods:**
- `public function save()` — Called when the form is submitted. Validates the data and dumps the output for testing.

**Livewire directives:**
- `wire:submit="save"` — Calls the `save()` method when the form is submitted, preventing the default page reload
- `wire:model="title"` — Creates two-way data binding between the input and the `$title` property. As you type, the property updates automatically
- `wire:model="content"` — Same two-way binding for the textarea and `$content` property
- `@error('title')` and `@error('content')` — Display validation error messages when validation fails

> [!warning] Livewire components MUST have a single root element
> Components must have exactly one root HTML element. In this example, the `<form>` element is the single root. Multiple root elements or HTML comments outside the root element will cause an error. When rendering [full-page components](/docs/4.x/pages), named slots for the layout can be placed outside the root element.

> [!tip] In a real application
> The `save()` method uses `dd()` to dump the values for testing purposes. In a production application, you would typically save the data to a database and redirect:
> ```php
> public function save()
> {
>     $validated = $this->validate([
>         'title' => 'required|max:255',
>         'content' => 'required',
>     ]);
>
>     Post::create($validated); // Assumes you have a Post model and database table
>
>     return $this->redirect('/posts');
> }
> ```

---

## Validation Deprecated `[#Rule]` attribute
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## Deprecated `[#Rule]` attribute

When Livewire v3 first launched, it used the term "Rule" instead of "Validate" for it's validation attributes (`#[Rule]`).

Because of naming conflicts with Laravel rule objects, this has since been changed to `#[Validate]`. Both are supported in Livewire v3, however it is recommended that you change all occurrences of `#[Rule]` with `#[Validate]` to stay current.

---

## Components Input > With buttons
Components Input | livewire/flux@2.x | Source: https://fluxui.dev/components/input#livewire/flux

With buttons

Attach buttons to the beginning or end of an input element.

<flux:input.group>    <flux:input placeholder="Post title" />    <flux:button icon="plus">New post</flux:button></flux:input.group><flux:input.group>    <flux:select class="max-w-fit">        <flux:select.option selected>USD</flux:select.option>            </flux:select>    <flux:input placeholder="$99.99" /></flux:input.group>

---

## Components Select > Checkbox indicator
Components Select | livewire/flux@2.x | Source: https://fluxui.dev/components/select#livewire/flux

            Checkbox indicator

If you prefer a checkbox indicator instead of the default checkmark icon, you can use the indicator="checkbox" prop.

<flux:select variant="listbox" indicator="checkbox" multiple>    ...</flux:select>

---

## Components Button > Full width
Components Button | livewire/flux@2.x | Source: https://fluxui.dev/components/button#livewire/flux

Full width

A button that spans the full width of the container.

<flux:button variant="primary" class="w-full">Send invite</flux:button>

---

## Attribute Validate Example: Contact form
Attribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate

## Example: Contact form

Here's a complete contact form with validation:

```php
<?php // resources/views/pages/⚡contact.blade.php

use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Mail\ContactMessage;
use Illuminate\Support\Facades\Mail;

new class extends Component {
    #[Validate('required|min:2', as: 'name')]
    public $name = '';

    #[Validate('required|email')]
    public $email = '';

    #[Validate('required')]
    public $subject = '';

    #[Validate('required|min:10', as: 'message')]
    public $message = '';

    public function submit()
    {
        $validated = $this->validate();

        Mail::to('support@example.com')->send(new ContactMessage($validated));

        session()->flash('success', 'Message sent successfully!');

        $this->reset();
    }
};
?>

<div>
    @if (session('success'))
        <div class="alert">{{ session('success') }}</div>
    @endif

    <form wire:submit="submit">
        <div>
            <input type="text" wire:model.live.blur="name" placeholder="Your name">
            @error('name') <span class="error">{{ $message }}</span> @enderror
        </div>

        <div>
            <input type="email" wire:model.live.blur="email" placeholder="Your email">
            @error('email') <span class="error">{{ $message }}</span> @enderror
        </div>

        <div>
            <input type="text" wire:model.live.blur="subject" placeholder="Subject">
            @error('subject') <span class="error">{{ $message }}</span> @enderror
        </div>

        <div>
            <textarea wire:model.live.blur="message" placeholder="Your message"></textarea>
            @error('message') <span class="error">{{ $message }}</span> @enderror
        </div>

        <button type="submit">Send Message</button>
    </form>
</div>
```

Users get immediate feedback as they fill out the form, with friendly field names and helpful error messages.

---

## Components Radio > Segmented with icons
Components Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux

Segmented with icons

Combine segmented radio buttons with icon prefixes.

<flux:radio.group wire:model="role" variant="segmented">    <flux:radio label="Admin" icon="wrench" />    <flux:radio label="Editor" icon="pencil-square" />    <flux:radio label="Viewer" icon="eye" /></flux:radio.group>

---

## Validation Accessing errors in JavaScript
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## Accessing errors in JavaScript

Livewire provides a `$errors` magic property for client-side access to validation errors:

```blade
<form wire:submit="save">
    <input type="email" wire:model="email">

    <div wire:show="$errors.has('email')">
        <span wire:text="$errors.first('email')"></span>
    </div>

    <button type="submit">Save</button>
</form>
```

### Available methods

- `$errors.has('field')` - Check if a field has errors
- `$errors.missing('field')` - Check if a field doesn't have errors
- `$errors.first('field')` - Get the first error message for a field
- `$errors.get('field')` - Get all error messages for a field
- `$errors.all()` - Get all errors for all fields
- `$errors.clear()` - Clear all errors
- `$errors.clear('field')` - Clear errors for a specific field

When using Alpine.js, access `$errors` through `$wire.$errors`.

---

## Validation Form objects
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## Form objects

As more properties and validation rules are added to a Livewire component, it can begin to feel too crowded. To alleviate this pain and also provide a helpful abstraction for code reuse, you can use Livewire's *Form Objects* to store your properties and validation rules.

Below is the same `CreatePost` example, but now the properties and rules have been extracted to a dedicated form object named `PostForm`:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:3')]
	public $title = '';

    #[Validate('required|min:3')]
    public $content = '';
}
```

The `PostForm` above can now be defined as a property on the `CreatePost` component:

```php
<?php

namespace App\Livewire;

use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;

class CreatePost extends Component
{
    public PostForm $form;

    public function save()
    {
		Post::create(
    		$this->form->all()
    	);

		return redirect()->to('/posts');
    }

    // ...
}
```

As you can see, instead of listing out each property individually, we can retrieve all the property values using the `->all()` method on the form object.

Also, when referencing the property names in the template, you must prepend `form.` to each instance:

```blade
<form wire:submit="save">
	<input type="text" wire:model="form.title">
    <div>@error('form.title') {{ $message }} @enderror</div>

	<textarea wire:model="form.content"></textarea>
    <div>@error('form.content') {{ $message }} @enderror</div>

	<button type="submit">Save</button>
</form>
```

When using form objects, `#[Validate]` attribute validation will be run every time a property is updated. However, if you disable this behavior by specifying `onUpdate: false` on the attribute, you can manually run a form object's validation using `$this->form->validate()`:

```php
public function save()
{
    Post::create(
        $this->form->validate()
    );

    return redirect()->to('/posts');
}
```

Form objects are a useful abstraction for most larger datasets and a variety of additional features that make them even more powerful. For more information, check out the comprehensive [form object documentation](/docs/4.x/forms#extracting-a-form-object).

---

## Validation Available Validation Rules unique:_table_,_column_
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### unique:_table_,_column_

The field under validation must not exist within the given database table.

**Specifying a Custom Table / Column Name:**

Instead of specifying the table name directly, you may specify the Eloquent model which should be used to determine the table name:

```php
'email' => 'unique:App\Models\User,email_address'
```

The `column` option may be used to specify the field's corresponding database column. If the `column` option is not specified, the name of the field under validation will be used.

```php
'email' => 'unique:users,email_address'
```

**Specifying a Custom Database Connection**

Occasionally, you may need to set a custom connection for database queries made by the Validator. To accomplish this, you may prepend the connection name to the table name:

```php
'email' => 'unique:connection.users,email_address'
```

**Forcing a Unique Rule to Ignore a Given ID:**

Sometimes, you may wish to ignore a given ID during unique validation. For example, consider an "update profile" screen that includes the user's name, email address, and location. You will probably want to verify that the email address is unique. However, if the user only changes the name field and not the email field, you do not want a validation error to be thrown because the user is already the owner of the email address in question.

To instruct the validator to ignore the user's ID, we'll use the `Rule` class to fluently define the rule. In this example, we'll also specify the validation rules as an array instead of using the `|` character to delimit the rules:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'email' => [
        'required',
        Rule::unique('users')->ignore($user->id),
    ],
]);
```

> [!WARNING]
> You should never pass any user controlled request input into the `ignore` method. Instead, you should only pass a system generated unique ID such as an auto-incrementing ID or UUID from an Eloquent model instance. Otherwise, your application will be vulnerable to an SQL injection attack.

Instead of passing the model key's value to the `ignore` method, you may also pass the entire model instance. Laravel will automatically extract the key from the model:

```php
Rule::unique('users')->ignore($user)
```

If your table uses a primary key column name other than `id`, you may specify the name of the column when calling the `ignore` method:

```php
Rule::unique('users')->ignore($user->id, 'user_id')
```

By default, the `unique` rule will check the uniqueness of the column matching the name of the attribute being validated. However, you may pass a different column name as the second argument to the `unique` method:

```php
Rule::unique('users', 'email_address')->ignore($user->id)
```

**Adding Additional Where Clauses:**

You may specify additional query conditions by customizing the query using the `where` method. For example, let's add a query condition that scopes the query to only search records that have an `account_id` column value of `1`:

```php
'email' => Rule::unique('users')->where(fn (Builder $query) => $query->where('account_id', 1))
```

**Ignoring Soft Deleted Records in Unique Checks:**

By default, the unique rule includes soft deleted records when determining uniqueness. To exclude soft deleted records from the uniqueness check, you may invoke the `withoutTrashed` method:

```php
Rule::unique('users')->withoutTrashed();
```

If your model uses a column name other than `deleted_at` for soft deleted records, you may provide the column name when invoking the `withoutTrashed` method:

```php
Rule::unique('users')->withoutTrashed('was_deleted_at');
```

---

## Search Full-Text Search Running Full-Text Queries
Search | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/search.md

### Running Full-Text Queries

Once the index is in place, use the `whereFullText` query builder method to search against it. Laravel will generate the appropriate SQL for your database driver — for example, `MATCH(...) AGAINST(...)` on MariaDB and MySQL, and `to_tsvector(...) @@ plainto_tsquery(...)` on PostgreSQL:

```php
$articles = Article::whereFullText('body', 'web developer')->get();
```

When using MariaDB and MySQL, results are automatically ordered by relevance score. On PostgreSQL, `whereFullText` filters matching records but does not order them by relevance — if you need automatic relevance ordering on PostgreSQL, consider using [Scout's database engine](#database-engine), which handles this for you.

If you created a composite full-text index across multiple columns, you may search against all of them by passing the same array of columns to `whereFullText`:

```php
$articles = Article::whereFullText(
    ['title', 'body'], 'web developer'
)->get();
```

The `orWhereFullText` method may be used to add a full-text search clause as an "or" condition. For complete details, consult the [query builder documentation](/docs/{{version}}/queries#full-text-where-clauses).

---

## Components Field > With badge
Components Field | livewire/flux@2.x | Source: https://fluxui.dev/components/field#livewire/flux

With badge

Badges allow you to enhance a field with additional information such as being "required" or "optional" when it might not be expected.

<flux:field>    <flux:label badge="Required">Email</flux:label>    <flux:input type="email" required />    <flux:error name="email" /></flux:field><flux:field>    <flux:label badge="Optional">Phone number</flux:label>    <flux:input type="phone" placeholder="(555) 555-5555" mask="(999) 999-9999"  />    <flux:error name="phone" /></flux:field>

---

## Forms See also
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## See also

- **[Validation](/docs/4.x/validation)** — Validate form inputs with real-time feedback
- **[wire:model](/docs/4.x/wire-model)** — Bind form inputs to component properties
- **[File Uploads](/docs/4.x/uploads)** — Handle file uploads in forms
- **[Actions](/docs/4.x/actions)** — Process form submissions with actions
- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during form submission

---

## Validation Testing validation
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## Testing validation

Livewire provides useful testing utilities for validation scenarios, such as the `assertHasErrors()` method.

Below is a basic test case that ensures validation errors are thrown if no input is set for the `title` property:

```php
<?php

namespace Tests\Feature\Livewire;

use App\Livewire\CreatePost;
use Livewire\Livewire;
use Tests\TestCase;

class CreatePostTest extends TestCase
{
    public function test_cant_create_post_without_title()
    {
        Livewire::test(CreatePost::class)
            ->set('content', 'Sample content...')
            ->call('save')
            ->assertHasErrors('title');
    }
}
```

In addition to testing the presence of errors, `assertHasErrors` allows you to also narrow down the assertion to specific rules by passing the rules to assert against as the second argument to the method:

```php
public function test_cant_create_post_with_title_shorter_than_3_characters()
{
    Livewire::test(CreatePost::class)
        ->set('title', 'Sa')
        ->set('content', 'Sample content...')
        ->call('save')
        ->assertHasErrors(['title' => ['min:3']]);
}
```

You can also assert the presence of validation errors for multiple properties at the same time:

```php
public function test_cant_create_post_without_title_and_content()
{
    Livewire::test(CreatePost::class)
        ->call('save')
        ->assertHasErrors(['title', 'content']);
}
```

For more information on other testing utilities provided by Livewire, check out the [testing documentation](/docs/4.x/testing).

_[Truncated]_


Output:
Wall time: 4.0855 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Components Pagination > Large result set\nComponents Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux\n\n
Large result set

When working with large result sets, the pagination component automatically adapts to show a reasonable number of page links. It shows the first and last pages, along with a window of pages around the current page, and adds ellipses for any gaps to ensure efficient navigation through numerous pages.

<flux:pagination :paginator=\"$orders\" />

\n\n---\n\n## Testing See also\nTesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing\n\n## See also\n\n- **[Actions](/docs/4.x/actions)** — Test component actions and interactions\n- **[Forms](/docs/4.x/forms)** — Test form submissions and validation\n- **[Events](/docs/4.x/events)** — Test event dispatching and listening\n- **[Components](/docs/4.x/components)** — Create testable component structure\n\n---\n\n## Components Radio > flux:radio\nComponents Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux\n\n
flux:radio

<ui-table-scroll-area>

Prop

Description

\n    label\n

Label text displayed above the radio button. When provided, wraps the radio button in a flux:field component with an adjacent flux:label component. See the field component.

\n    description\n

Help text displayed below the radio button. When provided alongside label, appears between the label and radio button within the flux:field wrapper. See the field component.

\n    value\n

Value associated with the radio button when used in a group.

\n    checked\n

If true, the radio button is selected by default.

\n    disabled\n

Prevents user interaction with the radio button.

\n    icon\n

Name of the icon to display (for segmented variant).

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

\n    default\n

Custom content for card variant.

</ui-table-scroll-area>

<ui-table-scroll-area>

Attribute

Description

\n    data-flux-radio\n

Applied to the root element for styling and identification.

\n    data-checked\n

Applied when the radio button is selected.

</ui-table-scroll-area>

\n\n---\n\n## Http Tests Validation Assertions assertInvalid\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertInvalid\n\nAssert that the response has validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session:\n\n```php\n$response->assertInvalid(['name', 'email']);\n```\n\nYou may also assert that a given key has a particular validation error message. When doing so, you may provide the entire message or only a small portion of the message:\n\n```php\n$response->assertInvalid([\n    'name' => 'The name field is required.',\n    'email' => 'valid email address',\n]);\n```\n\n---\n\n## Components Radio > Buttons\nComponents Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux\n\n
Buttons

Button-style radio options that look like a toolbar. Perfect for action selections and grouped controls where you want a more prominent visual style.

<flux:radio.group variant=\"buttons\" class=\"w-full *:flex-1\" label=\"Feedback type\">    <flux:radio icon=\"bug-ant\" checked>Bug report</flux:radio>    <flux:radio icon=\"light-bulb\">Suggestion</flux:radio>    <flux:radio icon=\"question-mark-circle\">Question</flux:radio></flux:radio.group>

\n\n---\n\n## Attribute Validate\nAttribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate\n\nThe `#[Validate]` attribute associates validation rules with component properties, enabling automatic real-time validation and clean rule declaration.\n\n---\n\n## Redirecting Redirecting to full-page components\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## Redirecting to full-page components\n\nBecause Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.\n\nFor example, if you are using a Livewire component as a full-page component for a route like so:\n\n```php\nRoute::livewire('/posts', 'pages::show-posts');\n```\n\nYou can redirect to it simply by using the route path:\n\n```php\npublic function save()\n{\n    // ...\n\n    $this->redirect('/posts');\n}\n```\n\n---\n\n## Quickstart Test it out\nQuickstart | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/quickstart\n\n## Test it out\n\nWith everything in place, let's test the component!\n\nStart your Laravel development server if it's not already running:\n\n```shell\nphp artisan serve\n```\n\nVisit `http://localhost:8000/post/create` in your browser (or `http://yourapp.test/post/create` if using Valet, Herd, or a similar tool).\n\nYou should see a simple form with two fields and a submit button.\n\n**Try the following:**\n\n1. **Test validation:** Click \"Save Post\" without filling in any fields. You'll see red error messages appear instantly below each field—no page reload required.\n\n2. **Test submission:** Fill in both fields and click \"Save Post\". You should see a debug screen showing the values you entered.\n\nThis demonstrates Livewire's core power: reactive data binding, real-time validation, and form handling—all written in PHP without touching JavaScript.\n\n---\n\n## Lazy Full-page lazy loading\nLazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy\n\n## Full-page lazy loading\n\nYou can lazy load or defer full-page Livewire components using route methods.\n\n### Lazy loading full pages\n\nUse `->lazy()` to load the component when it enters the viewport:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy();\n```\n\n### Deferring full pages\n\nUse `->defer()` to load the component immediately after the page loads:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->defer();\n```\n\n### Disabling lazy/defer loading\n\nIf a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);\nRoute::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);\n```\n\n---\n\n## Validation See also\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## See also\n\n- **[Forms](/docs/4.x/forms)** — Validate form inputs with real-time feedback\n- **[Properties](/docs/4.x/properties)** — Validate property values before persisting\n- **[Validate Attribute](/docs/4.x/attribute-validate)** — Use #[Validate] for property validation\n- **[Actions](/docs/4.x/actions)** — Validate data in action methods\n\n---\n\n## Components Checkbox > Buttons\nComponents Checkbox | livewire/flux@2.x | Source: https://fluxui.dev/components/checkbox#livewire/flux\n\n
Buttons

Button-style checkbox options that look like a toolbar. Perfect for toggle controls and feature selections where you want a more prominent visual style.

<flux:checkbox.group wire:model=\"features\" label=\"Features\" variant=\"buttons\">    <flux:checkbox value=\"notifications\" icon=\"bell\" label=\"Notifications\" />    <flux:checkbox value=\"analytics\" icon=\"chart-bar\" label=\"Analytics\" />    <flux:checkbox value=\"backups\" icon=\"cloud-arrow-up\" label=\"Backups\" /></flux:checkbox.group>

\n\n---\n\n## Validation Available Validation Rules enum\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### enum\n\nThe `Enum` rule is a class-based rule that validates whether the field under validation contains a valid enum value. The `Enum` rule accepts the name of the enum as its only constructor argument. When validating primitive values, a backed Enum should be provided to the `Enum` rule:\n\n```php\nuse App\\Enums\\ServerStatus;\nuse Illuminate\\Validation\\Rule;\n\n$request->validate([\n    'status' => [Rule::enum(ServerStatus::class)],\n]);\n```\n\nThe `Enum` rule's `only` and `except` methods may be used to limit which enum cases should be considered valid:\n\n```php\nRule::enum(ServerStatus::class)\n    ->only([ServerStatus::Pending, ServerStatus::Active]);\n\nRule::enum(ServerStatus::class)\n    ->except([ServerStatus::Pending, ServerStatus::Active]);\n```\n\nThe `when` method may be used to conditionally modify the `Enum` rule:\n\n```php\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Validation\\Rule;\n\nRule::enum(ServerStatus::class)\n    ->when(\n        Auth::user()->isAdmin(),\n        fn ($rule) => $rule->only(...),\n        fn ($rule) => $rule->only(...),\n    );\n```\n\n---\n\n## Attribute Title Only for full-page components\nAttribute Title | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-title\n\n## Only for full-page components\n\n> [!info] Full-page components only\n> The `#[Title]` attribute only works for full-page components accessed via routes. Regular components rendered within other views don't use titles—they inherit the parent page's title.\n\n---\n\n## Quickstart Write the component\nQuickstart | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/quickstart\n\n## Write the component\n\nOpen `resources/views/pages/post/⚡create.blade.php` and replace its contents with the following:\n\n```blade\n<?php\n\nuse Livewire\\Component;\n\nnew class extends Component {\n    public string $title = '';\n\n    public string $content = '';\n\n    public function save()\n    {\n        $this->validate([\n            'title' => 'required|max:255',\n            'content' => 'required',\n        ]);\n\n        dd($this->title, $this->content);\n    }\n};\n?>\n\n<form wire:submit=\"save\">\n    <label>\n        Title\n        <input type=\"text\" wire:model=\"title\">\n        @error('title') <span style=\"color: red;\">{{ $message }}</span> @enderror\n    </label>\n\n    <label>\n        Content\n        <textarea wire:model=\"content\" rows=\"5\"></textarea>\n        @error('content') <span style=\"color: red;\">{{ $message }}</span> @enderror\n    </label>\n\n    <button type=\"submit\">Save Post</button>\n</form>\n```\n\n> [!info] Don't worry about styling\n> This form is intentionally unstyled so we can focus on Livewire's functionality. In a real application, you'd add CSS or use a framework like Tailwind.\n\nHere's what's happening in the code above:\n\n**Component properties:**\n- `public string $title = '';` — Declares a public property for the post title\n- `public string $content = '';` — Declares a public property for the post content\n\n**Component methods:**\n- `public function save()` — Called when the form is submitted. Validates the data and dumps the output for testing.\n\n**Livewire directives:**\n- `wire:submit=\"save\"` — Calls the `save()` method when the form is submitted, preventing the default page reload\n- `wire:model=\"title\"` — Creates two-way data binding between the input and the `$title` property. As you type, the property updates automatically\n- `wire:model=\"content\"` — Same two-way binding for the textarea and `$content` property\n- `@error('title')` and `@error('content')` — Display validation error messages when validation fails\n\n> [!warning] Livewire components MUST have a single root element\n> Components must have exactly one root HTML element. In this example, the `<form>` element is the single root. Multiple root elements or HTML comments outside the root element will cause an error. When rendering [full-page components](/docs/4.x/pages), named slots for the layout can be placed outside the root element.\n\n> [!tip] In a real application\n> The `save()` method uses `dd()` to dump the values for testing purposes. In a production application, you would typically save the data to a database and redirect:\n> ```php\n> public function save()\n> {\n>     $validated = $this->validate([\n>         'title' => 'required|max:255',\n>         'content' => 'required',\n>     ]);\n>\n>     Post::create($validated); // Assumes you have a Post model and database table\n>\n>     return $this->redirect('/posts');\n> }\n> ```\n\n---\n\n## Validation Deprecated `[#Rule]` attribute\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## Deprecated `[#Rule]` attribute\n\nWhen Livewire v3 first launched, it used the term \"Rule\" instead of \"Validate\" for it's validation attributes (`#[Rule]`).\n\nBecause of naming conflicts with Laravel rule objects, this has since been changed to `#[Validate]`. Both are supported in Livewire v3, however it is recommended that you change all occurrences of `#[Rule]` with `#[Validate]` to stay current.\n\n---\n\n## Components Input > With buttons\nComponents Input | livewire/flux@2.x | Source: https://fluxui.dev/components/input#livewire/flux\n\n
With buttons

Attach buttons to the beginning or end of an input element.

<flux:input.group>    <flux:input placeholder=\"Post title\" />    <flux:button icon=\"plus\">New post</flux:button></flux:input.group><flux:input.group>    <flux:select class=\"max-w-fit\">        <flux:select.option selected>USD</flux:select.option>            </flux:select>    <flux:input placeholder=\"$99.99\" /></flux:input.group>

\n\n---\n\n## Components Select > Checkbox indicator\nComponents Select | livewire/flux@2.x | Source: https://fluxui.dev/components/select#livewire/flux\n\n
\n            Checkbox indicator\n

If you prefer a checkbox indicator instead of the default checkmark icon, you can use the indicator=\"checkbox\" prop.

<flux:select variant=\"listbox\" indicator=\"checkbox\" multiple>    ...</flux:select>

\n\n---\n\n## Components Button > Full width\nComponents Button | livewire/flux@2.x | Source: https://fluxui.dev/components/button#livewire/flux\n\n
Full width

A button that spans the full width of the container.

<flux:button variant=\"primary\" class=\"w-full\">Send invite</flux:button>

\n\n---\n\n## Attribute Validate Example: Contact form\nAttribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate\n\n## Example: Contact form\n\nHere's a complete contact form with validation:\n\n```php\n<?php // resources/views/pages/⚡contact.blade.php\n\nuse Livewire\\Attributes\\Validate;\nuse Livewire\\Component;\nuse App\\Mail\\ContactMessage;\nuse Illuminate\\Support\\Facades\\Mail;\n\nnew class extends Component {\n    #[Validate('required|min:2', as: 'name')]\n    public $name = '';\n\n    #[Validate('required|email')]\n    public $email = '';\n\n    #[Validate('required')]\n    public $subject = '';\n\n    #[Validate('required|min:10', as: 'message')]\n    public $message = '';\n\n    public function submit()\n    {\n        $validated = $this->validate();\n\n        Mail::to('support@example.com')->send(new ContactMessage($validated));\n\n        session()->flash('success', 'Message sent successfully!');\n\n        $this->reset();\n    }\n};\n?>\n\n<div>\n    @if (session('success'))\n        <div class=\"alert\">{{ session('success') }}</div>\n    @endif\n\n    <form wire:submit=\"submit\">\n        <div>\n            <input type=\"text\" wire:model.live.blur=\"name\" placeholder=\"Your name\">\n            @error('name') <span class=\"error\">{{ $message }}</span> @enderror\n        </div>\n\n        <div>\n            <input type=\"email\" wire:model.live.blur=\"email\" placeholder=\"Your email\">\n            @error('email') <span class=\"error\">{{ $message }}</span> @enderror\n        </div>\n\n        <div>\n            <input type=\"text\" wire:model.live.blur=\"subject\" placeholder=\"Subject\">\n            @error('subject') <span class=\"error\">{{ $message }}</span> @enderror\n        </div>\n\n        <div>\n            <textarea wire:model.live.blur=\"message\" placeholder=\"Your message\"></textarea>\n            @error('message') <span class=\"error\">{{ $message }}</span> @enderror\n        </div>\n\n        <button type=\"submit\">Send Message</button>\n    </form>\n</div>\n```\n\nUsers get immediate feedback as they fill out the form, with friendly field names and helpful error messages.\n\n---\n\n## Components Radio > Segmented with icons\nComponents Radio | livewire/flux@2.x | Source: https://fluxui.dev/components/radio#livewire/flux\n\n
Segmented with icons

Combine segmented radio buttons with icon prefixes.

<flux:radio.group wire:model=\"role\" variant=\"segmented\">    <flux:radio label=\"Admin\" icon=\"wrench\" />    <flux:radio label=\"Editor\" icon=\"pencil-square\" />    <flux:radio label=\"Viewer\" icon=\"eye\" /></flux:radio.group>

\n\n---\n\n## Validation Accessing errors in JavaScript\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## Accessing errors in JavaScript\n\nLivewire provides a `$errors` magic property for client-side access to validation errors:\n\n```blade\n<form wire:submit=\"save\">\n    <input type=\"email\" wire:model=\"email\">\n\n    <div wire:show=\"$errors.has('email')\">\n        <span wire:text=\"$errors.first('email')\"></span>\n    </div>\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\n### Available methods\n\n- `$errors.has('field')` - Check if a field has errors\n- `$errors.missing('field')` - Check if a field doesn't have errors\n- `$errors.first('field')` - Get the first error message for a field\n- `$errors.get('field')` - Get all error messages for a field\n- `$errors.all()` - Get all errors for all fields\n- `$errors.clear()` - Clear all errors\n- `$errors.clear('field')` - Clear errors for a specific field\n\nWhen using Alpine.js, access `$errors` through `$wire.$errors`.\n\n---\n\n## Validation Form objects\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## Form objects\n\nAs more properties and validation rules are added to a Livewire component, it can begin to feel too crowded. To alleviate this pain and also provide a helpful abstraction for code reuse, you can use Livewire's *Form Objects* to store your properties and validation rules.\n\nBelow is the same `CreatePost` example, but now the properties and rules have been extracted to a dedicated form object named `PostForm`:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:3')]\n\tpublic $title = '';\n\n    #[Validate('required|min:3')]\n    public $content = '';\n}\n```\n\nThe `PostForm` above can now be defined as a property on the `CreatePost` component:\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse App\\Livewire\\Forms\\PostForm;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nclass CreatePost extends Component\n{\n    public PostForm $form;\n\n    public function save()\n    {\n\t\tPost::create(\n    \t\t$this->form->all()\n    \t);\n\n\t\treturn redirect()->to('/posts');\n    }\n\n    // ...\n}\n```\n\nAs you can see, instead of listing out each property individually, we can retrieve all the property values using the `->all()` method on the form object.\n\nAlso, when referencing the property names in the template, you must prepend `form.` to each instance:\n\n```blade\n<form wire:submit=\"save\">\n\t<input type=\"text\" wire:model=\"form.title\">\n    <div>@error('form.title') {{ $message }} @enderror</div>\n\n\t<textarea wire:model=\"form.content\"></textarea>\n    <div>@error('form.content') {{ $message }} @enderror</div>\n\n\t<button type=\"submit\">Save</button>\n</form>\n```\n\nWhen using form objects, `#[Validate]` attribute validation will be run every time a property is updated. However, if you disable this behavior by specifying `onUpdate: false` on the attribute, you can manually run a form object's validation using `$this->form->validate()`:\n\n```php\npublic function save()\n{\n    Post::create(\n        $this->form->validate()\n    );\n\n    return redirect()->to('/posts');\n}\n```\n\nForm objects are a useful abstraction for most larger datasets and a variety of additional features that make them even more powerful. For more information, check out the comprehensive [form object documentation](/docs/4.x/forms#extracting-a-form-object).\n\n---\n\n## Validation Available Validation Rules unique:_table_,_column_\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### unique:_table_,_column_\n\nThe field under validation must not exist within the given database table.\n\n**Specifying a Custom Table / Column Name:**\n\nInstead of specifying the table name directly, you may specify the Eloquent model which should be used to determine the table name:\n\n```php\n'email' => 'unique:App\\Models\\User,email_address'\n```\n\nThe `column` option may be used to specify the field's corresponding database column. If the `column` option is not specified, the name of the field under validation will be used.\n\n```php\n'email' => 'unique:users,email_address'\n```\n\n**Specifying a Custom Database Connection**\n\nOccasionally, you may need to set a custom connection for database queries made by the Validator. To accomplish this, you may prepend the connection name to the table name:\n\n```php\n'email' => 'unique:connection.users,email_address'\n```\n\n**Forcing a Unique Rule to Ignore a Given ID:**\n\nSometimes, you may wish to ignore a given ID during unique validation. For example, consider an \"update profile\" screen that includes the user's name, email address, and location. You will probably want to verify that the email address is unique. However, if the user only changes the name field and not the email field, you do not want a validation error to be thrown because the user is already the owner of the email address in question.\n\nTo instruct the validator to ignore the user's ID, we'll use the `Rule` class to fluently define the rule. In this example, we'll also specify the validation rules as an array instead of using the `|` character to delimit the rules:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rule;\n\nValidator::make($data, [\n    'email' => [\n        'required',\n        Rule::unique('users')->ignore($user->id),\n    ],\n]);\n```\n\n> [!WARNING]\n> You should never pass any user controlled request input into the `ignore` method. Instead, you should only pass a system generated unique ID such as an auto-incrementing ID or UUID from an Eloquent model instance. Otherwise, your application will be vulnerable to an SQL injection attack.\n\nInstead of passing the model key's value to the `ignore` method, you may also pass the entire model instance. Laravel will automatically extract the key from the model:\n\n```php\nRule::unique('users')->ignore($user)\n```\n\nIf your table uses a primary key column name other than `id`, you may specify the name of the column when calling the `ignore` method:\n\n```php\nRule::unique('users')->ignore($user->id, 'user_id')\n```\n\nBy default, the `unique` rule will check the uniqueness of the column matching the name of the attribute being validated. However, you may pass a different column name as the second argument to the `unique` method:\n\n```php\nRule::unique('users', 'email_address')->ignore($user->id)\n```\n\n**Adding Additional Where Clauses:**\n\nYou may specify additional query conditions by customizing the query using the `where` method. For example, let's add a query condition that scopes the query to only search records that have an `account_id` column value of `1`:\n\n```php\n'email' => Rule::unique('users')->where(fn (Builder $query) => $query->where('account_id', 1))\n```\n\n**Ignoring Soft Deleted Records in Unique Checks:**\n\nBy default, the unique rule includes soft deleted records when determining uniqueness. To exclude soft deleted records from the uniqueness check, you may invoke the `withoutTrashed` method:\n\n```php\nRule::unique('users')->withoutTrashed();\n```\n\nIf your model uses a column name other than `deleted_at` for soft deleted records, you may provide the column name when invoking the `withoutTrashed` method:\n\n```php\nRule::unique('users')->withoutTrashed('was_deleted_at');\n```\n\n---\n\n## Search Full-Text Search Running Full-Text Queries\nSearch | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/search.md\n\n\n### Running Full-Text Queries\n\nOnce the index is in place, use the `whereFullText` query builder method to search against it. Laravel will generate the appropriate SQL for your database driver — for example, `MATCH(...) AGAINST(...)` on MariaDB and MySQL, and `to_tsvector(...) @@ plainto_tsquery(...)` on PostgreSQL:\n\n```php\n$articles = Article::whereFullText('body', 'web developer')->get();\n```\n\nWhen using MariaDB and MySQL, results are automatically ordered by relevance score. On PostgreSQL, `whereFullText` filters matching records but does not order them by relevance — if you need automatic relevance ordering on PostgreSQL, consider using [Scout's database engine](#database-engine), which handles this for you.\n\nIf you created a composite full-text index across multiple columns, you may search against all of them by passing the same array of columns to `whereFullText`:\n\n```php\n$articles = Article::whereFullText(\n    ['title', 'body'], 'web developer'\n)->get();\n```\n\nThe `orWhereFullText` method may be used to add a full-text search clause as an \"or\" condition. For complete details, consult the [query builder documentation](/docs/{{version}}/queries#full-text-where-clauses).\n\n---\n\n## Components Field > With badge\nComponents Field | livewire/flux@2.x | Source: https://fluxui.dev/components/field#livewire/flux\n\n
With badge

Badges allow you to enhance a field with additional information such as being \"required\" or \"optional\" when it might not be expected.

<flux:field>    <flux:label badge=\"Required\">Email</flux:label>    <flux:input type=\"email\" required />    <flux:error name=\"email\" /></flux:field><flux:field>    <flux:label badge=\"Optional\">Phone number</flux:label>    <flux:input type=\"phone\" placeholder=\"(555) 555-5555\" mask=\"(999) 999-9999\"  />    <flux:error name=\"phone\" /></flux:field>

\n\n---\n\n## Forms See also\nForms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms\n\n## See also\n\n- **[Validation](/docs/4.x/validation)** — Validate form inputs with real-time feedback\n- **[wire:model](/docs/4.x/wire-model)** — Bind form inputs to component properties\n- **[File Uploads](/docs/4.x/uploads)** — Handle file uploads in forms\n- **[Actions](/docs/4.x/actions)** — Process form submissions with actions\n- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during form submission\n\n---\n\n## Validation Testing validation\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## Testing validation\n\nLivewire provides useful testing utilities for validation scenarios, such as the `assertHasErrors()` method.\n\nBelow is a basic test case that ensures validation errors are thrown if no input is set for the `title` property:\n\n```php\n<?php\n\nnamespace Tests\\Feature\\Livewire;\n\nuse App\\Livewire\\CreatePost;\nuse Livewire\\Livewire;\nuse Tests\\TestCase;\n\nclass CreatePostTest extends TestCase\n{\n    public function test_cant_create_post_without_title()\n    {\n        Livewire::test(CreatePost::class)\n            ->set('content', 'Sample content...')\n            ->call('save')\n            ->assertHasErrors('title');\n    }\n}\n```\n\nIn addition to testing the presence of errors, `assertHasErrors` allows you to also narrow down the assertion to specific rules by passing the rules to assert against as the second argument to the method:\n\n```php\npublic function test_cant_create_post_with_title_shorter_than_3_characters()\n{\n    Livewire::test(CreatePost::class)\n        ->set('title', 'Sa')\n        ->set('content', 'Sample content...')\n        ->call('save')\n        ->assertHasErrors(['title' => ['min:3']]);\n}\n```\n\nYou can also assert the presence of validation errors for multiple properties at the same time:\n\n```php\npublic function test_cant_create_post_without_title_and_content()\n{\n    Livewire::test(CreatePost::class)\n        ->call('save')\n        ->assertHasErrors(['title', 'content']);\n}\n```\n\nFor more information on other testing utilities provided by Livewire, check out the [testing documentation](/docs/4.x/testing).\n\n_[Truncated]_"}]


sed -n '1,260p' database/factories/DiscountFactory.php

DiscountFactory.php

<?php

namespace Database\Factories;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Discount>
 */
class DiscountFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'type' => DiscountType::Code,
            'code' => fake()->unique()->bothify('SAVE##'),
            'value_type' => DiscountValueType::Percent,
            'value_amount' => 10,
            'starts_at' => now()->subDay(),
            'ends_at' => now()->addMonth(),
            'usage_limit' => null,
            'usage_count' => 0,
            'rules_json' => ['customer_eligibility' => 'all'],
            'status' => DiscountStatus::Active,
        ];
    }

    public function fixed(int $amount = 500): static
    {
        return $this->state(fn (array $attributes): array => [
            'value_type' => DiscountValueType::Fixed,
            'value_amount' => $amount,
        ]);
    }

    public function freeShipping(): static
    {
        return $this->state(fn (array $attributes): array => [
            'value_type' => DiscountValueType::FreeShipping,
            'value_amount' => 0,
        ]);
    }
}


/bin/zsh
-lc
sed -n '1,260p' app/Models/Discount.php

<?php

namespace App\Models;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Discount extends Model
{
    /** @use HasFactory<\Database\Factories\DiscountFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'type',
        'code',
        'value_type',
        'value_amount',
        'starts_at',
        'ends_at',
        'usage_limit',
        'usage_count',
        'rules_json',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'code',
        'value_amount' => 0,
        'usage_count' => 0,
        'rules_json' => '{}',
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => DiscountType::class,
            'value_type' => DiscountValueType::class,
            'value_amount' => 'integer',
            'starts_at' => 'datetime',
            'ends_at' => 'datetime',
            'usage_limit' => 'integer',
            'usage_count' => 'integer',
            'rules_json' => 'array',
            'status' => DiscountStatus::class,
        ];
    }
}


sed -n '1,240p' database/migrations/2026_05_03_225427_create_discounts_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('discounts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->enum('type', ['code', 'automatic'])->default('code');
            $table->string('code')->nullable();
            $table->enum('value_type', ['fixed', 'percent', 'free_shipping']);
            $table->integer('value_amount')->default(0);
            $table->timestamp('starts_at');
            $table->timestamp('ends_at')->nullable();
            $table->unsignedInteger('usage_limit')->nullable();
            $table->unsignedInteger('usage_count')->default(0);
            $table->text('rules_json')->default('{}');
            $table->enum('status', ['draft', 'active', 'expired', 'disabled'])->default('active');
            $table->timestamps();

            $table->unique(['store_id', 'code']);
            $table->index('store_id');
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'type']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('discounts');
    }
};


Output:
Chunk ID: bcf8ca
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 410
Output:
<?php

namespace App\Models;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Discount extends Model
{
    /** @use HasFactory<\Database\Factories\DiscountFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'type',
        'code',
        'value_type',
        'value_amount',
        'starts_at',
        'ends_at',
        'usage_limit',
        'usage_count',
        'rules_json',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'code',
        'value_amount' => 0,
        'usage_count' => 0,
        'rules_json' => '{}',
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => DiscountType::class,
            'value_type' => DiscountValueType::class,
            'value_amount' => 'integer',
            'starts_at' => 'datetime',
            'ends_at' => 'datetime',
            'usage_limit' => 'integer',
            'usage_count' => 'integer',
            'rules_json' => 'array',
            'status' => DiscountStatus::class,
        ];
    }
}


Output:
Chunk ID: 094264
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 369
Output:
<?php

namespace Database\Factories;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Discount>
 */
class DiscountFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'type' => DiscountType::Code,
            'code' => fake()->unique()->bothify('SAVE##'),
            'value_type' => DiscountValueType::Percent,
            'value_amount' => 10,
            'starts_at' => now()->subDay(),
            'ends_at' => now()->addMonth(),
            'usage_limit' => null,
            'usage_count' => 0,
            'rules_json' => ['customer_eligibility' => 'all'],
            'status' => DiscountStatus::Active,
        ];
    }

    public function fixed(int $amount = 500): static
    {
        return $this->state(fn (array $attributes): array => [
            'value_type' => DiscountValueType::Fixed,
            'value_amount' => $amount,
        ]);
    }

    public function freeShipping(): static
    {
        return $this->state(fn (array $attributes): array => [
            'value_type' => DiscountValueType::FreeShipping,
            'value_amount' => 0,
        ]);
    }
}


Output:
Chunk ID: 2588dc
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 360
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('discounts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->enum('type', ['code', 'automatic'])->default('code');
            $table->string('code')->nullable();
            $table->enum('value_type', ['fixed', 'percent', 'free_shipping']);
            $table->integer('value_amount')->default(0);
            $table->timestamp('starts_at');
            $table->timestamp('ends_at')->nullable();
            $table->unsignedInteger('usage_limit')->nullable();
            $table->unsignedInteger('usage_count')->default(0);
            $table->text('rules_json')->default('{}');
            $table->enum('status', ['draft', 'active', 'expired', 'disabled'])->default('active');
            $table->timestamps();

            $table->unique(['store_id', 'code']);
            $table->index('store_id');
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'type']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('discounts');
    }
};


sed -n '1,120p' app/Enums/DiscountType.php


sed -n '1,120p' app/Enums/DiscountValueType.php


sed -n '1,120p' app/Enums/DiscountStatus.php


sed -n '1,260p' app/Services/DiscountService.php


rg -n "rules_json|minimum|specific|collection|one_per|customer" app tests database/seeders/DiscountSeeder.php


sed -n '1,120p' app/Enums/DiscountType.php

<?php

namespace App\Enums;

enum DiscountType: string
{
    case Code = 'code';
    case Automatic = 'automatic';
}


sed -n '1,120p' app/Enums/DiscountValueType.php

<?php

namespace App\Enums;

enum DiscountValueType: string
{
    case Percent = 'percent';
    case Fixed = 'fixed';
    case FreeShipping = 'free_shipping';
}


sed -n '1,120p' app/Enums/DiscountStatus.php

<?php

namespace App\Enums;

enum DiscountStatus: string
{
    case Draft = 'draft';
    case Active = 'active';
    case Expired = 'expired';
    case Disabled = 'disabled';
}


sed -n '1,260p' app/Services/DiscountService.php

<?php

namespace App\Services;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Exceptions\InvalidDiscountException;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Discount;
use App\Models\ProductVariant;
use App\Models\Store;
use App\ValueObjects\DiscountResult;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class DiscountService
{
    public function validate(string $code, Store $store, Cart $cart): Discount
    {
        $normalizedCode = mb_strtolower(trim($code));

        $discount = Discount::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('type', DiscountType::Code)
            ->whereRaw('lower(code) = ?', [$normalizedCode])
            ->first();

        if (! $discount instanceof Discount) {
            throw InvalidDiscountException::because('discount_not_found', 'Discount code was not found.');
        }

        if ($discount->status !== DiscountStatus::Active) {
            throw InvalidDiscountException::because('discount_expired', 'Discount is not active.');
        }

        if ($discount->starts_at->isFuture()) {
            throw InvalidDiscountException::because('discount_not_yet_active', 'Discount is not active yet.');
        }

        if ($discount->ends_at !== null && $discount->ends_at->isPast()) {
            throw InvalidDiscountException::because('discount_expired', 'Discount has expired.');
        }

        if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
            throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount usage limit has been reached.');
        }

        $lines = $this->cartLines($cart);
        $subtotal = $lines->sum('line_subtotal_amount');
        $minimum = (int) data_get($discount->rules_json, 'min_purchase_amount', data_get($discount->rules_json, 'minimum_purchase', 0));

        if ($minimum > 0 && $subtotal < $minimum) {
            throw InvalidDiscountException::because('discount_min_purchase_not_met', 'Cart does not meet the minimum purchase amount.');
        }

        if ($this->qualifyingLines($discount, $lines)->isEmpty()) {
            throw InvalidDiscountException::because('discount_not_applicable', 'Discount does not apply to these cart lines.');
        }

        return $discount;
    }

    /**
     * @param  array<int, CartLine>  $lines
     */
    public function calculate(Discount $discount, int $subtotal, array $lines): DiscountResult
    {
        if ($discount->value_type === DiscountValueType::FreeShipping) {
            return new DiscountResult(0, [], true);
        }

        $qualifyingLines = $this->qualifyingLines($discount, collect($lines));
        $qualifyingSubtotal = $qualifyingLines->sum('line_subtotal_amount');

        if ($qualifyingSubtotal <= 0) {
            return new DiscountResult(0, []);
        }

        $discountAmount = match ($discount->value_type) {
            DiscountValueType::Percent => (int) round($qualifyingSubtotal * $discount->value_amount / 100),
            DiscountValueType::Fixed => min($discount->value_amount, $qualifyingSubtotal),
            DiscountValueType::FreeShipping => 0,
        };

        $remaining = $discountAmount;
        $allocations = [];
        $lastIndex = $qualifyingLines->keys()->last();

        foreach ($qualifyingLines as $index => $line) {
            if ($index === $lastIndex) {
                $allocations[$line->getKey()] = $remaining;

                continue;
            }

            $allocation = (int) round($discountAmount * $line->line_subtotal_amount / $qualifyingSubtotal);
            $allocations[$line->getKey()] = $allocation;
            $remaining -= $allocation;
        }

        return new DiscountResult($discountAmount, $allocations);
    }

    public function applyToCart(Cart $cart, Discount $discount): DiscountResult
    {
        return DB::transaction(function () use ($cart, $discount): DiscountResult {
            $lines = $this->cartLines($cart);
            $result = $this->calculate($discount, $lines->sum('line_subtotal_amount'), $lines->all());

            $lines->each(function (CartLine $line) use ($result): void {
                $discountAmount = $result->allocations[$line->getKey()] ?? 0;

                $line->forceFill([
                    'line_discount_amount' => $discountAmount,
                    'line_total_amount' => $line->line_subtotal_amount - $discountAmount,
                ])->save();
            });

            return $result;
        });
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function cartLines(Cart $cart): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->orderBy('id')
            ->get();
    }

    /**
     * @param  Collection<int, CartLine>  $lines
     * @return Collection<int, CartLine>
     */
    private function qualifyingLines(Discount $discount, Collection $lines): Collection
    {
        $productIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
            ->filter()
            ->map(fn (mixed $id): int => (int) $id)
            ->values();
        $collectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
            ->filter()
            ->map(fn (mixed $id): int => (int) $id)
            ->values();

        if ($productIds->isEmpty() && $collectionIds->isEmpty()) {
            return $lines;
        }

        return $lines->filter(function (CartLine $line) use ($productIds, $collectionIds): bool {
            $variant = ProductVariant::withoutGlobalScopes()
                ->with(['product' => fn ($query) => $query->withoutGlobalScopes()->with('collections')])
                ->find($line->variant_id);

            if (! $variant instanceof ProductVariant || $variant->product === null) {
                return false;
            }

            if ($productIds->contains((int) $variant->product_id)) {
                return true;
            }

            return $variant->product->collections
                ->pluck('id')
                ->map(fn (int $id): int => $id)
                ->intersect($collectionIds)
                ->isNotEmpty();
        });
    }
}


rg -n "rules_json|minimum|specific|collection|one_per|customer" app tests database/seeders/DiscountSeeder.php

rg -n 'rules_json|minimum|specific|collection|one_per|customer' app tests database/seeders/DiscountSeeder.php

Question: rules_json|minimum|specific|collection|one_per|customer

database/seeders/DiscountSeeder.php:31:                        'rules_json' => $discount['rules_json'],
database/seeders/DiscountSeeder.php:40:     * @return array<int, array{code: string, value_type: string, value_amount: int, rules_json: array<string, mixed>}>
database/seeders/DiscountSeeder.php:45:            ['code' => 'SAVE10', 'value_type' => 'percent', 'value_amount' => 10, 'rules_json' => ['customer_eligibility' => 'all']],
database/seeders/DiscountSeeder.php:46:            ['code' => '5OFF', 'value_type' => 'fixed', 'value_amount' => 500, 'rules_json' => ['min_purchase_amount' => 2500, 'customer_eligibility' => 'all']],
database/seeders/DiscountSeeder.php:47:            ['code' => 'FREESHIP', 'value_type' => 'free_shipping', 'value_amount' => 0, 'rules_json' => ['customer_eligibility' => 'all']],
tests/Pest.php:8:| The closure you provide to your test functions is always bound to a specific PHPUnit test
tests/Pest.php:38:| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
tests/Feature/Catalog/CatalogFoundationTest.php:24:        ->and(Schema::hasColumns('collections', ['store_id', 'title', 'handle', 'type', 'status']))->toBeTrue()
tests/Feature/Catalog/CatalogUiTest.php:31:        ->get('/collections/t-shirts')
tests/Feature/Catalog/CatalogUiTest.php:63:        ->get('/admin/collections')
tests/Feature/Catalog/CatalogUiTest.php:154:test('admin collection form creates and assigns products', function () {
tests/Feature/Catalog/CatalogUiTest.php:163:        ->set('handle', 'e2e-test-collection')
tests/Feature/Catalog/CatalogUiTest.php:169:    $collection = Collection::query()->where('handle', 'e2e-test-collection')->firstOrFail();
tests/Feature/Catalog/CatalogUiTest.php:171:    expect($collection->products()->pluck('products.id')->all())->toBe([$product->getKey()]);
tests/Feature/Cart/CartServiceTest.php:107:test('cart service allows oversell for continue policy and merges guest carts into customer carts', function () {
tests/Feature/Cart/CartServiceTest.php:113:    $customer = Customer::factory()->create(['store_id' => $store->getKey()]);
tests/Feature/Cart/CartServiceTest.php:114:    $customerCart = $service->create($store, $customer);
tests/Feature/Cart/CartServiceTest.php:117:    $service->addLine($customerCart, $firstVariant->getKey(), 2);
tests/Feature/Cart/CartServiceTest.php:118:    $service->addLine($customerCart, $secondVariant->getKey(), 3);
tests/Feature/Cart/CartServiceTest.php:120:    $merged = $service->mergeOnLogin($guestCart, $customerCart);
tests/Feature/Foundation/CustomerAuthTest.php:13:test('customer authentication is scoped to the current store', function () {
tests/Feature/Foundation/CustomerAuthTest.php:31:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:36:    Auth::guard('customer')->logout();
tests/Feature/Foundation/CustomerAuthTest.php:38:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:45:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:51:test('customer login component authenticates within the resolved store', function () {
tests/Feature/Foundation/CustomerAuthTest.php:53:    $customer = Customer::factory()->create([
tests/Feature/Foundation/CustomerAuthTest.php:55:        'email' => 'customer@example.test',
tests/Feature/Foundation/CustomerAuthTest.php:62:        ->set('email', $customer->email)
tests/Feature/Foundation/CustomerAuthTest.php:68:    $this->assertAuthenticatedAs($customer, 'customer');
tests/Feature/Foundation/CustomerAuthTest.php:71:test('customer registration is unique per store', function () {
tests/Feature/Storefront/OrderViewsTest.php:126:test('order confirmation is visible only for the session order or customer order', function () {
tests/Feature/Storefront/OrderViewsTest.php:128:    $customer = Customer::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
tests/Feature/Storefront/OrderViewsTest.php:130:    $order = Order::factory()->forCustomer($customer)->paid()->create([
tests/Feature/Storefront/OrderViewsTest.php:132:        'customer_id' => $customer->getKey(),
tests/Feature/Storefront/OrderViewsTest.php:133:        'email' => $customer->email,
tests/Feature/Storefront/OrderViewsTest.php:146:    $this->actingAs($otherCustomer, 'customer');
tests/Feature/Storefront/OrderViewsTest.php:151:    $this->actingAs($customer, 'customer');
tests/Feature/Storefront/OrderViewsTest.php:157:test('customer account lists and shows customer orders', function () {
tests/Feature/Storefront/OrderViewsTest.php:159:    $customer = Customer::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
tests/Feature/Storefront/OrderViewsTest.php:161:    $order = Order::factory()->forCustomer($customer)->paid()->create([
tests/Feature/Storefront/OrderViewsTest.php:163:        'customer_id' => $customer->getKey(),
tests/Feature/Storefront/OrderViewsTest.php:168:    …20044 chars truncated…wire/Storefront/Products/Show.php:109:            ->with(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections'])
app/Services/ThemeSettingsService.php:71:                    'primary_url' => '/collections/new-arrivals',
app/Services/ThemeSettingsService.php:72:                    'secondary_label' => 'View collections',
app/Services/ThemeSettingsService.php:73:                    'secondary_url' => '/collections',
app/Services/ThemeSettingsService.php:76:                'featured_collection_limit' => 4,
app/Livewire/Storefront/Account/Auth/Login.php:40:        $key = 'customer-login:'.request()->ip();
app/Livewire/Storefront/Account/Auth/Login.php:52:        if (! Auth::guard('customer')->attempt([
app/Livewire/Storefront/Account/Auth/Login.php:69:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Account/Auth/Login.php:71:        if ($customer instanceof Customer && session()->has('cart_id')) {
app/Livewire/Storefront/Account/Auth/Login.php:72:            app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);
app/Models/CustomerAddress.php:20:        'customer_id',
app/Models/CustomerAddress.php:29:    public function customer(): BelongsTo
app/Livewire/Storefront/Home.php:41:            ->limit((int) data_get($this->themeSettings(), 'home.featured_collection_limit', 4))
app/Livewire/Storefront/Checkout/Show.php:82:        $this->email = $this->customer()?->email ?? '';
app/Livewire/Storefront/Checkout/Show.php:271:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/Checkout/Show.php:294:            $checkout = app(CheckoutService::class)->createFromCart($cart, $this->customer());
app/Livewire/Storefront/Checkout/Show.php:390:    private function customer(): ?Customer
app/Livewire/Storefront/Checkout/Show.php:392:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Checkout/Show.php:394:        return $customer instanceof Customer ? $customer : null;
app/Livewire/Storefront/Cart/Show.php:158:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/Cart/Show.php:286:    private function customer(): ?Customer
app/Livewire/Storefront/Cart/Show.php:288:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Cart/Show.php:290:        return $customer instanceof Customer ? $customer : null;
app/Livewire/Storefront/CartDrawer.php:90:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/CartDrawer.php:126:    private function customer(): ?Customer
app/Livewire/Storefront/CartDrawer.php:128:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/CartDrawer.php:130:        return $customer instanceof Customer ? $customer : null;
app/Models/Discount.php:31:        'rules_json',
app/Models/Discount.php:42:        'rules_json' => '{}',
app/Models/Discount.php:67:            'rules_json' => 'array',
app/Models/Order.php:26:        'customer_id',
app/Models/Order.php:78:    public function customer(): BelongsTo
app/Models/Cart.php:22:        'customer_id',
app/Models/Cart.php:48:    public function customer(): BelongsTo
app/Models/Product.php:76:    public function collections(): BelongsToMany
app/Models/Product.php:78:        return $this->belongsToMany(Collection::class, 'collection_products')
app/Models/Checkout.php:22:        'customer_id',
app/Models/Checkout.php:61:    public function customer(): BelongsTo
app/Models/Collection.php:51:        return $this->belongsToMany(Product::class, 'collection_products')
app/Http/Resources/Admin/V1/OrderResource.php:22:            'customer' => $this->whenLoaded('customer', fn () => $this->customer ? [
app/Http/Resources/Admin/V1/OrderResource.php:23:                'id' => $this->customer->id,
app/Http/Resources/Admin/V1/OrderResource.php:24:                'name' => $this->customer->name,
app/Http/Resources/Admin/V1/OrderResource.php:25:                'email' => $this->customer->email,
app/Http/Resources/Admin/V1/OrderResource.php:57:            'refunds' => RefundResource::collection($this->whenLoaded('refunds')),
app/Http/Resources/Admin/V1/OrderResource.php:58:            'fulfillments' => FulfillmentResource::collection($this->whenLoaded('fulfillments')),
app/Enums/NavigationItemType.php:9:    case Collection = 'collection';
app/Http/Resources/Storefront/V1/CartResource.php:22:            'customer_id' => $this->customer_id,
app/Http/Resources/Storefront/V1/CartResource.php:33:            'lines' => CartLineResource::collection($lines),
app/Http/Resources/Storefront/V1/CheckoutResource.php:21:            'customer_id' => $this->customer_id,
app/Http/Resources/Storefront/V1/CheckoutResource.php:31:            'available_shipping_rates' => ShippingRateResource::collection($this->whenLoaded('availableRates')),


Output:
Chunk ID: f0b3bb
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 30
Output:
<?php

namespace App\Enums;

enum DiscountType: string
{
    case Code = 'code';
    case Automatic = 'automatic';
}


Output:
Chunk ID: 8a91a8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 41
Output:
<?php

namespace App\Enums;

enum DiscountValueType: string
{
    case Percent = 'percent';
    case Fixed = 'fixed';
    case FreeShipping = 'free_shipping';
}


Output:
Chunk ID: 8f71ba
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 45
Output:
<?php

namespace App\Enums;

enum DiscountStatus: string
{
    case Draft = 'draft';
    case Active = 'active';
    case Expired = 'expired';
    case Disabled = 'disabled';
}


Output:
Chunk ID: d78650
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1594
Output:
<?php

namespace App\Services;

use App\Enums\DiscountStatus;
use App\Enums\DiscountType;
use App\Enums\DiscountValueType;
use App\Exceptions\InvalidDiscountException;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Discount;
use App\Models\ProductVariant;
use App\Models\Store;
use App\ValueObjects\DiscountResult;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class DiscountService
{
    public function validate(string $code, Store $store, Cart $cart): Discount
    {
        $normalizedCode = mb_strtolower(trim($code));

        $discount = Discount::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('type', DiscountType::Code)
            ->whereRaw('lower(code) = ?', [$normalizedCode])
            ->first();

        if (! $discount instanceof Discount) {
            throw InvalidDiscountException::because('discount_not_found', 'Discount code was not found.');
        }

        if ($discount->status !== DiscountStatus::Active) {
            throw InvalidDiscountException::because('discount_expired', 'Discount is not active.');
        }

        if ($discount->starts_at->isFuture()) {
            throw InvalidDiscountException::because('discount_not_yet_active', 'Discount is not active yet.');
        }

        if ($discount->ends_at !== null && $discount->ends_at->isPast()) {
            throw InvalidDiscountException::because('discount_expired', 'Discount has expired.');
        }

        if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
            throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount usage limit has been reached.');
        }

        $lines = $this->cartLines($cart);
        $subtotal = $lines->sum('line_subtotal_amount');
        $minimum = (int) data_get($discount->rules_json, 'min_purchase_amount', data_get($discount->rules_json, 'minimum_purchase', 0));

        if ($minimum > 0 && $subtotal < $minimum) {
            throw InvalidDiscountException::because('discount_min_purchase_not_met', 'Cart does not meet the minimum purchase amount.');
        }

        if ($this->qualifyingLines($discount, $lines)->isEmpty()) {
            throw InvalidDiscountException::because('discount_not_applicable', 'Discount does not apply to these cart lines.');
        }

        return $discount;
    }

    /**
     * @param  array<int, CartLine>  $lines
     */
    public function calculate(Discount $discount, int $subtotal, array $lines): DiscountResult
    {
        if ($discount->value_type === DiscountValueType::FreeShipping) {
            return new DiscountResult(0, [], true);
        }

        $qualifyingLines = $this->qualifyingLines($discount, collect($lines));
        $qualifyingSubtotal = $qualifyingLines->sum('line_subtotal_amount');

        if ($qualifyingSubtotal <= 0) {
            return new DiscountResult(0, []);
        }

        $discountAmount = match ($discount->value_type) {
            DiscountValueType::Percent => (int) round($qualifyingSubtotal * $discount->value_amount / 100),
            DiscountValueType::Fixed => min($discount->value_amount, $qualifyingSubtotal),
            DiscountValueType::FreeShipping => 0,
        };

        $remaining = $discountAmount;
        $allocations = [];
        $lastIndex = $qualifyingLines->keys()->last();

        foreach ($qualifyingLines as $index => $line) {
            if ($index === $lastIndex) {
                $allocations[$line->getKey()] = $remaining;

                continue;
            }

            $allocation = (int) round($discountAmount * $line->line_subtotal_amount / $qualifyingSubtotal);
            $allocations[$line->getKey()] = $allocation;
            $remaining -= $allocation;
        }

        return new DiscountResult($discountAmount, $allocations);
    }

    public function applyToCart(Cart $cart, Discount $discount): DiscountResult
    {
        return DB::transaction(function () use ($cart, $discount): DiscountResult {
            $lines = $this->cartLines($cart);
            $result = $this->calculate($discount, $lines->sum('line_subtotal_amount'), $lines->all());

            $lines->each(function (CartLine $line) use ($result): void {
                $discountAmount = $result->allocations[$line->getKey()] ?? 0;

                $line->forceFill([
                    'line_discount_amount' => $discountAmount,
                    'line_total_amount' => $line->line_subtotal_amount - $discountAmount,
                ])->save();
            });

            return $result;
        });
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function cartLines(Cart $cart): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->orderBy('id')
            ->get();
    }

    /**
     * @param  Collection<int, CartLine>  $lines
     * @return Collection<int, CartLine>
     */
    private function qualifyingLines(Discount $discount, Collection $lines): Collection
    {
        $productIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
            ->filter()
            ->map(fn (mixed $id): int => (int) $id)
            ->values();
        $collectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
            ->filter()
            ->map(fn (mixed $id): int => (int) $id)
            ->values();

        if ($productIds->isEmpty() && $collectionIds->isEmpty()) {
            return $lines;
        }

        return $lines->filter(function (CartLine $line) use ($productIds, $collectionIds): bool {
            $variant = ProductVariant::withoutGlobalScopes()
                ->with(['product' => fn ($query) => $query->withoutGlobalScopes()->with('collections')])
                ->find($line->variant_id);

            if (! $variant instanceof ProductVariant || $variant->product === null) {
                return false;
            }

            if ($productIds->contains((int) $variant->product_id)) {
                return true;
            }

            return $variant->product->collections
                ->pluck('id')
                ->map(fn (int $id): int => $id)
                ->intersect($collectionIds)
                ->isNotEmpty();
        });
    }
}