command: sed -n '1,120p' app/Events/OrderCreated.php

aggregated_output:
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}


command:
/bin/zsh
-lc
rg --files app/Events

cmd: rg --files app/Events

path: Events

aggregated_output:
app/Events/FulfillmentCreated.php
app/Events/FulfillmentDelivered.php
app/Events/FulfillmentShipped.php
app/Events/OrderPaid.php
app/Events/OrderCreated.php
app/Events/ProductStatusChanged.php
app/Events/OrderCancelled.php
app/Events/OrderRefunded.php

status: completed


output:
Chunk ID: 118cd5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 63
Output:
app/Events/FulfillmentCreated.php
app/Events/FulfillmentDelivered.php
app/Events/FulfillmentShipped.php
app/Events/OrderPaid.php
app/Events/OrderCreated.php
app/Events/ProductStatusChanged.php
app/Events/OrderCancelled.php
app/Events/OrderRefunded.php


output:
Chunk ID: 2cfcf5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 69
Output:
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}


command: sed -n '1,120p' app/Events/OrderRefunded.php

OrderRefunded.php

type: read

aggregated_output:
<?php

namespace App\Events;

use App\Models\Order;
use App\Models\Refund;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderRefunded
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
        public Refund $refund,
    ) {}
}


output:
Chunk ID: 43a5f3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 83
Output:
<?php

namespace App\Events;

use App\Models\Order;
use App\Models\Refund;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderRefunded
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
        public Refund $refund,
    ) {}
}


command: sed -n '1,120p' app/Events/ProductStatusChanged.php

ProductStatusChanged.php

aggregated_output:
<?php

namespace App\Events;

use App\Enums\ProductStatus;
use App\Models\Product;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ProductStatusChanged
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Product $product,
        public ProductStatus $from,
        public ProductStatus $to,
    ) {}
}


output:
Chunk ID: 9a6b67
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 97
Output:
<?php

namespace App\Events;

use App\Enums\ProductStatus;
use App\Models\Product;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ProductStatusChanged
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Product $product,
        public ProductStatus $from,
        public ProductStatus $to,
    ) {}
}


command: sed -n '1,120p' app/Events/FulfillmentCreated.php

FulfillmentCreated.php

aggregated_output:
<?php

namespace App\Events;

use App\Models\Fulfillment;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class FulfillmentCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Fulfillment $fulfillment,
    ) {}
}


output:
Chunk ID: 693136
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 75
Output:
<?php

namespace App\Events;

use App\Models\Fulfillment;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class FulfillmentCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Fulfillment $fulfillment,
    ) {}
}


queries: events listeners register listen discovery queued listeners


server: laravel-boost

tool: search-docs

type: text

text:
# Search Results

## Verification Events
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

## Events

When using the [Laravel application starter kits](/docs/{{version}}/starter-kits), Laravel dispatches an `Illuminate\Auth\Events\Verified` [event](/docs/{{version}}/events) during the email verification process. If you are manually handling email verification for your application, you may wish to manually dispatch these events after verification is completed.

---

## Telescope Available Watchers Event Watcher
Telescope | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/telescope.md

### Event Watcher

The event watcher records the payload, listeners, and broadcast data for any [events](/docs/{{version}}/events) dispatched by your application. The Laravel framework's internal events are ignored by the Event watcher.

---

## Events Registering Events and Listeners Event Discovery
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

### Event Discovery

By default, Laravel will automatically find and register your event listeners by scanning your application's `Listeners` directory. When Laravel finds any listener class method that begins with `handle` or `__invoke`, Laravel will register those methods as event listeners for the event that is type-hinted in the method's signature:

```php
use App\Events\PodcastProcessed;

class SendPodcastNotification
{
    /**
     * Handle the event.
     */
    public function handle(PodcastProcessed $event): void
    {
        // ...
    }
}
```

You may listen to multiple events using PHP's union types:

```php
/**
 * Handle the event.
 */
public function handle(PodcastProcessed|PodcastPublished $event): void
{
    // ...
}
```

If you plan to store your listeners in a different directory or within multiple directories, you may instruct Laravel to scan those directories using the `withEvents` method in your application's `bootstrap/app.php` file:

```php
->withEvents(discover: [
    __DIR__.'/../app/Domain/Orders/Listeners',
])
```

You may scan for listeners in multiple similar directories using the `*` character as a wildcard:

```php
->withEvents(discover: [
    __DIR__.'/../app/Domain/*/Listeners',
])
```

The `event:list` command may be used to list all of the listeners registered within your application:

```shell
php artisan event:list
```

---

## Structure The App Directory The Events Directory
Structure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md

### The Events Directory

This directory does not exist by default, but will be created for you by the `event:generate` and `make:event` Artisan commands. The `Events` directory houses [event classes](/docs/{{version}}/events). Events may be used to alert other parts of your application that a given action has occurred, providing a great deal of flexibility and decoupling.

---

## Seeding Writing Seeders Muting Model Events
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

### Muting Model Events

While running seeds, you may want to prevent models from dispatching events. You may achieve this using the `WithoutModelEvents` trait. When used, the `WithoutModelEvents` trait ensures no model events are dispatched, even if additional seed classes are executed via the `call` method:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Run the database seeders.
     */
    public function run(): void
    {
        $this->call([
            UserSeeder::class,
        ]);
    }
}
```

---

## Events Registering Events and Listeners
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

## Registering Events and Listeners

---

## Scheduling Events
Scheduling | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scheduling.md

## Events

Laravel dispatches a variety of [events](/docs/{{version}}/events) during the scheduling process. You may [define listeners](/docs/{{version}}/events) for any of the following events:

| Event Name                                                  |
| ----------------------------------------------------------- |
| `Illuminate\Console\Events\ScheduledTaskStarting`           |
| `Illuminate\Console\Events\ScheduledTaskFinished`           |
| `Illuminate\Console\Events\ScheduledBackgroundTaskFinished` |
| `Illuminate\Console\Events\ScheduledTaskSkipped`            |
| `Illuminate\Console\Events\ScheduledTaskFailed`             |

---

## Scheduling Defining Schedules Preventing Task Overlaps
Scheduling | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scheduling.md

### Preventing Task Overlaps

By default, scheduled tasks will be run even if the previous instance of the task is still running. To prevent this, you may use the `withoutOverlapping` method:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();
```

In this example, the `emails:send` [Artisan command](/docs/{{version}}/artisan) will be run every minute if it is not already running. The `withoutOverlapping` method is especially useful if you have tasks that vary drastically in their execution time, preventing you from predicting exactly how long a given task will take.

If needed, you may specify how many minutes must pass before the "without overlapping" lock expires. By default, the lock will expire after 24 hours:

```php
Schedule::command('emails:send')->withoutOverlapping(10);
```

Behind the scenes, the `withoutOverlapping` method utilizes your application's [cache](/docs/{{version}}/cache) to obtain locks. If necessary, you can clear these cache locks using the `schedule:clear-cache` Artisan command. This is typically only necessary if a task becomes stuck due to an unexpected server problem.

---

## Events Registering Events and Listeners Event Discovery Event Discovery in Production
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

#### Event Discovery in Production

To give your application a speed boost, you should cache a manifest of all of your application's listeners using the `optimize` or `event:cache` Artisan commands. Typically, this command should be run as part of your application's [deployment process](/docs/{{version}}/deployment#optimization). This manifest will be used by the framework to speed up the event registration process. The `event:clear` command may be used to destroy the event cache.

---

## Reverb Events
Reverb | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/reverb.md

## Events

Reverb dispatches internal events during the lifecycle of a connection and message handling. You may [listen for these events](/docs/{{version}}/events) to perform actions when connections are managed or messages are exchanged.

The following events are dispatched by Reverb:

#### `Laravel\Reverb\Events\ChannelCreated`

Dispatched when a channel is created. This typically occurs when the first connection subscribes to a specific channel. The event receives the `Laravel\Reverb\Protocols\Pusher\Channel` instance.

#### `Laravel\Reverb\Events\ChannelRemoved`

Dispatched when a channel is removed. This typically occurs when the last connection unsubscribes from a channel. The event receives the `Laravel\Reverb\Protocols\Pusher\Channel` instance.

#### `Laravel\Reverb\Events\ConnectionPruned`

Dispatched when a stale connection is pruned by the server. The event receives the `Laravel\Reverb\Contracts\Connection` instance.

#### `Laravel\Reverb\Events\MessageReceived`

Dispatched when a message is received from a client connection. The event receives the `Laravel\Reverb\Contracts\Connection` instance and the raw string `$message`.

#### `Laravel\Reverb\Events\MessageSent`

Dispatched when a message is sent to a client connection. The event receives the `Laravel\Reverb\Contracts\Connection` instance and the raw string `$message`.

---

## Reverb Running Reverb in Production Event Loop
Reverb | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/reverb.md

### Event Loop

Under the hood, Reverb uses a ReactPHP event loop to manage WebSocket connections on the server. By default, this event loop is powered by `stream_select`, which doesn't require any additional extensions. However, `stream_select` is typically limited to 1,024 open files. As such, if you plan to handle more than 1,000 concurrent connections, you will need to use an alternative event loop not bound to the same restrictions.

Reverb will automatically switch to an `ext-uv` powered loop when available. This PHP extension is available for install via PECL:

```shell
pecl install uv
```

---

## Events Queued Event Listeners
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

## Queued Event Listeners

Queueing listeners can be beneficial if your listener is going to perform a slow task such as sending an email or making an HTTP request. Before using queued listeners, make sure to [configure your queue](/docs/{{version}}/queues) and start a queue worker on your server or local development environment.

To specify that a listener should be queued, add the `ShouldQueue` interface to the listener class. Listeners generated by the `make:listener` Artisan commands already have this interface imported into the current namespace so you can use it immediately:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```

That's it! Now, when an event handled by this listener is dispatched, the listener will automatically be queued by the event dispatcher using Laravel's [queue system](/docs/{{version}}/queues). If no exceptions are thrown when the listener is executed by the queue, the queued job will automatically be deleted after it has finished processing.

---

## Responses Streamed Responses Event Streams (SSE) Consuming Event Streams
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

#### Consuming Event Streams

Event streams may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the `@laravel/stream-react`, `@laravel/stream-vue`, or `@laravel/stream-svelte` package:

```shell tab=React
npm install @laravel/stream-react
```

```shell tab=Vue
npm install @laravel/stream-vue
```

```shell tab=Svelte
npm install @laravel/stream-svelte
```

Then, `useEventStream` may be used to consume the event stream. After providing your stream URL, the hook will automatically update the `message` with the concatenated response as messages are returned from your Laravel application:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/chat");

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat");
</script>

<template>
  <div>{{ message }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat");
</script>

<div>{$eventStream.message}</div>
```

The second argument given to `useEventStream` is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/stream", {
    eventName: "update",
    onMessage: (message) => {
      //
    },
    onError: (error) => {
      //
    },
    onComplete: () => {
      //
    },
    endSignal: "</stream>",
    glue: " ",
  });

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat", {
  eventName: "update",
  onMessage: (message) => {
    // ...
  },
  onError: (error) => {
    // ...
  },
  onComplete: () => {
    // ...
  },
  endSignal: "</stream>",
  glue: " ",
});
</script>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat", {
    eventName: "update",
    onMessage: (event) => {
        //
    },
    onError: (error) => {
        //
    },
    onComplete: () => {
        //
    },
    endSignal: "</stream>",
    glue: " ",
    replace: false,
});
</script>
```

Event streams may also be manually consumed via an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) object by your application's frontend. The `eventStream` method will automatically send a `</stream>` update to the event stream when the stream is complete:

```js
const source = new EventSource('/chat');

source.addEventListener('update', (event) => {
    if (event.data === '</stream>') {
        source.close();

        return;
    }

    console.log(event.data);
});
```

To customize the final event that is sent to the event stream, you may provide a `StreamedEvent` instance to the `eventStream` method's `endStreamWith` argument:

```php
return response()->eventStream(function () {
    // ...
}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));
```

---

## Responses Streamed Responses Event Streams (SSE)
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

### Event Streams (SSE)

The `eventStream` method may be used to return a server-sent events (SSE) streamed response using the `text/event-stream` content type. The `eventStream` method accepts a closure which should [yield](https://www.php.net/manual/en/language.generators.overview.php) responses to the stream as the responses become available:

```php
Route::get('/chat', function () {
    return response()->eventStream(function () {
        $stream = OpenAI::client()->chat()->createStreamed(...);

        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});
```

If you would like to customize the name of the event, you may yield an instance of the `StreamedEvent` class:

```php
use Illuminate\Http\StreamedEvent;

yield new StreamedEvent(
    event: 'update',
    data: $response->choices[0],
);
```

---

## Events Registering Events and Listeners Closure Listeners Queueable Anonymous Event Listeners
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

#### Queueable Anonymous Event Listeners

When registering closure-based event listeners, you may wrap the listener closure within the `Illuminate\Events\queueable` function to instruct Laravel to execute the listener using the [queue](/docs/{{version}}/queues):

```php
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(queueable(function (PodcastProcessed $event) {
        // ...
    }));
}
```

Like queued jobs, you may use the `onConnection`, `onQueue`, and `delay` methods to customize the execution of the queued listener:

```php
Event::listen(queueable(function (PodcastProcessed $event) {
    // ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->plus(seconds: 10)));
```

If you would like to handle anonymous queued listener failures, you may provide a closure to the `catch` method while defining the `queueable` listener. This closure will receive the event instance and the `Throwable` instance that caused the listener's failure:

```php
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;

Event::listen(queueable(function (PodcastProcessed $event) {
    // ...
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // The queued listener failed...
}));
```

---

## Queues Job Events
Queues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md

## Job Events

Using the `before` and `after` methods on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks to be executed before or after a queued job is processed. These callbacks are a great opportunity to perform additional logging or increment statistics for a dashboard. Typically, you should call these methods from the `boot` method of a [service provider](/docs/{{version}}/providers). For example, we may use the `AppServiceProvider` that is included with Laravel:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Queue::before(function (JobProcessing $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });

        Queue::after(function (JobProcessed $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });
    }
}
```

Using the `looping` method on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks that execute before the worker attempts to fetch a job from a queue. For example, you might register a closure to rollback any transactions that were left open by a previously failed job:

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;

Queue::looping(function () {
    while (DB::transactionLevel() > 0) {
        DB::rollBack();
    }
});
```

---

## Queues Dealing With Failed Jobs Failed Job Events
Queues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md

### Failed Job Events

If you would like to register an event listener that will be invoked when a job fails, you may use the `Queue` facade's `failing` method. For example, we may attach a closure to this event from the `boot` method of the `AppServiceProvider` that is included with Laravel:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobFailed;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->exception
        });
    }
}
```

---

## Events
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

# Events

- [Introduction](#introduction)
- [Generating Events and Listeners](#generating-events-and-listeners)
- [Registering Events and Listeners](#registering-events-and-listeners)
    - [Event Discovery](#event-discovery)
    - [Manually Registering Events](#manually-registering-events)
    - [Closure Listeners](#closure-listeners)
- [Defining Events](#defining-events)
- [Defining Listeners](#defining-listeners)
- [Queued Event Listeners](#queued-event-listeners)
    - [Manually Interacting With the Queue](#manually-interacting-with-the-queue)
    - [Queued Event Listeners and Database Transactions](#queued-event-listeners-and-database-transactions)
    - [Queued Listener Middleware](#queued-listener-middleware)
    - [Encrypted Queued Listeners](#encrypted-queued-listeners)
    - [Unique Event Listeners](#unique-event-listeners)
        - [Keeping Listeners Unique Until Processing Begins](#keeping-listeners-unique-until-processing-begins)
        - [Unique Listener Locks](#unique-listener-locks)
    - [Handling Failed Jobs](#handling-failed-jobs)
- [Dispatching Events](#dispatching-events)
    - [Dispatching Events After Database Transactions](#dispatching-events-after-database-transactions)
    - [Deferring Events](#deferring-events)
- [Event Subscribers](#event-subscribers)
    - [Writing Event Subscribers](#writing-event-subscribers)
    - [Registering Event Subscribers](#registering-event-subscribers)
- [Testing](#testing)
    - [Faking a Subset of Events](#faking-a-subset-of-events)
    - [Scoped Events Fakes](#scoped-event-fakes)

---

## Queues Job Middleware Preventing Job Overlaps Sharing Lock Keys Across Job Classes
Queues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md

#### Sharing Lock Keys Across Job Classes

By default, the `WithoutOverlapping` middleware will only prevent overlapping jobs of the same class. So, although two different job classes may use the same lock key, they will not be prevented from overlapping. However, you can instruct Laravel to apply the key across job classes using the `shared` method:

```php
use Illuminate\Queue\Middleware\WithoutOverlapping;

class ProviderIsDown
{
    // ...

    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}

class ProviderIsUp
{
    // ...

    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}
```

---

## Events Registering Events and Listeners Manually Registering Events
Events | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md

### Manually Registering Events

Using the `Event` facade, you may manually register events and their corresponding listeners within the `boot` method of your application's `AppServiceProvider`:

```php
use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(
        PodcastProcessed::class,
        SendPodcastNotification::class,
    );
}
```

The `event:list` command may be used to list all of the listeners registered within your application:

```shell
php artisan event:list
```

---

## Queues Job Middleware Preventing Job Overlaps
Queues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md

### Preventing Job Overlaps

Laravel includes an `Illuminate\Queue\Middleware\WithoutOverlapping` middleware that allows you to prevent job overlaps based on an arbitrary key. This can be helpful when a queued job is modifying a resource that should only be modified by one job at a time.

For example, let's imagine you have a queued job that updates a user's credit score and you want to prevent credit score update job overlaps for the same user ID. To accomplish this, you can return the `WithoutOverlapping` middleware from your job's `middleware` method:

```php
use Illuminate\Queue\Middleware\WithoutOverlapping;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new WithoutOverlapping($this->user->id)];
}
```

Releasing an overlapping job back onto the queue will still increment the job's total number of attempts. You may wish to tune your `tries` and `maxExceptions` properties on your job class accordingly. For example, leaving the `tries` property to 1 as it is by default would prevent any overlapping job from being retried later.

Any overlapping jobs of the same type will be released back to the queue. You may also specify the number of seconds that must elapse before the released job will be attempted again:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];
}
```

If you wish to immediately delete any overlapping jobs so that they will not be retried, you may use the `dontRelease` method:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->dontRelease()];
}
```

The `WithoutOverlapping` middleware is powered by Laravel's atomic lock feature. Sometimes, your job may unexpectedly fail or timeout in such a way that the lock is not released. Therefore, you may explicitly define a lock expiration time using the `expireAfter` method. For example, the example below will instruct Laravel to release the `WithoutOverlapping` lock three minutes after the job has started processing:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];
}
```

> [!WARNING]
> The `WithoutOverlapping` middleware requires a cache driver that supports [locks](/docs/{{version}}/cache#atomic-locks). Currently, the `memcached`, `redis`, `dynamodb`, `database`, `file`, and `array` cache drivers support atomic locks.

_[Truncated]_


output:
Wall time: 2.2946 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Verification Events\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n## Events\n\nWhen using the [Laravel application starter kits](/docs/{{version}}/starter-kits), Laravel dispatches an `Illuminate\\Auth\\Events\\Verified` [event](/docs/{{version}}/events) during the email verification process. If you are manually handling email verification for your application, you may wish to manually dispatch these events after verification is completed.\n\n---\n\n## Telescope Available Watchers Event Watcher\nTelescope | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/telescope.md\n\n\n### Event Watcher\n\nThe event watcher records the payload, listeners, and broadcast data for any [events](/docs/{{version}}/events) dispatched by your application. The Laravel framework's internal events are ignored by the Event watcher.\n\n---\n\n## Events Registering Events and Listeners Event Discovery\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n### Event Discovery\n\nBy default, Laravel will automatically find and register your event listeners by scanning your application's `Listeners` directory. When Laravel finds any listener class method that begins with `handle` or `__invoke`, Laravel will register those methods as event listeners for the event that is type-hinted in the method's signature:\n\n```php\nuse App\\Events\\PodcastProcessed;\n\nclass SendPodcastNotification\n{\n    /**\n     * Handle the event.\n     */\n    public function handle(PodcastProcessed $event): void\n    {\n        // ...\n    }\n}\n```\n\nYou may listen to multiple events using PHP's union types:\n\n```php\n/**\n * Handle the event.\n */\npublic function handle(PodcastProcessed|PodcastPublished $event): void\n{\n    // ...\n}\n```\n\nIf you plan to store your listeners in a different directory or within multiple directories, you may instruct Laravel to scan those directories using the `withEvents` method in your application's `bootstrap/app.php` file:\n\n```php\n->withEvents(discover: [\n    __DIR__.'/../app/Domain/Orders/Listeners',\n])\n```\n\nYou may scan for listeners in multiple similar directories using the `*` character as a wildcard:\n\n```php\n->withEvents(discover: [\n    __DIR__.'/../app/Domain/*/Listeners',\n])\n```\n\nThe `event:list` command may be used to list all of the listeners registered within your application:\n\n```shell\nphp artisan event:list\n```\n\n---\n\n## Structure The App Directory The Events Directory\nStructure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md\n\n\n### The Events Directory\n\nThis directory does not exist by default, but will be created for you by the `event:generate` and `make:event` Artisan commands. The `Events` directory houses [event classes](/docs/{{version}}/events). Events may be used to alert other parts of your application that a given action has occurred, providing a great deal of flexibility and decoupling.\n\n---\n\n## Seeding Writing Seeders Muting Model Events\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n### Muting Model Events\n\nWhile running seeds, you may want to prevent models from dispatching events. You may achieve this using the `WithoutModelEvents` trait. When used, the `WithoutModelEvents` trait ensures no model events are dispatched, even if additional seed classes are executed via the `call` method:\n\n```php\n<?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse Illuminate\\Database\\Console\\Seeds\\WithoutModelEvents;\n\nclass DatabaseSeeder extends Seeder\n{\n    use WithoutModelEvents;\n\n    /**\n     * Run the database seeders.\n     */\n    public function run(): void\n    {\n        $this->call([\n            UserSeeder::class,\n        ]);\n    }\n}\n```\n\n---\n\n## Events Registering Events and Listeners\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n## Registering Events and Listeners\n\n---\n\n## Scheduling Events\nScheduling | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scheduling.md\n\n\n## Events\n\nLaravel dispatches a variety of [events](/docs/{{version}}/events) during the scheduling process. You may [define listeners](/docs/{{version}}/events) for any of the following events:\n\n
\n\n| Event Name                                                  |\n| ----------------------------------------------------------- |\n| `Illuminate\\Console\\Events\\ScheduledTaskStarting`           |\n| `Illuminate\\Console\\Events\\ScheduledTaskFinished`           |\n| `Illuminate\\Console\\Events\\ScheduledBackgroundTaskFinished` |\n| `Illuminate\\Console\\Events\\ScheduledTaskSkipped`            |\n| `Illuminate\\Console\\Events\\ScheduledTaskFailed`             |\n\n
\n\n---\n\n## Scheduling Defining Schedules Preventing Task Overlaps\nScheduling | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scheduling.md\n\n\n### Preventing Task Overlaps\n\nBy default, scheduled tasks will be run even if the previous instance of the task is still running. To prevent this, you may use the `withoutOverlapping` method:\n\n```php\nuse Illuminate\\Support\\Facades\\Schedule;\n\nSchedule::command('emails:send')->withoutOverlapping();\n```\n\nIn this example, the `emails:send` [Artisan command](/docs/{{version}}/artisan) will be run every minute if it is not already running. The `withoutOverlapping` method is especially useful if you have tasks that vary drastically in their execution time, preventing you from predicting exactly how long a given task will take.\n\nIf needed, you may specify how many minutes must pass before the \"without overlapping\" lock expires. By default, the lock will expire after 24 hours:\n\n```php\nSchedule::command('emails:send')->withoutOverlapping(10);\n```\n\nBehind the scenes, the `withoutOverlapping` method utilizes your application's [cache](/docs/{{version}}/cache) to obtain locks. If necessary, you can clear these cache locks using the `schedule:clear-cache` Artisan command. This is typically only necessary if a task becomes stuck due to an unexpected server problem.\n\n---\n\n## Events Registering Events and Listeners Event Discovery Event Discovery in Production\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n#### Event Discovery in Production\n\nTo give your application a speed boost, you should cache a manifest of all of your application's listeners using the `optimize` or `event:cache` Artisan commands. Typically, this command should be run as part of your application's [deployment process](/docs/{{version}}/deployment#optimization). This manifest will be used by the framework to speed up the event registration process. The `event:clear` command may be used to destroy the event cache.\n\n---\n\n## Reverb Events\nReverb | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/reverb.md\n\n\n## Events\n\nReverb dispatches internal events during the lifecycle of a connection and message handling. You may [listen for these events](/docs/{{version}}/events) to perform actions when connections are managed or messages are exchanged.\n\nThe following events are dispatched by Reverb:\n\n#### `Laravel\\Reverb\\Events\\ChannelCreated`\n\nDispatched when a channel is created. This typically occurs when the first connection subscribes to a specific channel. The event receives the `Laravel\\Reverb\\Protocols\\Pusher\\Channel` instance.\n\n#### `Laravel\\Reverb\\Events\\ChannelRemoved`\n\nDispatched when a channel is removed. This typically occurs when the last connection unsubscribes from a channel. The event receives the `Laravel\\Reverb\\Protocols\\Pusher\\Channel` instance.\n\n#### `Laravel\\Reverb\\Events\\ConnectionPruned`\n\nDispatched when a stale connection is pruned by the server. The event receives the `Laravel\\Reverb\\Contracts\\Connection` instance.\n\n#### `Laravel\\Reverb\\Events\\MessageReceived`\n\nDispatched when a message is received from a client connection. The event receives the `Laravel\\Reverb\\Contracts\\Connection` instance and the raw string `$message`.\n\n#### `Laravel\\Reverb\\Events\\MessageSent`\n\nDispatched when a message is sent to a client connection. The event receives the `Laravel\\Reverb\\Contracts\\Connection` instance and the raw string `$message`.\n\n---\n\n## Reverb Running Reverb in Production Event Loop\nReverb | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/reverb.md\n\n\n### Event Loop\n\nUnder the hood, Reverb uses a ReactPHP event loop to manage WebSocket connections on the server. By default, this event loop is powered by `stream_select`, which doesn't require any additional extensions. However, `stream_select` is typically limited to 1,024 open files. As such, if you plan to handle more than 1,000 concurrent connections, you will need to use an alternative event loop not bound to the same restrictions.\n\nReverb will automatically switch to an `ext-uv` powered loop when available. This PHP extension is available for install via PECL:\n\n```shell\npecl install uv\n```\n\n---\n\n## Events Queued Event Listeners\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n## Queued Event Listeners\n\nQueueing listeners can be beneficial if your listener is going to perform a slow task such as sending an email or making an HTTP request. Before using queued listeners, make sure to [configure your queue](/docs/{{version}}/queues) and start a queue worker on your server or local development environment.\n\nTo specify that a listener should be queued, add the `ShouldQueue` interface to the listener class. Listeners generated by the `make:listener` Artisan commands already have this interface imported into the current namespace so you can use it immediately:\n\n```php\n<?php\n\nnamespace App\\Listeners;\n\nuse App\\Events\\OrderShipped;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\n\nclass SendShipmentNotification implements ShouldQueue\n{\n    // ...\n}\n```\n\nThat's it! Now, when an event handled by this listener is dispatched, the listener will automatically be queued by the event dispatcher using Laravel's [queue system](/docs/{{version}}/queues). If no exceptions are thrown when the listener is executed by the queue, the queued job will automatically be deleted after it has finished processing.\n\n---\n\n## Responses Streamed Responses Event Streams (SSE) Consuming Event Streams\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n#### Consuming Event Streams\n\nEvent streams may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the `@laravel/stream-react`, `@laravel/stream-vue`, or `@laravel/stream-svelte` package:\n\n```shell tab=React\nnpm install @laravel/stream-react\n```\n\n```shell tab=Vue\nnpm install @laravel/stream-vue\n```\n\n```shell tab=Svelte\nnpm install @laravel/stream-svelte\n```\n\nThen, `useEventStream` may be used to consume the event stream. After providing your stream URL, the hook will automatically update the `message` with the concatenated response as messages are returned from your Laravel application:\n\n```jsx tab=React\nimport { useEventStream } from \"@laravel/stream-react\";\n\nfunction App() {\n  const { message } = useEventStream(\"/chat\");\n\n  return <div>{message}</div>;\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useEventStream } from \"@laravel/stream-vue\";\n\nconst { message } = useEventStream(\"/chat\");\n</script>\n\n<template>\n  <div>{{ message }}</div>\n</template>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useEventStream } from \"@laravel/stream-svelte\";\n\nconst eventStream = useEventStream(\"/chat\");\n</script>\n\n<div>{$eventStream.message}</div>\n```\n\nThe second argument given to `useEventStream` is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:\n\n```jsx tab=React\nimport { useEventStream } from \"@laravel/stream-react\";\n\nfunction App() {\n  const { message } = useEventStream(\"/stream\", {\n    eventName: \"update\",\n    onMessage: (message) => {\n      //\n    },\n    onError: (error) => {\n      //\n    },\n    onComplete: () => {\n      //\n    },\n    endSignal: \"</stream>\",\n    glue: \" \",\n  });\n\n  return <div>{message}</div>;\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useEventStream } from \"@laravel/stream-vue\";\n\nconst { message } = useEventStream(\"/chat\", {\n  eventName: \"update\",\n  onMessage: (message) => {\n    // ...\n  },\n  onError: (error) => {\n    // ...\n  },\n  onComplete: () => {\n    // ...\n  },\n  endSignal: \"</stream>\",\n  glue: \" \",\n});\n</script>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useEventStream } from \"@laravel/stream-svelte\";\n\nconst eventStream = useEventStream(\"/chat\", {\n    eventName: \"update\",\n    onMessage: (event) => {\n        //\n    },\n    onError: (error) => {\n        //\n    },\n    onComplete: () => {\n        //\n    },\n    endSignal: \"</stream>\",\n    glue: \" \",\n    replace: false,\n});\n</script>\n```\n\nEvent streams may also be manually consumed via an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) object by your application's frontend. The `eventStream` method will automatically send a `</stream>` update to the event stream when the stream is complete:\n\n```js\nconst source = new EventSource('/chat');\n\nsource.addEventListener('update', (event) => {\n    if (event.data === '</stream>') {\n        source.close();\n\n        return;\n    }\n\n    console.log(event.data);\n});\n```\n\nTo customize the final event that is sent to the event stream, you may provide a `StreamedEvent` instance to the `eventStream` method's `endStreamWith` argument:\n\n```php\nreturn response()->eventStream(function () {\n    // ...\n}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));\n```\n\n---\n\n## Responses Streamed Responses Event Streams (SSE)\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### Event Streams (SSE)\n\nThe `eventStream` method may be used to return a server-sent events (SSE) streamed response using the `text/event-stream` content type. The `eventStream` method accepts a closure which should [yield](https://www.php.net/manual/en/language.generators.overview.php) responses to the stream as the responses become available:\n\n```php\nRoute::get('/chat', function () {\n    return response()->eventStream(function () {\n        $stream = OpenAI::client()->chat()->createStreamed(...);\n\n        foreach ($stream as $response) {\n            yield $response->choices[0];\n        }\n    });\n});\n```\n\nIf you would like to customize the name of the event, you may yield an instance of the `StreamedEvent` class:\n\n```php\nuse Illuminate\\Http\\StreamedEvent;\n\nyield new StreamedEvent(\n    event: 'update',\n    data: $response->choices[0],\n);\n```\n\n---\n\n## Events Registering Events and Listeners Closure Listeners Queueable Anonymous Event Listeners\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n#### Queueable Anonymous Event Listeners\n\nWhen registering closure-based event listeners, you may wrap the listener closure within the `Illuminate\\Events\\queueable` function to instruct Laravel to execute the listener using the [queue](/docs/{{version}}/queues):\n\n```php\nuse App\\Events\\PodcastProcessed;\nuse function Illuminate\\Events\\queueable;\nuse Illuminate\\Support\\Facades\\Event;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Event::listen(queueable(function (PodcastProcessed $event) {\n        // ...\n    }));\n}\n```\n\nLike queued jobs, you may use the `onConnection`, `onQueue`, and `delay` methods to customize the execution of the queued listener:\n\n```php\nEvent::listen(queueable(function (PodcastProcessed $event) {\n    // ...\n})->onConnection('redis')->onQueue('podcasts')->delay(now()->plus(seconds: 10)));\n```\n\nIf you would like to handle anonymous queued listener failures, you may provide a closure to the `catch` method while defining the `queueable` listener. This closure will receive the event instance and the `Throwable` instance that caused the listener's failure:\n\n```php\nuse App\\Events\\PodcastProcessed;\nuse function Illuminate\\Events\\queueable;\nuse Illuminate\\Support\\Facades\\Event;\nuse Throwable;\n\nEvent::listen(queueable(function (PodcastProcessed $event) {\n    // ...\n})->catch(function (PodcastProcessed $event, Throwable $e) {\n    // The queued listener failed...\n}));\n```\n\n---\n\n## Queues Job Events\nQueues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md\n\n\n## Job Events\n\nUsing the `before` and `after` methods on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks to be executed before or after a queued job is processed. These callbacks are a great opportunity to perform additional logging or increment statistics for a dashboard. Typically, you should call these methods from the `boot` method of a [service provider](/docs/{{version}}/providers). For example, we may use the `AppServiceProvider` that is included with Laravel:\n\n```php\n<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\Facades\\Queue;\nuse Illuminate\\Support\\ServiceProvider;\nuse Illuminate\\Queue\\Events\\JobProcessed;\nuse Illuminate\\Queue\\Events\\JobProcessing;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    /**\n     * Register any application services.\n     */\n    public function register(): void\n    {\n        // ...\n    }\n\n    /**\n     * Bootstrap any application services.\n     */\n    public function boot(): void\n    {\n        Queue::before(function (JobProcessing $event) {\n            // $event->connectionName\n            // $event->job\n            // $event->job->payload()\n        });\n\n        Queue::after(function (JobProcessed $event) {\n            // $event->connectionName\n            // $event->job\n            // $event->job->payload()\n        });\n    }\n}\n```\n\nUsing the `looping` method on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks that execute before the worker attempts to fetch a job from a queue. For example, you might register a closure to rollback any transactions that were left open by a previously failed job:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Queue;\n\nQueue::looping(function () {\n    while (DB::transactionLevel() > 0) {\n        DB::rollBack();\n    }\n});\n```\n\n---\n\n## Queues Dealing With Failed Jobs Failed Job Events\nQueues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md\n\n\n### Failed Job Events\n\nIf you would like to register an event listener that will be invoked when a job fails, you may use the `Queue` facade's `failing` method. For example, we may attach a closure to this event from the `boot` method of the `AppServiceProvider` that is included with Laravel:\n\n```php\n<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\Facades\\Queue;\nuse Illuminate\\Support\\ServiceProvider;\nuse Illuminate\\Queue\\Events\\JobFailed;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    /**\n     * Register any application services.\n     */\n    public function register(): void\n    {\n        // ...\n    }\n\n    /**\n     * Bootstrap any application services.\n     */\n    public function boot(): void\n    {\n        Queue::failing(function (JobFailed $event) {\n            // $event->connectionName\n            // $event->job\n            // $event->exception\n        });\n    }\n}\n```\n\n---\n\n## Events\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n# Events\n\n- [Introduction](#introduction)\n- [Generating Events and Listeners](#generating-events-and-listeners)\n- [Registering Events and Listeners](#registering-events-and-listeners)\n    - [Event Discovery](#event-discovery)\n    - [Manually Registering Events](#manually-registering-events)\n    - [Closure Listeners](#closure-listeners)\n- [Defining Events](#defining-events)\n- [Defining Listeners](#defining-listeners)\n- [Queued Event Listeners](#queued-event-listeners)\n    - [Manually Interacting With the Queue](#manually-interacting-with-the-queue)\n    - [Queued Event Listeners and Database Transactions](#queued-event-listeners-and-database-transactions)\n    - [Queued Listener Middleware](#queued-listener-middleware)\n    - [Encrypted Queued Listeners](#encrypted-queued-listeners)\n    - [Unique Event Listeners](#unique-event-listeners)\n        - [Keeping Listeners Unique Until Processing Begins](#keeping-listeners-unique-until-processing-begins)\n        - [Unique Listener Locks](#unique-listener-locks)\n    - [Handling Failed Jobs](#handling-failed-jobs)\n- [Dispatching Events](#dispatching-events)\n    - [Dispatching Events After Database Transactions](#dispatching-events-after-database-transactions)\n    - [Deferring Events](#deferring-events)\n- [Event Subscribers](#event-subscribers)\n    - [Writing Event Subscribers](#writing-event-subscribers)\n    - [Registering Event Subscribers](#registering-event-subscribers)\n- [Testing](#testing)\n    - [Faking a Subset of Events](#faking-a-subset-of-events)\n    - [Scoped Events Fakes](#scoped-event-fakes)\n\n---\n\n## Queues Job Middleware Preventing Job Overlaps Sharing Lock Keys Across Job Classes\nQueues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md\n\n\n#### Sharing Lock Keys Across Job Classes\n\nBy default, the `WithoutOverlapping` middleware will only prevent overlapping jobs of the same class. So, although two different job classes may use the same lock key, they will not be prevented from overlapping. However, you can instruct Laravel to apply the key across job classes using the `shared` method:\n\n```php\nuse Illuminate\\Queue\\Middleware\\WithoutOverlapping;\n\nclass ProviderIsDown\n{\n    // ...\n\n    public function middleware(): array\n    {\n        return [\n            (new WithoutOverlapping(\"status:{$this->provider}\"))->shared(),\n        ];\n    }\n}\n\nclass ProviderIsUp\n{\n    // ...\n\n    public function middleware(): array\n    {\n        return [\n            (new WithoutOverlapping(\"status:{$this->provider}\"))->shared(),\n        ];\n    }\n}\n```\n\n---\n\n## Events Registering Events and Listeners Manually Registering Events\nEvents | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/events.md\n\n\n### Manually Registering Events\n\nUsing the `Event` facade, you may manually register events and their corresponding listeners within the `boot` method of your application's `AppServiceProvider`:\n\n```php\nuse App\\Domain\\Orders\\Events\\PodcastProcessed;\nuse App\\Domain\\Orders\\Listeners\\SendPodcastNotification;\nuse Illuminate\\Support\\Facades\\Event;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Event::listen(\n        PodcastProcessed::class,\n        SendPodcastNotification::class,\n    );\n}\n```\n\nThe `event:list` command may be used to list all of the listeners registered within your application:\n\n```shell\nphp artisan event:list\n```\n\n---\n\n## Queues Job Middleware Preventing Job Overlaps\nQueues | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queues.md\n\n\n### Preventing Job Overlaps\n\nLaravel includes an `Illuminate\\Queue\\Middleware\\WithoutOverlapping` middleware that allows you to prevent job overlaps based on an arbitrary key. This can be helpful when a queued job is modifying a resource that should only be modified by one job at a time.\n\nFor example, let's imagine you have a queued job that updates a user's credit score and you want to prevent credit score update job overlaps for the same user ID. To accomplish this, you can return the `WithoutOverlapping` middleware from your job's `middleware` method:\n\n```php\nuse Illuminate\\Queue\\Middleware\\WithoutOverlapping;\n\n/**\n * Get the middleware the job should pass through.\n *\n * @return array<int, object>\n */\npublic function middleware(): array\n{\n    return [new WithoutOverlapping($this->user->id)];\n}\n```\n\nReleasing an overlapping job back onto the queue will still increment the job's total number of attempts. You may wish to tune your `tries` and `maxExceptions` properties on your job class accordingly. For example, leaving the `tries` property to 1 as it is by default would prevent any overlapping job from being retried later.\n\nAny overlapping jobs of the same type will be released back to the queue. You may also specify the number of seconds that must elapse before the released job will be attempted again:\n\n```php\n/**\n * Get the middleware the job should pass through.\n *\n * @return array<int, object>\n */\npublic function middleware(): array\n{\n    return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];\n}\n```\n\nIf you wish to immediately delete any overlapping jobs so that they will not be retried, you may use the `dontRelease` method:\n\n```php\n/**\n * Get the middleware the job should pass through.\n *\n * @return array<int, object>\n */\npublic function middleware(): array\n{\n    return [(new WithoutOverlapping($this->order->id))->dontRelease()];\n}\n```\n\nThe `WithoutOverlapping` middleware is powered by Laravel's atomic lock feature. Sometimes, your job may unexpectedly fail or timeout in such a way that the lock is not released. Therefore, you may explicitly define a lock expiration time using the `expireAfter` method. For example, the example below will instruct Laravel to release the `WithoutOverlapping` lock three minutes after the job has started processing:\n\n```php\n/**\n * Get the middleware the job should pass through.\n *\n * @return array<int, object>\n */\npublic function middleware(): array\n{\n    return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];\n}\n```\n\n> [!WARNING]\n> The `WithoutOverlapping` middleware requires a cache driver that supports [locks](/docs/{{version}}/cache#atomic-locks). Currently, the `memcached`, `redis`, `dynamodb`, `database`, `file`, and `array` cache drivers support atomic locks.\n\n_[Truncated]_"}]


cmd: sed -n '1,220p' app/Observers/ProductObserver.php


command: sed -n '1,220p' app/Observers/ProductObserver.php

aggregated_output:
<?php

namespace App\Observers;

use App\Models\Product;
use App\Services\SearchService;

class ProductObserver
{
    public function created(Product $product): void
    {
        app(SearchService::class)->syncProduct($product);
    }

    public function updated(Product $product): void
    {
        app(SearchService::class)->syncProduct($product);
    }

    public function deleted(Product $product): void
    {
        app(SearchService::class)->removeProduct($product->getKey());
    }

    public function forceDeleted(Product $product): void
    {
        app(SearchService::class)->removeProduct($product->getKey());
    }
}


output:
Chunk ID: b596f0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 159
Output:
<?php

namespace App\Observers;

use App\Models\Product;
use App\Services\SearchService;

class ProductObserver
{
    public function created(Product $product): void
    {
        app(SearchService::class)->syncProduct($product);
    }

    public function updated(Product $product): void
    {
        app(SearchService::class)->syncProduct($product);
    }

    public function deleted(Product $product): void
    {
        app(SearchService::class)->removeProduct($product->getKey());
    }

    public function forceDeleted(Product $product): void
    {
        app(SearchService::class)->removeProduct($product->getKey());
    }
}


command:
/bin/zsh
-lc
ls database/migrations | tail -30

cmd: ls database/migrations

path: migrations


output:
Chunk ID: 07a65c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 364
Output:
2026_05_03_213900_create_variant_option_values_table.php
2026_05_03_213901_create_inventory_items_table.php
2026_05_03_213902_create_collections_table.php
2026_05_03_213903_create_collection_products_table.php
2026_05_03_213904_create_product_media_table.php
2026_05_03_223824_create_themes_table.php
2026_05_03_223830_create_theme_files_table.php
2026_05_03_223837_create_theme_settings_table.php
2026_05_03_223841_create_pages_table.php
2026_05_03_223844_create_navigation_menus_table.php
2026_05_03_223848_create_navigation_items_table.php
2026_05_03_225400_create_carts_table.php
2026_05_03_225405_create_cart_lines_table.php
2026_05_03_225409_create_checkouts_table.php
2026_05_03_225413_create_shipping_zones_table.php
2026_05_03_225418_create_shipping_rates_table.php
2026_05_03_225422_create_tax_settings_table.php
2026_05_03_225427_create_discounts_table.php
2026_05_04_000408_create_customer_addresses_table.php
2026_05_04_000408_create_orders_table.php
2026_05_04_000410_create_order_lines_table.php
2026_05_04_000411_create_payments_table.php
2026_05_04_000412_create_refunds_table.php
2026_05_04_000413_create_fulfillments_table.php
2026_05_04_000414_create_fulfillment_lines_table.php
2026_05_04_030847_create_search_settings_table.php
2026_05_04_030848_create_products_fts_table.php
2026_05_04_030848_create_search_queries_table.php
2026_05_04_032640_create_analytics_events_table.php
2026_05_04_032641_create_analytics_dailies_table.php


message: I’ve got enough context to make the slice concrete. I’m going to add the six app/webhook tables and models, webhook signing/delivery with retry metadata, event dispatch wiring for existing domain events, and the two admin pages with focused Pest and browser checks.

phase: commentary


type: message

role: assistant

text: I’ve got enough context to make the slice concrete. I’m going to add the six app/webhook tables and models, webhook signing/delivery with retry metadata, event dispatch wiring for existing domain events, and the two admin pages with focused Pest and browser checks.


cmd: php artisan make:enum AppStatus --no-interaction


output:
Chunk ID: 7fcd1e
Wall time: 0.0319 seconds
Process exited with code 0
Original token count: 17
Output:

   INFO  Enum [app/Enums/AppStatus.php] created successfully.


command: php artisan make:enum AppStatus --no-interaction

type: unknown

aggregated_output: INFO  Enum [app/Enums/AppStatus.php] created successfully.


cmd: php artisan make:enum AppInstallationStatus --no-interaction


output:
Chunk ID: 7d166d
Wall time: 0.0264 seconds
Process exited with code 0
Original token count: 20
Output:

   INFO  Enum [app/Enums/AppInstallationStatus.php] created successfully.


command: php artisan make:enum AppInstallationStatus --no-interaction

aggregated_output: INFO  Enum [app/Enums/AppInstallationStatus.php] created successfully.


cmd: php artisan make:enum WebhookSubscriptionStatus --no-interaction


output:
Chunk ID: dd9ea9
Wall time: 0.0305 seconds
Process exited with code 0
Original token count: 21
Output:

   INFO  Enum [app/Enums/WebhookSubscriptionStatus.php] created successfully.


command: php artisan make:enum WebhookSubscriptionStatus --no-interaction

aggregated_output: INFO  Enum [app/Enums/WebhookSubscriptionStatus.php] created successfully.


cmd: php artisan make:enum WebhookDeliveryStatus --no-interaction


output:
Chunk ID: 057374
Wall time: 0.0270 seconds
Process exited with code 0
Original token count: 20
Output:

   INFO  Enum [app/Enums/WebhookDeliveryStatus.php] created successfully.


command: php artisan make:enum WebhookDeliveryStatus --no-interaction

aggregated_output: INFO  Enum [app/Enums/WebhookDeliveryStatus.php] created successfully.


cmd: php artisan make:enum WebhookEventType --no-interaction


output:
Chunk ID: 32ed83
Wall time: 0.0203 seconds
Process exited with code 0
Original token count: 19
Output:

   INFO  Enum [app/Enums/WebhookEventType.php] created successfully.


command: php artisan make:enum WebhookEventType --no-interaction

aggregated_output: INFO  Enum [app/Enums/WebhookEventType.php] created successfully.


cmd: php artisan make:model App --migration --factory --no-interaction


output:
Chunk ID: 244f15
Wall time: 0.0533 seconds
Process exited with code 0
Original token count: 62
Output:

   INFO  Model [app/Models/App.php] created successfully.

   INFO  Factory [database/factories/AppFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034556_create_apps_table.php] created successfully.


command: php artisan make:model App --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/App.php] created successfully.

   INFO  Factory [database/factories/AppFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034556_create_apps_table.php] created successfully.


cmd: php artisan make:model AppInstallation --migration --factory --no-interaction


output:
Chunk ID: 1d7ae7
Wall time: 0.0259 seconds
Process exited with code 0
Original token count: 71
Output:

   INFO  Model [app/Models/AppInstallation.php] created successfully.

   INFO  Factory [database/factories/AppInstallationFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034601_create_app_installations_table.php] created successfully.


command: php artisan make:model AppInstallation --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/AppInstallation.php] created successfully.

   INFO  Factory [database/factories/AppInstallationFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034601_create_app_installations_table.php] created successfully.


cmd: php artisan make:model OauthClient --migration --factory --no-interaction


output:
Chunk ID: 9923d6
Wall time: 0.0431 seconds
Process exited with code 0
Original token count: 68
Output:

   INFO  Model [app/Models/OauthClient.php] created successfully.

   INFO  Factory [database/factories/OauthClientFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034606_create_oauth_clients_table.php] created successfully.


command: php artisan make:model OauthClient --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/OauthClient.php] created successfully.

   INFO  Factory [database/factories/OauthClientFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034606_create_oauth_clients_table.php] created successfully.


cmd: php artisan make:model OauthToken --migration --factory --no-interaction


output:
Chunk ID: f13a7e
Wall time: 0.0325 seconds
Process exited with code 0
Original token count: 67
Output:

   INFO  Model [app/Models/OauthToken.php] created successfully.

   INFO  Factory [database/factories/OauthTokenFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034611_create_oauth_tokens_table.php] created successfully.


command: php artisan make:model OauthToken --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/OauthToken.php] created successfully.

   INFO  Factory [database/factories/OauthTokenFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034611_create_oauth_tokens_table.php] created successfully.


cmd: php artisan make:model WebhookSubscription --migration --factory --no-interaction


output:
Chunk ID: 336820
Wall time: 0.0409 seconds
Process exited with code 0
Original token count: 74
Output:

   INFO  Model [app/Models/WebhookSubscription.php] created successfully.

   INFO  Factory [database/factories/WebhookSubscriptionFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034616_create_webhook_subscriptions_table.php] created successfully.


command: php artisan make:model WebhookSubscription --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/WebhookSubscription.php] created successfully.

   INFO  Factory [database/factories/WebhookSubscriptionFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034616_create_webhook_subscriptions_table.php] created successfully.


cmd: php artisan make:model WebhookDelivery --migration --factory --no-interaction


output:
Chunk ID: cd0fea
Wall time: 0.0259 seconds
Process exited with code 0
Original token count: 71
Output:

   INFO  Model [app/Models/WebhookDelivery.php] created successfully.

   INFO  Factory [database/factories/WebhookDeliveryFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034621_create_webhook_deliveries_table.php] created successfully.


command: php artisan make:model WebhookDelivery --migration --factory --no-interaction

aggregated_output:
   INFO  Model [app/Models/WebhookDelivery.php] created successfully.

   INFO  Factory [database/factories/WebhookDeliveryFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_034621_create_webhook_deliveries_table.php] created successfully.


cmd: php artisan make:seeder AppSeeder --no-interaction


output:
Chunk ID: 0dcaa4
Wall time: 0.0362 seconds
Process exited with code 0
Original token count: 19
Output:

   INFO  Seeder [database/seeders/AppSeeder.php] created successfully.


command: php artisan make:seeder AppSeeder --no-interaction

aggregated_output: INFO  Seeder [database/seeders/AppSeeder.php] created successfully.


cmd: php artisan make:class Services/WebhookService --no-interaction


output:
Chunk ID: 85eac7
Wall time: 0.0211 seconds
Process exited with code 0
Original token count: 19
Output:

   INFO  Class [app/Services/WebhookService.php] created successfully.


command: php artisan make:class Services/WebhookService --no-interaction

aggregated_output: INFO  Class [app/Services/WebhookService.php] created successfully.


cmd: php artisan make:job DeliverWebhook --no-interaction


output:
Chunk ID: 53f9c0
Wall time: 0.0253 seconds
Process exited with code 0
Original token count: 18
Output:

   INFO  Job [app/Jobs/DeliverWebhook.php] created successfully.


command: php artisan make:job DeliverWebhook --no-interaction

aggregated_output: INFO  Job [app/Jobs/DeliverWebhook.php] created successfully.


cmd: php artisan make:listener DispatchWebhooks --no-interaction


output:
Chunk ID: 91d84f
Wall time: 0.0279 seconds
Process exited with code 0
Original token count: 21
Output:

   INFO  Listener [app/Listeners/DispatchWebhooks.php] created successfully.


command: php artisan make:listener DispatchWebhooks --no-interaction

aggregated_output: INFO  Listener [app/Listeners/DispatchWebhooks.php] created successfully.


cmd: php artisan make:livewire Admin/Apps/Index --class --no-interaction


output:
Chunk ID: 666f66
Wall time: 0.0221 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Livewire component [app/Livewire/Admin/Apps/Index.php] created successfully.


command: php artisan make:livewire Admin/Apps/Index --class --no-interaction

aggregated_output: INFO  Livewire component [app/Livewire/Admin/Apps/Index.php] created successfully.


cmd: php artisan make:livewire Admin/Apps/Show --class --no-interaction


output:
Chunk ID: 7ff59d
Wall time: 0.0321 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Livewire component [app/Livewire/Admin/Apps/Show.php] created successfully.


command: php artisan make:livewire Admin/Apps/Show --class --no-interaction

aggregated_output: INFO  Livewire component [app/Livewire/Admin/Apps/Show.php] created successfully.


cmd: php artisan make:livewire Admin/Developers/Index --class --no-interaction


output:
Chunk ID: c00bc6
Wall time: 0.0268 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Livewire component [app/Livewire/Admin/Developers/Index.php] created successfully.


command: php artisan make:livewire Admin/Developers/Index --class --no-interaction

aggregated_output: INFO  Livewire component [app/Livewire/Admin/Developers/Index.php] created successfully.


cmd: php artisan make:test Webhooks/WebhookDeliveryTest --pest --no-interaction


output:
Chunk ID: 027131
Wall time: 0.0280 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Test [tests/Feature/Webhooks/WebhookDeliveryTest.php] created successfully.


command: php artisan make:test Webhooks/WebhookDeliveryTest --pest --no-interaction

aggregated_output: INFO  Test [tests/Feature/Webhooks/WebhookDeliveryTest.php] created successfully.


cmd: php artisan make:test Webhooks/WebhookSignatureTest --pest --no-interaction


output:
Chunk ID: 2e607a
Wall time: 0.0291 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Test [tests/Feature/Webhooks/WebhookSignatureTest.php] created successfully.


command: php artisan make:test Webhooks/WebhookSignatureTest --pest --no-interaction

aggregated_output: INFO  Test [tests/Feature/Webhooks/WebhookSignatureTest.php] created successfully.


cmd: php artisan make:test Admin/AppsDevelopersTest --pest --no-interaction


output:
Chunk ID: 083628
Wall time: 0.0188 seconds
Process exited with code 0
Original token count: 22
Output:

   INFO  Test [tests/Feature/Admin/AppsDevelopersTest.php] created successfully.


command: php artisan make:test Admin/AppsDevelopersTest --pest --no-interaction

aggregated_output: INFO  Test [tests/Feature/Admin/AppsDevelopersTest.php] created successfully.


message: The scaffolding is in place. I’m filling it in now, with the OAuth/app pieces kept intentionally stubbed where the roadmap says Passport/OAuth is deferred, and webhooks implemented as the working part of the slice.


text: The scaffolding is in place. I’m filling it in now, with the OAuth/app pieces kept intentionally stubbed where the roadmap says Passport/OAuth is deferred, and webhooks implemented as the working part of the slice.


cmd: sed -n '1,220p' database/migrations/2026_05_04_034556_create_apps_table.php


cmd: sed -n '1,220p' app/Livewire/Admin/Analytics/Index.php


cmd: sed -n '1,260p' tests/Feature/Admin/AnalyticsDashboardTest.php


cmd: sed -n '1,220p' database/seeders/DatabaseSeeder.php


cmd: sed -n '1,260p' resources/views/layouts/app/sidebar.blade.php


command: sed -n '1,220p' database/migrations/2026_05_04_034556_create_apps_table.php

aggregated_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('apps', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

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


output:
Chunk ID: 4b9e4e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 132
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('apps', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

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


command: sed -n '1,220p' app/Livewire/Admin/Analytics/Index.php

aggregated_output:
<?php

namespace App\Livewire\Admin\Analytics;

use App\Enums\AnalyticsEventType;
use App\Enums\FinancialStatus;
use App\Enums\StoreUserRole;
use App\Models\AnalyticsEvent;
use App\Models\OrderLine;
use App\Models\Store;
use App\Services\AnalyticsService;
use App\Support\Money;
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Throwable;

class Index extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    public string $storeCurrency = 'EUR';

    public string $dateRange = 'last_30_days';

    public ?string $customStartDate = null;

    public ?string $customEndDate = null;

    public string $channelFilter = 'all';

    public string $deviceFilter = 'all';

    public int $totalSales = 0;

    public int $ordersCount = 0;

    public int $averageOrderValue = 0;

    public float $conversionRate = 0.0;

    /**
     * @var list<array{date: string, label: string, revenue: int, orders: int}>
     */
    public array $salesChartData = [];

    public int $maxSalesChartAmount = 1;

    /**
     * @var list<array{title: string, units_sold: int, revenue: int, percentage: float}>
     */
    public array $topProducts = [];

    /**
     * @var list<array{source: string, sessions: int, orders: int, conversion_rate: float}>
     */
    public array $topReferrers = [];

    public bool $isExporting = false;

    public ?string $exportUrl = null;

    public function mount(AnalyticsService $analytics): void
    {
        $store = $this->store();

        $this->authorize('view', $store);
        abort_unless($this->canViewAnalytics($store), 403);

        $this->storeId = $store->getKey();
        $this->storeCurrency = $store->default_currency;

        $this->loadAnalytics($analytics);
    }

    public function updatedDateRange(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function updatedCustomStartDate(AnalyticsService $analytics): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadAnalytics($analytics);
        }
    }

    public function updatedCustomEndDate(AnalyticsService $analytics): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadAnalytics($analytics);
        }
    }

    public function updatedChannelFilter(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function updatedDeviceFilter(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function loadAnalytics(AnalyticsService $analytics): void
    {
        [$start, $end] = $this->currentRange();
        $store = $this->scopedStore();

        $totals = $analytics->totals($store, $start->toDateString(), $end->toDateString());

        $this->totalSales = $totals['revenue_amount'];
        $this->ordersCount = $totals['orders_count'];
        $this->averageOrderValue = $totals['aov_amount'];
        $this->conversionRate = $totals['visits_count'] > 0
            ? round(($totals['checkout_completed_count'] / $totals['visits_count']) * 100, 2)
            : 0.0;

        $daily = $analytics->getDailyMetrics($store, $start->toDateString(), $end->toDateString())->keyBy('date');

        $this->salesChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
            ->map(function (CarbonInterface $date) use ($daily): array {
                $metric = $daily->get($date->toDateString());

                return [
                    'date' => $date->toDateString(),
                    'label' => $date->format('M j'),
                    'revenue' => (int) ($metric?->revenue_amount ?? 0),
                    'orders' => (int) ($metric?->orders_count ?? 0),
                ];
            })
            ->values()
            ->all();

        $this->maxSalesChartAmount = max(1, max(array_column($this->salesChartData, 'revenue') ?: [0]));
        $this->loadTopProducts($start, $end);
        $this->loadTopReferrers($start, $end);
    }

    public function exportCsv(AnalyticsService $analytics): void
    {
        $this->isExporting = true;
        $this->loadAnalytics($analytics);

        $lines = ['date,revenue_amount,orders_count'];

        foreach ($this->salesChartData as $point) {
            $lines[] = "{$point['date']},{$point['revenue']},{$point['orders']}";
        }

        $this->exportUrl = 'data:text/csv;charset=utf-8,'.rawurlencode(implode("\n", $lines));
        $this->isExporting = false;
    }

    #[Computed]
    public function formattedTotalSales(): string
    {
        return Money::format($this->totalSales, $this->storeCurrency);
    }

    #[Computed]
    public function formattedAov(): string
    {
        return Money::format($this->averageOrderValue, $this->storeCurrency);
    }

    public function render(): mixed
    {
        return view('livewire.admin.analytics.index')->layout('layouts.app', [
            'title' => __('Analytics'),
        ]);
    }

    private function loadTopProducts(CarbonInterface $start, CarbonInterface $end): void
    {
        $rows = OrderLine::query()
            ->selectRaw('order_lines.title_snapshot as title, sum(order_lines.quantity) as units_sold, sum(order_lines.total_amount) as revenue')
            ->join('orders', 'orders.id', '=', 'order_lines.order_id')
            ->where('orders.store_id', $this->storeId)
            ->whereBetween('orders.placed_at', [$start, $end])
            ->whereIn('orders.financial_status', [
                FinancialStatus::Paid->value,
                FinancialStatus::PartiallyRefunded->value,
            ])
            ->groupBy('order_lines.product_id', 'order_lines.title_snapshot')
            ->orderByDesc('revenue')
            ->limit(10)
            ->get();

        $totalRevenue = max(1, (int) $rows->sum('revenue'));

        $this->topProducts = $rows
            ->map(fn (OrderLine $line): array => [
                'title' => (string) $line->title,
                'units_sold' => (int) $line->units_sold,
                'revenue' => (int) $line->revenue,
                'percentage' => round(((int) $line->revenue / $totalRevenue) * 100, 1),
            ])
            ->all();
    }

    private function loadTopReferrers(CarbonInterface $start, CarbonInterface $end): void
    {
        $pageViews = $this->filteredEvents($start, $end, AnalyticsEventType::PageView);
        $orders = $this->filteredEvents($start, $end, AnalyticsEventType::CheckoutCompleted);
        $ordersBySource = $orders->groupBy(fn (AnalyticsEvent $event): string => $this->sourceFor($event));

        $this->topReferrers = $pageViews
            ->groupBy(fn (AnalyticsEvent $event): string => $this->sourceFor($event))
            ->map(function ($events, string $source) use ($ordersBySource): array {
                $sessions = $events->pluck('session_id')->filter()->unique()->count();
                $orders = $ordersBySource->get($source, collect())->count();


output:
Chunk ID: c9a492
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1811
Output:
<?php

namespace App\Livewire\Admin\Analytics;

use App\Enums\AnalyticsEventType;
use App\Enums\FinancialStatus;
use App\Enums\StoreUserRole;
use App\Models\AnalyticsEvent;
use App\Models\OrderLine;
use App\Models\Store;
use App\Services\AnalyticsService;
use App\Support\Money;
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Throwable;

class Index extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    public string $storeCurrency = 'EUR';

    public string $dateRange = 'last_30_days';

    public ?string $customStartDate = null;

    public ?string $customEndDate = null;

    public string $channelFilter = 'all';

    public string $deviceFilter = 'all';

    public int $totalSales = 0;

    public int $ordersCount = 0;

    public int $averageOrderValue = 0;

    public float $conversionRate = 0.0;

    /**
     * @var list<array{date: string, label: string, revenue: int, orders: int}>
     */
    public array $salesChartData = [];

    public int $maxSalesChartAmount = 1;

    /**
     * @var list<array{title: string, units_sold: int, revenue: int, percentage: float}>
     */
    public array $topProducts = [];

    /**
     * @var list<array{source: string, sessions: int, orders: int, conversion_rate: float}>
     */
    public array $topReferrers = [];

    public bool $isExporting = false;

    public ?string $exportUrl = null;

    public function mount(AnalyticsService $analytics): void
    {
        $store = $this->store();

        $this->authorize('view', $store);
        abort_unless($this->canViewAnalytics($store), 403);

        $this->storeId = $store->getKey();
        $this->storeCurrency = $store->default_currency;

        $this->loadAnalytics($analytics);
    }

    public function updatedDateRange(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function updatedCustomStartDate(AnalyticsService $analytics): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadAnalytics($analytics);
        }
    }

    public function updatedCustomEndDate(AnalyticsService $analytics): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadAnalytics($analytics);
        }
    }

    public function updatedChannelFilter(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function updatedDeviceFilter(AnalyticsService $analytics): void
    {
        $this->loadAnalytics($analytics);
    }

    public function loadAnalytics(AnalyticsService $analytics): void
    {
        [$start, $end] = $this->currentRange();
        $store = $this->scopedStore();

        $totals = $analytics->totals($store, $start->toDateString(), $end->toDateString());

        $this->totalSales = $totals['revenue_amount'];
        $this->ordersCount = $totals['orders_count'];
        $this->averageOrderValue = $totals['aov_amount'];
        $this->conversionRate = $totals['visits_count'] > 0
            ? round(($totals['checkout_completed_count'] / $totals['visits_count']) * 100, 2)
            : 0.0;

        $daily = $analytics->getDailyMetrics($store, $start->toDateString(), $end->toDateString())->keyBy('date');

        $this->salesChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
            ->map(function (CarbonInterface $date) use ($daily): array {
                $metric = $daily->get($date->toDateString());

                return [
                    'date' => $date->toDateString(),
                    'label' => $date->format('M j'),
                    'revenue' => (int) ($metric?->revenue_amount ?? 0),
                    'orders' => (int) ($metric?->orders_count ?? 0),
                ];
            })
            ->values()
            ->all();

        $this->maxSalesChartAmount = max(1, max(array_column($this->salesChartData, 'revenue') ?: [0]));
        $this->loadTopProducts($start, $end);
        $this->loadTopReferrers($start, $end);
    }

    public function exportCsv(AnalyticsService $analytics): void
    {
        $this->isExporting = true;
        $this->loadAnalytics($analytics);

        $lines = ['date,revenue_amount,orders_count'];

        foreach ($this->salesChartData as $point) {
            $lines[] = "{$point['date']},{$point['revenue']},{$point['orders']}";
        }

        $this->exportUrl = 'data:text/csv;charset=utf-8,'.rawurlencode(implode("\n", $lines));
        $this->isExporting = false;
    }

    #[Computed]
    public function formattedTotalSales(): string
    {
        return Money::format($this->totalSales, $this->storeCurrency);
    }

    #[Computed]
    public function formattedAov(): string
    {
        return Money::format($this->averageOrderValue, $this->storeCurrency);
    }

    public function render(): mixed
    {
        return view('livewire.admin.analytics.index')->layout('layouts.app', [
            'title' => __('Analytics'),
        ]);
    }

    private function loadTopProducts(CarbonInterface $start, CarbonInterface $end): void
    {
        $rows = OrderLine::query()
            ->selectRaw('order_lines.title_snapshot as title, sum(order_lines.quantity) as units_sold, sum(order_lines.total_amount) as revenue')
            ->join('orders', 'orders.id', '=', 'order_lines.order_id')
            ->where('orders.store_id', $this->storeId)
            ->whereBetween('orders.placed_at', [$start, $end])
            ->whereIn('orders.financial_status', [
                FinancialStatus::Paid->value,
                FinancialStatus::PartiallyRefunded->value,
            ])
            ->groupBy('order_lines.product_id', 'order_lines.title_snapshot')
            ->orderByDesc('revenue')
            ->limit(10)
            ->get();

        $totalRevenue = max(1, (int) $rows->sum('revenue'));

        $this->topProducts = $rows
            ->map(fn (OrderLine $line): array => [
                'title' => (string) $line->title,
                'units_sold' => (int) $line->units_sold,
                'revenue' => (int) $line->revenue,
                'percentage' => round(((int) $line->revenue / $totalRevenue) * 100, 1),
            ])
            ->all();
    }

    private function loadTopReferrers(CarbonInterface $start, CarbonInterface $end): void
    {
        $pageViews = $this->filteredEvents($start, $end, AnalyticsEventType::PageView);
        $orders = $this->filteredEvents($start, $end, AnalyticsEventType::CheckoutCompleted);
        $ordersBySource = $orders->groupBy(fn (AnalyticsEvent $event): string => $this->sourceFor($event));

        $this->topReferrers = $pageViews
            ->groupBy(fn (AnalyticsEvent $event): string => $this->sourceFor($event))
            ->map(function ($events, string $source) use ($ordersBySource): array {
                $sessions = $events->pluck('session_id')->filter()->unique()->count();
                $orders = $ordersBySource->get($source, collect())->count();


command: sed -n '1,220p' database/seeders/DatabaseSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            OrganizationSeeder::class,
            StoreSeeder::class,
            StoreDomainSeeder::class,
            UserSeeder::class,
            StoreUserSeeder::class,
            StoreSettingsSeeder::class,
            SearchSettingsSeeder::class,
            CollectionSeeder::class,
            ProductSeeder::class,
            ThemeSeeder::class,
            ThemeFileSeeder::class,
            ThemeSettingsSeeder::class,
            PageSeeder::class,
            NavigationMenuSeeder::class,
            NavigationItemSeeder::class,
            TaxSettingsSeeder::class,
            ShippingZoneSeeder::class,
            ShippingRateSeeder::class,
            DiscountSeeder::class,
            CustomerSeeder::class,
            AnalyticsSeeder::class,
        ]);
    }
}


command: sed -n '1,260p' tests/Feature/Admin/AnalyticsDashboardTest.php

aggregated_output:
<?php

use App\Enums\StoreUserRole;
use App\Livewire\Admin\Analytics\Index as AnalyticsIndex;
use App\Models\Store;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminAnalyticsStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminAnalyticsUser(?StoreUserRole $role = null): User
{
    $store = adminAnalyticsStore();
    $user = User::factory()->create([
        'email_verified_at' => now(),
    ]);

    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $user->getKey(),
        'role' => ($role ?? StoreUserRole::Owner)->value,
        'created_at' => now(),
    ]);

    return $user;
}

test('admin analytics route renders metrics for store staff and above', function (): void {
    $this->actingAs(adminAnalyticsUser(StoreUserRole::Staff))
        ->get('/admin/analytics')
        ->assertSuccessful()
        ->assertSee('Analytics')
        ->assertSee('Total sales')
        ->assertSee('Top referrers');
});

test('admin analytics rejects support users', function (): void {
    $this->actingAs(adminAnalyticsUser(StoreUserRole::Support))
        ->get('/admin/analytics')
        ->assertForbidden();
});

test('admin analytics component filters and exports csv data', function (): void {
    $store = adminAnalyticsStore();
    app()->instance('current_store', $store);

    Livewire::actingAs(adminAnalyticsUser(StoreUserRole::Admin))
        ->test(AnalyticsIndex::class)
        ->assertSee('Sales over time')
        ->set('dateRange', 'last_7_days')
        ->set('channelFilter', 'storefront')
        ->set('deviceFilter', 'mobile')
        ->call('exportCsv')
        ->assertSet('isExporting', false)
        ->assertSet('exportUrl', fn (?string $url): bool => str_starts_with((string) $url, 'data:text/csv'));
});


output:
Chunk ID: 0c6ec0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 526
Output:
<?php

use App\Enums\StoreUserRole;
use App\Livewire\Admin\Analytics\Index as AnalyticsIndex;
use App\Models\Store;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminAnalyticsStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminAnalyticsUser(?StoreUserRole $role = null): User
{
    $store = adminAnalyticsStore();
    $user = User::factory()->create([
        'email_verified_at' => now(),
    ]);

    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $user->getKey(),
        'role' => ($role ?? StoreUserRole::Owner)->value,
        'created_at' => now(),
    ]);

    return $user;
}

test('admin analytics route renders metrics for store staff and above', function (): void {
    $this->actingAs(adminAnalyticsUser(StoreUserRole::Staff))
        ->get('/admin/analytics')
        ->assertSuccessful()
        ->assertSee('Analytics')
        ->assertSee('Total sales')
        ->assertSee('Top referrers');
});

test('admin analytics rejects support users', function (): void {
    $this->actingAs(adminAnalyticsUser(StoreUserRole::Support))
        ->get('/admin/analytics')
        ->assertForbidden();
});

test('admin analytics component filters and exports csv data', function (): void {
    $store = adminAnalyticsStore();
    app()->instance('current_store', $store);

    Livewire::actingAs(adminAnalyticsUser(StoreUserRole::Admin))
        ->test(AnalyticsIndex::class)
        ->assertSee('Sales over time')
        ->set('dateRange', 'last_7_days')
        ->set('channelFilter', 'storefront')
        ->set('deviceFilter', 'mobile')
        ->call('exportCsv')
        ->assertSet('isExporting', false)
        ->assertSet('exportUrl', fn (?string $url): bool => str_starts_with((string) $url, 'data:text/csv'));
});


output:
Chunk ID: 4b7765
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 254
Output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            OrganizationSeeder::class,
            StoreSeeder::class,
            StoreDomainSeeder::class,
            UserSeeder::class,
            StoreUserSeeder::class,
            StoreSettingsSeeder::class,
            SearchSettingsSeeder::class,
            CollectionSeeder::class,
            ProductSeeder::class,
            ThemeSeeder::class,
            ThemeFileSeeder::class,
            ThemeSettingsSeeder::class,
            PageSeeder::class,
            NavigationMenuSeeder::class,
            NavigationItemSeeder::class,
            TaxSettingsSeeder::class,
            ShippingZoneSeeder::class,
            ShippingRateSeeder::class,
            DiscountSeeder::class,
            CustomerSeeder::class,
            AnalyticsSeeder::class,
        ]);
    }
}