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