Back to blog
LaravelPHPAutomatización

Typed UUID input and migration failures in Laravel 13.42

Laravel 13.42 improves typed input, failed migration handling, and automation with process and scheduler updates.

By Isma5 min read

Stop passing unqualified strings through an API

A lot of APIs receive UUIDs and ULIDs as strings, validate them loosely, and then pass those strings through services, actions, and queries. Laravel 13.42 adds uuid() and ulid() to request input, returning typed objects instead. The HTTP contract stays the same, but the conversion can now happen at the boundary instead of leaking into the rest of the application. (laravel.com)

Validate first, then retrieve the typed value

I would use these methods immediately after request validation, not as a replacement for validation. uuid() returns a Ramsey\Uuid\UuidInterface instance and ulid() returns a Symfony\Component\Uid\Ulid instance; missing input returns null. If the input exists but does not contain a valid identifier, Laravel throws an InvalidArgumentException. (laravel.com)

use Illuminate\Http\Request;
 
public function show(Request $request)
{
    $request->validate([
        'order_id' => ['required', 'uuid'],
        'event_id' => ['nullable', 'ulid'],
    ]);
 
    $orderId = $request->uuid('order_id');
    $eventId = $request->ulid('event_id');
 
    return response()->json([
        'order_id' => (string) $orderId,
        'event_id' => $eventId ? (string) $eventId : null,
    ]);
}

The benefit becomes clearer once an application has several entry points: controllers, commands, jobs, and third-party integrations. A known identifier type makes service boundaries more explicit and removes scattered manual conversions. It also makes the moment when external input becomes valid application data much easier to identify.

Process macros that work inside a chain

Automation code that launches commands from Laravel often repeats the same setup: working directory, environment variables, timeout, or TTY mode. In this release, PendingProcess supports macros, so that setup can be reused even after methods such as tty() or path() have already been called. I can see this being useful for self-hosted deployments, homelabs, and internal jobs running Composer, maintenance scripts, or infrastructure tools. (github.com)

use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
 
PendingProcess::macro('inProject', function (string $path) {
    return $this
        ->path($path)
        ->timeout(120);
});
 
$result = Process::tty()
    ->inProject(base_path())
    ->run(['composer', 'install', '--no-interaction']);

This is not a reason to turn PHP into an unrestricted shell orchestrator. It is still sensible to use argument arrays where possible, set timeouts, and avoid interpolating user-controlled values into commands. A macro removes repetition, but it does not remove operational risk or the security constraints around proc_open.

Respond when a migration leaves the schema in an awkward state

The MigrationFailed event provides one central place to react when a migration throws an exception. That matters most when managing MySQL yourself and a schema operation has already changed part of the database before a later statement fails. The event does not swallow the failure: Laravel dispatches it and the exception still continues through the normal error path. (github.com)

use Illuminate\Database\Events\MigrationFailed;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
 
Event::listen(function (MigrationFailed $event): void {
    Log::critical('Migration failed', [
        'migration' => $event->name,
        'exception' => $event->exception->getMessage(),
    ]);
});

For me, the useful part is connecting that signal to actual operations: an alert, an incident marker, or a step that prevents workers and users from running against an inconsistent schema. I would not automate a destructive response on day one. First I would capture enough context, test the flow in staging, and decide what response fits each application.

Find out why the scheduler skipped a task

Laravel’s scheduler already supports task constraints through methods such as when(), skip(), withoutOverlapping(), and onOneServer(). Laravel 13.42 adds a reason to the ScheduledTaskSkipped event, which is useful when an automation looks as if it simply did nothing. Logging that reason helps separate a business condition from a paused scheduler or a lock acquired by another server. (github.com)

use Illuminate\Console\Events\ScheduledTaskSkipped;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
 
Event::listen(function (ScheduledTaskSkipped $event): void {
    Log::info('Scheduled task skipped', [
        'task' => $event->task->getSummaryForDisplay(),
        'reason' => $event->reason?->value,
    ]);
});

This works especially well for automations that rely on shared locks, including tasks using onOneServer() or withoutOverlapping(). On a small installation, keeping the event in the logs may be enough; on multi-node infrastructure, the same information is useful telemetry. The important distinction is that a skipped task is not the same as a successfully completed task.

The fine print

uuid() and ulid() do not validate input for you, and they do not turn malformed identifiers into a suitable HTTP response by themselves. If they are called before validation, the application still has to decide how to handle the exception; for a public API, validation usually provides a more consistent 422 response. They also do not solve database key design or replace typed DTOs in the application layer.

MigrationFailed does not roll schema changes back or repair a partially applied migration. It is a signal to act on, not an automatic recovery mechanism, and putting an application into maintenance mode should be tested carefully. PendingProcess macros do not isolate processes either, and they are not a replacement for queues, supervision, or resource limits when an automation can run for too long.

Small changes with practical consequences

I do not see a feature here that requires rewriting an application, and that is exactly why it is useful. Typed request input removes ambiguity in APIs, process macros clean up repeated automation setup, and the new events improve observability where it hurts most: migrations and scheduled tasks. I would adopt these changes gradually, starting with the endpoints and processes that currently have the most manual conversion and the least operational context.


Source: Laravel News. (laravel-news.com) Official documentation: HTTP Requests, Processes, and Task Scheduling. (laravel.com) Official framework changes: MigrationFailed, PendingProcess macros, and ScheduledTaskSkipped reasons. (github.com)

Newsletter

What I learn coding with AI, every two weeks in your inbox.

Real numbers, mistakes included. No spam, unsubscribe in one click.

No spam, unsubscribe in one click. I only use your email to send you this and the newsletter. More in the privacy policy.