Skip to main content

Events

Laravel URL provides a comprehensive event system that fires domain events for all URL operations. These events allow you to hook into the URL management lifecycle and perform additional actions when URLs are created, changed, matched, or when resources need to be generated.

Overview

The event system in Laravel URL:

  • Fires automatically during URL operations
  • Provides model and URL data in event payloads
  • Uses DomainEvent interface for consistency
  • Supports event listeners for custom logic
  • Enables event-driven architecture for URL management

Event Structure

All events in Laravel URL:

  • Implement JobMetric\EventSystem\Contracts\DomainEvent (where applicable)
  • Are readonly classes for immutability (where applicable)
  • Contain model and URL information
  • Provide a stable key() method (where applicable)
  • Include metadata via definition() method (where applicable)

Base Event Structure

readonly class UrlChanged implements DomainEvent
{
public function __construct(
public Model $model,
public ?string $old,
public string $new,
public int $version
) {}

public static function key(): string
{
return 'url.changed';
}

public static function definition(): DomainEventDefinition
{
return new DomainEventDefinition(
self::key(),
'url::base.entity_names.url',
'url::base.events.url_changed.title',
'url::base.events.url_changed.description',
'fas fa-link',
['url', 'routing', 'seo']
);
}
}

Available Events

URL Events

Events fired for URL operations.

Event ClassEvent KeyDescriptionPayloadTriggered When
UrlChangedurl.changedFired after a URL is created or changedModel $model, ?string $old, string $new, int $versionHasUrl::dispatchSlug(), HasUrl::rebuildUrl()
UrlMatchedurl.matchedFired when fallback route matches an active URLRequest $request, Url $url, Model $urlable, ?string $collectionFullUrlController::__invoke()
UrlableResourceEventurlable.resourceFired when resource representation is neededmixed $urlableAccessing slugable_resource or urlable_resource

Namespace: JobMetric\Url\Events\*

Listening to Events

Method 1: Event Listeners in Service Provider

Register event listeners in a service provider:

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use JobMetric\Url\Events\UrlChanged;
use JobMetric\Url\Events\UrlMatched;
use JobMetric\Url\Events\UrlableResourceEvent;

class EventServiceProvider extends ServiceProvider
{
protected $listen = [
UrlChanged::class => [
\App\Listeners\Url\LogUrlChanged::class,
\App\Listeners\Url\InvalidateUrlCache::class,
\App\Listeners\Url\UpdateSearchIndex::class,
\App\Listeners\Url\RegenerateSitemap::class,
\App\Listeners\Url\TrackUrlAnalytics::class,
],
UrlMatched::class => [
\App\Listeners\Url\HandleUrlMatch::class,
\App\Listeners\Url\LogUrlAccess::class,
],
UrlableResourceEvent::class => [
\App\Listeners\Url\GenerateUrlableResource::class,
],
];

public function boot(): void
{
parent::boot();
}
}

Method 2: Using Event Facade

Listen to events using the Event facade:

use Illuminate\Support\Facades\Event;
use JobMetric\Url\Events\UrlChanged;

Event::listen(UrlChanged::class, function (UrlChanged $event) {
$model = $event->model;
$old = $event->old;
$new = $event->new;
$version = $event->version;

// Perform actions
Log::info('URL changed', [
'model' => get_class($model),
'model_id' => $model->id,
'old_url' => $old,
'new_url' => $new,
'version' => $version,
]);

Cache::tags([get_class($model)])->flush();
});

Method 3: Using Event Subscribers

Create event subscribers for multiple events:

namespace App\Listeners;

use Illuminate\Events\Dispatcher;
use JobMetric\Url\Events\UrlChanged;
use JobMetric\Url\Events\UrlMatched;
use JobMetric\Url\Events\UrlableResourceEvent;

class UrlEventSubscriber
{
public function handleUrlChanged(UrlChanged $event): void
{
$model = $event->model;
$old = $event->old;
$new = $event->new;

Log::info('URL changed', [
'model' => get_class($model),
'model_id' => $model->id,
'old_url' => $old,
'new_url' => $new,
]);
}

public function handleUrlMatched(UrlMatched $event): void
{
$url = $event->url;
$urlable = $event->urlable;

Log::info('URL matched', [
'url_id' => $url->id,
'full_url' => $url->full_url,
'model' => get_class($urlable),
'model_id' => $urlable->id,
]);
}

public function handleUrlableResource(UrlableResourceEvent $event): void
{
$urlable = $event->urlable;

if ($urlable instanceof Product) {
$event->resource = new ProductResource($urlable);
}
}

public function subscribe(Dispatcher $events): void
{
$events->listen(
UrlChanged::class,
[UrlEventSubscriber::class, 'handleUrlChanged']
);

$events->listen(
UrlMatched::class,
[UrlEventSubscriber::class, 'handleUrlMatched']
);

$events->listen(
UrlableResourceEvent::class,
[UrlEventSubscriber::class, 'handleUrlableResource']
);
}
}

Register the subscriber:

namespace App\Providers;

use App\Listeners\UrlEventSubscriber;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
protected $subscribe = [
UrlEventSubscriber::class,
];
}

Complete Examples

Example 1: Cache Invalidation on URL Changes

Invalidate cache when URLs are modified:

namespace App\Listeners\Url;

use Illuminate\Support\Facades\Cache;
use JobMetric\Url\Events\UrlChanged;

class InvalidateUrlCache
{
public function handle(UrlChanged $event): void
{
$model = $event->model;
$old = $event->old;
$new = $event->new;

// Invalidate specific URL cache
if ($old) {
Cache::forget("url:{$old}");
Cache::forget("page:{$old}");
}

Cache::forget("url:{$new}");
Cache::forget("page:{$new}");

// Invalidate all URLs cache for this model
Cache::forget("urls.{$model->getMorphClass()}.{$model->id}");

// Clear tagged cache
Cache::tags([get_class($model)])->flush();
Cache::tags(["url_{$model->getMorphClass()}_{$model->id}"])->flush();
}
}

Example 2: Audit Logging

Log all URL operations for audit trail:

namespace App\Listeners\Url;

use App\Models\AuditLog;
use Illuminate\Support\Facades\Auth;
use JobMetric\Url\Events\UrlChanged;
use JobMetric\Url\Events\UrlMatched;

class AuditUrlOperations
{
public function handleUrlChanged(UrlChanged $event): void
{
$this->logAudit('changed', $event->model, [
'old_url' => $event->old,
'new_url' => $event->new,
'version' => $event->version,
]);
}

public function handleUrlMatched(UrlMatched $event): void
{
$this->logAudit('matched', $event->urlable, [
'url_id' => $event->url->id,
'full_url' => $event->url->full_url,
'collection' => $event->collection,
]);
}

protected function logAudit(string $action, $model, array $data): void
{
AuditLog::create([
'user_id' => Auth::id(),
'action' => "url.{$action}",
'model_type' => get_class($model),
'model_id' => $model->id,
'data' => $data,
'ip_address' => request()->ip(),
]);
}
}

Example 3: Search Index Updates

Update search index when URLs change:

namespace App\Listeners\Url;

use App\Services\SearchService;
use JobMetric\Url\Events\UrlChanged;

class UpdateSearchIndex
{
public function handle(UrlChanged $event): void
{
$model = $event->model;
$old = $event->old;
$new = $event->new;

// Update search index with new URL
SearchService::index($model, [
'url' => $new,
'old_url' => $old,
'version' => $event->version,
]);

// If old URL exists, update redirects
if ($old) {
SearchService::updateRedirect($old, $new);
}
}
}

Example 4: Sitemap Generation

Regenerate sitemap when URLs change:

namespace App\Listeners\Url;

use App\Services\SitemapService;
use JobMetric\Url\Events\UrlChanged;

class RegenerateSitemap
{
public function __construct(
protected SitemapService $sitemap
) {}

public function handle(UrlChanged $event): void
{
$model = $event->model;
$new = $event->new;

// Regenerate sitemap for this model type
$this->sitemap->regenerate(get_class($model));

// Add new URL to sitemap
$this->sitemap->add($new, [
'lastmod' => now(),
'changefreq' => 'weekly',
'priority' => 0.8,
]);

// Remove old URL if exists
if ($event->old) {
$this->sitemap->remove($event->old);
}
}
}

Example 5: Analytics Tracking

Track URL changes for analytics:

namespace App\Listeners\Url;

use App\Services\AnalyticsService;
use JobMetric\Url\Events\UrlChanged;
use JobMetric\Url\Events\UrlMatched;

class TrackUrlAnalytics
{
public function __construct(
protected AnalyticsService $analytics
) {}

public function handleUrlChanged(UrlChanged $event): void
{
$model = $event->model;

$this->analytics->track('url.changed', [
'model_type' => get_class($model),
'model_id' => $model->id,
'old_url' => $event->old,
'new_url' => $event->new,
'version' => $event->version,
]);
}

public function handleUrlMatched(UrlMatched $event): void
{
$url = $event->url;
$urlable = $event->urlable;

$this->analytics->track('url.matched', [
'url_id' => $url->id,
'full_url' => $url->full_url,
'model_type' => get_class($urlable),
'model_id' => $urlable->id,
'collection' => $event->collection,
]);
}
}

Example 6: Fallback Route Handling

Handle unmatched URLs with custom logic:

namespace App\Listeners\Url;

use JobMetric\Url\Events\UrlMatched;

class HandleUrlMatch
{
public function handle(UrlMatched $event): void
{
$model = $event->urlable;
$request = $event->request;

// Handle different model types
if ($model instanceof Product) {
$event->response = view('products.show', [
'product' => $model,
]);
} elseif ($model instanceof Category) {
$event->response = view('categories.show', [
'category' => $model,
]);
} elseif ($model instanceof Page) {
$event->response = view('pages.show', [
'page' => $model,
]);
}

// Handle API requests
if ($request->wantsJson()) {
$event->response = response()->json([
'url' => $event->url->full_url,
'model' => $model->toArray(),
]);
}
}
}

Example 7: Resource Generation

Transform models into API resources:

namespace App\Listeners\Url;

use App\Http\Resources\ProductResource;
use App\Http\Resources\CategoryResource;
use App\Http\Resources\PageResource;
use JobMetric\Url\Events\UrlableResourceEvent;

class GenerateUrlableResource
{
public function handle(UrlableResourceEvent $event): void
{
$urlable = $event->urlable;

if ($urlable instanceof Product) {
$event->resource = new ProductResource($urlable);
} elseif ($urlable instanceof Category) {
$event->resource = new CategoryResource($urlable);
} elseif ($urlable instanceof Page) {
$event->resource = new PageResource($urlable);
}
}
}

Example 8: Complete Event Listener Setup

Complete setup for all URL events:

namespace App\Providers;

use App\Listeners\Url\AuditUrlOperations;
use App\Listeners\Url\GenerateUrlableResource;
use App\Listeners\Url\HandleUrlMatch;
use App\Listeners\Url\InvalidateUrlCache;
use App\Listeners\Url\RegenerateSitemap;
use App\Listeners\Url\TrackUrlAnalytics;
use App\Listeners\Url\UpdateSearchIndex;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use JobMetric\Url\Events\UrlChanged;
use JobMetric\Url\Events\UrlMatched;
use JobMetric\Url\Events\UrlableResourceEvent;

class EventServiceProvider extends ServiceProvider
{
protected $listen = [
// After URL change
UrlChanged::class => [
AuditUrlOperations::class,
InvalidateUrlCache::class,
UpdateSearchIndex::class,
RegenerateSitemap::class,
TrackUrlAnalytics::class,
],

// After URL match
UrlMatched::class => [
HandleUrlMatch::class,
AuditUrlOperations::class,
TrackUrlAnalytics::class,
],

// Resource generation
UrlableResourceEvent::class => [
GenerateUrlableResource::class,
],
];

public function boot(): void
{
parent::boot();
}
}

Event Timing

UrlChanged

Fired after the URL is saved to database:

// Execution order:
// 1. Validate slug and URL
// 2. Check for conflicts
// 3. Create or update URL record
// 4. Save to database
// 5. Fire UrlChanged ← Event fired here
// 6. Return model instance

Note: This event fires for both new URLs and updates to existing URLs.

UrlMatched

Fired when the fallback route matches an active URL:

// Execution order:
// 1. Find matching URL in database
// 2. Load related model
// 3. Fire UrlMatched ← Event fired here
// 4. Return response (set by listener)

Important Notes:

  • The URL is matched before UrlMatched fires
  • You can access matched URL and model in listeners
  • Response must be set by a listener
  • If no listener sets response, default behavior applies

UrlableResourceEvent

Fired when resource representation is needed:

// Execution order:
// 1. Access slugable_resource or urlable_resource
// 2. Fire UrlableResourceEvent ← Event fired here
// 3. Return resource (set by listener)

Important Notes:

  • Event fires lazily when resource is accessed
  • Resource must be set by a listener
  • If no listener sets resource, null is returned

Queued Events

You can queue events for asynchronous processing:

namespace App\Listeners\Url;

use Illuminate\Contracts\Queue\ShouldQueue;
use JobMetric\Url\Events\UrlChanged;

class ProcessUrlAsync implements ShouldQueue
{
public function handle(UrlChanged $event): void
{
$model = $event->model;
$new = $event->new;

// Heavy processing that doesn't block the request
$this->updateSearchIndex($model, $new);
$this->regenerateSitemap($model);
$this->syncWithExternalService($model, $new);
}

protected function updateSearchIndex($model, $url): void
{
// Update search index
}

protected function regenerateSitemap($model): void
{
// Regenerate sitemap
}

protected function syncWithExternalService($model, $url): void
{
// Sync with external API
}
}

Event Payload Details

UrlChanged

readonly class UrlChanged implements DomainEvent
{
public function __construct(
public Model $model, // Model instance
public ?string $old, // Previous URL (null on create)
public string $new, // New active URL
public int $version // Version number of new URL
) {}
}

Available Properties:

  • $model: The model instance that received the URL change
  • $old: The previous URL (null on first creation)
  • $new: The new active URL
  • $version: The version number of the new URL

Example Payload:

$event->model;        // Product instance
$event->old; // '/shop/laptops/macbook-pro-13' or null
$event->new; // '/shop/laptops/macbook-pro-14'
$event->version; // 2

UrlMatched

class UrlMatched
{
public Request $request; // Incoming HTTP request
public Url $url; // Matched Url model instance
public Model $urlable; // Related model instance
public ?string $collection; // Optional collection name
public mixed $response = null; // Response to return (set by listener)
}

Available Properties:

  • $request: The incoming HTTP request
  • $url: The matched Url model instance
  • $urlable: The related model instance
  • $collection: Optional collection name
  • $response: Response to return (must be set by listener)

Example Payload:

$event->request;      // Request instance
$event->url; // Url model instance
$event->urlable; // Product instance
$event->collection; // 'products' or null
$event->response; // null (set by listener)

UrlableResourceEvent

class UrlableResourceEvent
{
public mixed $urlable; // Model instance needing resource
public mixed $resource = null; // Resource representation (set by listener)
}

Available Properties:

  • $urlable: The model instance that needs a resource
  • $resource: The resource representation (must be set by listener)

Example Payload:

$event->urlable;      // Product instance
$event->resource; // null (set by listener)

When to Use Events

Use events when you need to:

  • Cache Management: Invalidate or update cached URLs
  • Search Indexing: Update search index when URLs change
  • Sitemap Generation: Regenerate sitemaps on URL changes
  • Analytics: Track URL operations for analytics
  • Audit Logging: Log URL changes for compliance
  • Fallback Routing: Handle unmatched URLs with custom logic
  • Resource Transformation: Transform models into API resources
  • SEO Optimization: Track URL changes for SEO purposes
  • Integration: Integrate with external services
  • Side Effects: Perform side effects without coupling