Laravel Doctor es un paquete de primera parte que inspecciona tu aplicación y te dice qué está mal antes de que lo descubras en producción. Cada comprobación es una clase que revisa una sola cosa —si Laravel puede escribir en storage, si la conexión por defecto responde, si el modo debug está activo en producción— y devuelve un estado. Cuando la reparación es segura y determinista, Doctor la ofrece.
Requisitos
PHP 8.3 o superior
Laravel 12 o superior
El paquete está en la versión 0.1.0, así que la API todavía puede moverse
Se instala como dependencia de desarrollo:
composer require laravel/doctor --devNo hay que registrar nada: el paquete descubre su service provider y añade el comando doctor a Artisan.
Primera ejecución
php artisan doctorDoctor recorre la suite de diagnósticos y muestra un informe. Cuando encuentra un fallo que sabe reparar, no lo arregla por su cuenta: pregunta.
Storage is writable: The application cannot write to every required storage directory.
Make the storage directories writable? (yes/no) [yes]Algunos fallos admiten varias reparaciones válidas y en ese caso Doctor muestra una lista de opciones en lugar de un sí/no. Si tu store de caché por defecto no responde, por ejemplo, te ofrece los stores configurados que sí funcionan más una entrada para dejarlo como está y arreglarlo a mano. Si declinas, el fallo se queda en el informe con su texto de remediación.
Los seis estados
Todo diagnóstico devuelve uno de estos seis estados, y de ellos depende el código de salida del comando:
Estado | Significado | Afecta al exit code |
|---|---|---|
| La comprobación pasó | No |
| Información que merece la pena ver | No |
| Problema potencial que quizá no requiere acción | Solo con |
| Problema que hay que resolver | Sí |
| La comprobación no aplica a este entorno | No |
| El diagnóstico lanzó una excepción | Sí |
Por defecto el comando sale con código distinto de cero cuando algo falla o revienta. Si quieres que los avisos también rompan el build, usa --fail-on=warn. Si prefieres que Doctor solo informe y nunca falle, --fail-on=never.
Para cortar en el primer fallo:
php artisan doctor --bailLos avisos, notices, pases y skips no detienen la ejecución.
Qué repara realmente --fix
php artisan doctor --fixCon --fix no hay preguntas: se aplican todas las reparaciones que no requieran una decisión humana. Las de primera parte cubren reparaciones locales deterministas:
Crear un
.envque faltaGenerar la
APP_KEYDesactivar el modo debug en producción
Añadir
.enval.gitignoreCrear el enlace simbólico de storage público
Reparar los permisos de los directorios de storage
Las reparaciones que necesitan elegir entre opciones se reportan como fallos normales con su texto de remediación. Y --fix solo está disponible con los formatos de salida CLI y agente: Doctor lo rechaza con los formatos JSON y GitHub para que un informe pensado para máquinas nunca modifique la aplicación.
Qué comprueba de serie
La suite por defecto cubre:
Entorno: presencia del
.env,APP_KEY, versión de PHP, extensiones requeridas y recomendadas, zona horariaComposer: dependencias instaladas, autoload optimizado, problemas reparables en el
composer.lockConfiguración: los ficheros de config cargan y se pueden cachear, los valores que requieren los drivers activos están definidos, y los ficheros de caché de bootstrap se reportan cuando su presencia no encaja con el entorno
Base de datos: la conexión por defecto responde, el fichero SQLite existe cuando toca, y las migraciones pendientes pueden aplicarse en local
Caché, colas, scheduler y sesión: los drivers configurados son alcanzables, se comprueban las conexiones Redis activas, se marcan las colas
syncfuera de local y se listan las tareas programadas como noticeAlmacenamiento: el disco por defecto responde, los directorios requeridos son escribibles y existe el symlink de
storage:linkSeguridad: el modo debug encaja con el entorno, el
.envestá ignorado y las dependencias pasan la auditoría
Modos de entorno
Hay hechos que no se pueden juzgar en abstracto. Una conexión de cola sync es lo normal en tu máquina; en producción significa que los jobs se ejecutan dentro de la petición web. Doctor resuelve la aplicación a uno de dos modos, local o production, y el modo cambia el veredicto, no solo el mensaje. La ausencia de cachés de bootstrap avisa en producción y pasa en local; su presencia pasa en producción y genera un notice en local, porque una caché obsoleta es una causa habitual de que los cambios recientes no se vean.
Doctor reconoce los nombres convencionales local, production y staging. Si usas otros, agrúpalos en la configuración:
'environments' => [
'local' => ['local', 'dev'],
'production' => ['production', 'staging', 'qa'],
],Cualquier entorno no listado se trata como production. Es la decisión correcta: un entorno desconocido se somete al criterio más estricto en lugar de librarse por defecto.
Seleccionar diagnósticos
Puedes filtrar por nombre de clase, grupo, paquete o comodín de paquete, repitiendo la opción o separando valores por comas:
php artisan doctor --only=storage
php artisan doctor --only=StorageIsWritable
php artisan doctor --only=vendor/package
php artisan doctor --except=laravel/*Para dejar la selección fija, publica la configuración:
php artisan vendor:publish --tag=doctor-config'only' => [
// 'laravel/doctor',
// 'security',
],
'except' => [
// \Laravel\Doctor\Diagnostics\EnvironmentFileIsGitIgnored::class,
],Los only configurados actúan como lista blanca persistente: pasar --only en tiempo de ejecución la estrecha todavía más. Los except configurados y los de línea de comandos se suman.
Crear tus propios diagnósticos
Un diagnóstico es una clase que declara su definición como propiedades, igual que un comando de Artisan. Extiende Laravel\Doctor\Diagnostic e implementa un método check() que devuelva un DiagnosticResult.
php artisan make:diagnostic HorizonIsRunning --fixableEl comando genera la clase en app/Doctor/Diagnostics. Si no pasas --fixable, te pregunta si el diagnóstico debe ofrecer reparación.
Para ofrecer una, implementa el contrato Laravel\Doctor\Contracts\Fixable y marca cada fallo reparable con ->fixable(). Doctor solo intenta reparar resultados fail marcados explícitamente en clases que implementan el contrato; el resto de fallos y los error inesperados nunca se tocan de forma automática.
<?php
namespace App\Doctor\Diagnostics;
use Illuminate\Support\Facades\Artisan;
use Laravel\Doctor\Contracts\Fixable;
use Laravel\Doctor\Diagnostic;
use Laravel\Doctor\EnvironmentMode;
use Laravel\Doctor\Results\DiagnosticResult;
use Laravel\Doctor\Results\FixResult;
use Laravel\Doctor\Results\Link;
use Laravel\Doctor\Results\Message;
class ApplicationKeyIsSet extends Diagnostic implements Fixable
{
public string $name = 'App key is set';
public string $group = 'environment';
protected function messages(): array
{
return [
'configured' => 'La clave de aplicación está configurada.',
'missing' => Message::make(
summary: 'La clave de aplicación no está configurada.',
remediation: 'Genera una clave con `php artisan key:generate`.',
confirmation: '¿Generar la clave con `php artisan key:generate`?',
)->link(Link::docs('encryption')),
'generated' => 'La clave de aplicación se ha generado.',
'generation-failed' => 'No se ha podido generar la clave.',
];
}
public function check(): DiagnosticResult
{
$key = config('app.key');
if (is_string($key) && trim($key) !== '') {
return $this->pass('configured');
}
return $this->fail('missing')->fixable(EnvironmentMode::Local);
}
public function fix(DiagnosticResult $result): FixResult
{
Artisan::call('key:generate', ['--force' => true]);
$key = config('app.key');
if (! is_string($key) || trim($key) === '') {
return $this->fixFailed('generation-failed')
->withDetails(trim(Artisan::output()));
}
return $this->fixed('generated');
}
}Fíjate en dónde vive el texto. Los mensajes van en el registro messages(), no dispersos por la lógica. Una cadena simple se usa como resumen del resultado; Message::make() permite además añadir remediación, enlaces a documentación y el texto de confirmación del prompt.
Los estados se declaran donde se toma la decisión: devuelve $this->pass(), $this->fail(), $this->warn(), $this->notice(), $this->skip() o $this->error() desde check(), y $this->fixed() o $this->fixFailed() desde fix(). Cada resultado recibe además un código estable derivado de la clase y el mensaje, del tipo application-key-is-set.missing.
Los resúmenes admiten interpolación con tokens {placeholder}:
'unsatisfied' => Message::make(
summary: 'PHP {version} no satisface [{constraint}].',
remediation: 'Usa un binario de PHP que cumpla la restricción de composer.json.',
),
return $this->fail('unsatisfied', [
'version' => PHP_VERSION,
'constraint' => $constraint,
]);Reserva los tokens para valores cortos e identificativos: versiones, rutas, contadores. La evidencia sin límite de tamaño —mensajes de excepción, salida de procesos, listas de fallos— va en withDetails().
Reparaciones con varias opciones
Cuando un fallo admite varios arreglos válidos y la elección correcta es humana, declara las opciones con fixOptions() después de fixable():
return $this->fail('unreachable')
->fixable(EnvironmentMode::Local)
->fixOptions(['file' => 'File', 'redis' => 'Redis']);
public function fix(DiagnosticResult $result, ?string $option = null): FixResult
{
// $option es uno de los valores declarados...
}Calcula las opciones en check() y filtra las que realmente vayan a funcionar: sondea los servicios candidatos, comprueba que los paquetes cliente estén instalados y descarta lo que fallaría. Un resultado sin opciones viables debería quedarse como no reparable y confiar en su texto de remediación.
Toda lista de opciones termina con una entrada que rechaza la reparación, Skip — leave unfixed por defecto. Puedes nombrarla mejor cuando la selección actual tenga sentido explícito:
->fixOptions(['file' => 'File'], decline: 'Mantener Redis (repararlo a mano)');Registrar diagnósticos
Desde un service provider:
<?php
use App\Doctor\Diagnostics\ApplicationKeyIsSet;
use Laravel\Doctor\Facades\Doctor;
public function boot(): void
{
Doctor::diagnostic(ApplicationKeyIsSet::class);
}Los paquetes usan exactamente la misma API. El informe muestra la procedencia de cada diagnóstico junto a su nombre, que es el paquete de Composer que lo aporta:
[fail] Storage is writable (laravel/doctor): The application cannot write to every required storage directory.
[pass] SQLite database exists (acme/application): The SQLite database file exists.
[warn] Horizon is running (laravel/horizon): Horizon is not currently running.Ejecución programática
Doctor también funciona sin el comando. El método run() ejecuta los diagnósticos registrados y devuelve un DiagnosticReport:
<?php
use Laravel\Doctor\Facades\Doctor;
$report = Doctor::run();
if ($report->hasFailures()) {
// ...
}Las ejecuciones programáticas respetan los selectores only y except de la configuración, y admiten estrecharlos más:
$report = Doctor::only('security')
->except(SomeDiagnostic::class)
->run();Las llamadas repetidas a only se intersecan, de modo que cada una acota dentro de la anterior.
Para aplicar reparaciones en una ejecución programática se usa fixUsing(). El callback recibe cada diagnóstico fallido que ofrezca reparación y decide: false para saltarla, true para aplicar una reparación simple, o uno de los valores de opción para aplicarla con esa elección. Cuando se aplica alguna, Doctor vuelve a ejecutar los diagnósticos para que el informe refleje la aplicación ya reparada.
$report = Doctor::fixUsing(
fn ($outcome) => $outcome->fixRequiresOption() ? false : true,
)->run();
$report->fixes();Devolver true para un resultado que declara opciones lanza una LogicException: una reparación con elección nunca se ejecuta sin elección.
Formatos de salida
Además del CLI:
php artisan doctor --format=json
php artisan doctor --format=githubEl formato github emite anotaciones de GitHub Actions.
Salida para agentes
Aquí está la parte interesante. Cuando Doctor detecta que se está ejecutando dentro de un agente de programación como Claude Code o Cursor, cambia por defecto a un formato optimizado para agentes: una única línea de JSON con los contadores agregados delante y solo los resultados accionables detallados.
{"tool":"doctor","result":"failed","diagnostics":27,"failed":1,"warnings":1,"notices":0,"passed":19,"skipped":6,"issues":[{"name":".env file exists","status":"fail","summary":"The application does not have an environment file.","fix":"Run `cp .env.example .env`, then review the copied values.","fixable":true}]}Los issues marcados fixable se pueden arreglar volviendo a ejecutar Doctor con --fix, que aplica todas las reparaciones deterministas disponibles, repite los diagnósticos y añade los resultados al array fixes del payload. Los issues que llevan un mapa options en lugar del flag fixable requieren una decisión que --fix no va a tomar: el agente debería aplicar el cambio siguiendo el texto de remediación, o escalar la lista de opciones a una persona.
Un --format explícito siempre gana sobre la detección:
php artisan doctor --format=cli
php artisan doctor --format=agentY para ver la salida de agente sin estar en uno:
AI_AGENT=test php artisan doctorDónde encaja en tu flujo
En CI, php artisan doctor con el código de salida por defecto ya te sirve como puerta de entrada: si el entorno de la pipeline no está bien montado, el build falla antes de ejecutar un solo test. Añade --fail-on=warn si quieres ser estricto y --format=github para ver las anotaciones directamente sobre el diff.
En despliegue, --only=security te da una comprobación rápida de que el debug está apagado y el .env no se ha colado en el repositorio.
Y en onboarding es donde más se nota: un php artisan doctor --fix recién clonado el repositorio crea el .env, genera la clave y monta el enlace de storage sin que nadie tenga que acordarse de la lista.
Puntos clave
Se instala con
composer require laravel/doctor --devy requiere PHP 8.3 y Laravel 12 o 13Cada diagnóstico devuelve uno de seis estados;
failyerrorrompen el exit code,warnsolo con--fail-on=warn--fixaplica reparaciones deterministas sin preguntar, pero nunca las que exigen elegir entre opcionesLos diagnósticos propios extienden
Laravel\Doctor\Diagnosticy se generan conmake:diagnosticLos modos
localyproductioncambian el veredicto de las comprobaciones, no solo el mensajeLa salida para agentes convierte Doctor en una puerta de verificación parseable para Claude Code o Cursor
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción