Aprende a añadir interfaces interactivas atractivas a tus comandos Artisan con el paquete laravel/prompts. Se presentan funciones para entrada de texto, selección, búsqueda, autocompletado, tareas, streaming y mucho más.
Laravel Prompts es un paquete PHP para añadir formularios interactivos atractivos y fáciles de usar a las aplicaciones de línea de comandos.
Ofrece una experiencia parecida a la de un formulario web, con placeholders, validación, etc.Puedes invocarlo directamente desde tus comandos Artisan, lo que hace muy sencilla e intuitiva la interacción con el usuario.
Laravel Prompts es compatible con macOS, Linux y Windows (WSL).
En entornos no compatibles se activa automáticamente un modo alternativo.
Con text() pides al usuario que introduzca una cadena.
use function Laravel\Prompts\text;$name = text('What is your name?');
Puedes indicar placeholder, valor por defecto y un hint.
$name = text( label: 'What is your name?', placeholder: 'E.g. Taylor Otwell', default: $user?->name, hint: 'This will be displayed on your profile.');
Con required haces la entrada obligatoria (puedes personalizar el mensaje).
$name = text( label: 'What is your name?', required: 'Your name is required.');
Con la closure validate puedes añadir validaciones. Devuelve una cadena de error o null si pasa.
$name = text( label: 'What is your name?', validate: fn (string $value) => match (true) { strlen($value) < 3 => 'The name must be at least 3 characters.', strlen($value) > 255 => 'The name must not exceed 255 characters.', default => null });
También puedes usar reglas de validación de Laravel en formato array.
$name = text( label: 'What is your name?', validate: ['name' => 'required|max:255|unique:users']);
number() acepta valores numéricos. Puedes cambiarlos con las flechas.
use function Laravel\Prompts\number;$copies = number( label: 'How many copies would you like?', default: 1, validate: ['copies' => 'required|integer|min:1|max:100']);
password() funciona como el texto normal, pero oculta lo que se escribe.
use function Laravel\Prompts\password;$password = password('What is your password?');
$password = password( label: 'What is your password?', placeholder: 'password', hint: 'Minimum 8 characters.', validate: fn (string $value) => strlen($value) < 8 ? 'The password must be at least 8 characters.' : null);
confirm() pregunta con dos opciones y devuelve true o false.
use function Laravel\Prompts\confirm;$confirmed = confirm('Do you accept the terms?');
Puedes personalizar el valor por defecto y los textos.
$confirmed = confirm( label: 'Do you accept the terms?', default: false, yes: 'I accept', no: 'I decline', hint: 'The terms must be accepted to continue.');
Con select() el usuario elige una opción de un listado.
use function Laravel\Prompts\select;$role = select( label: 'What role should the user have?', options: ['Member', 'Contributor', 'Owner'], default: 'Owner');
Si pasas un array asociativo, devuelve la clave en lugar de la etiqueta.
$role = select( label: 'What role should the user have?', options: [ 'member' => 'Member', 'contributor' => 'Contributor', 'owner' => 'Owner', ], default: 'owner');
Con scroll cambias el número de opciones visibles antes de hacer scroll (por defecto 5).
$role = select( label: 'Which category would you like to assign?', options: Category::pluck('name', 'id'), scroll: 10);
multiselect() permite elegir varias opciones a la vez.
use function Laravel\Prompts\multiselect;$permissions = multiselect( label: 'What permissions should be assigned?', options: ['Read', 'Create', 'Update', 'Delete']);
Con required obligas a elegir al menos una.
$permissions = multiselect( label: 'What permissions should be assigned?', options: ['Read', 'Create', 'Update', 'Delete'], required: 'At least one permission must be selected.');
search() actualiza la lista de opciones a medida que se escribe. La closure devuelve las candidatas.
use function Laravel\Prompts\search;$userId = search( label: 'Search for the user that should receive the mail', options: fn (string $value) => strlen($value) > 0 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : []);
multisearch() combina la búsqueda dinámica con selección múltiple.
use function Laravel\Prompts\multisearch;$userIds = multisearch( label: 'Search for the users that should receive the mail', options: fn (string $value) => strlen($value) > 0 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : []);
autocomplete() muestra sugerencias como ghost text. A diferencia de suggest(), la sugerencia se muestra en línea y se acepta con Tab o la flecha derecha.
use function Laravel\Prompts\autocomplete;$name = autocomplete( label: 'What is your name?', options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim']);
Puedes indicar placeholder, valor por defecto y hint.
$name = autocomplete( label: 'What is your name?', options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'], placeholder: 'E.g. Taylor', default: $user?->name, hint: 'Use Tab to accept, up/down to cycle.');
Con una closure filtras las opciones dinámicamente.
Todas las funciones de prompt aceptan el argumento validate.
$name = text( label: 'What is your name?', validate: fn (string $value) => match (true) { strlen($value) < 3 => 'The name must be at least 3 characters.', strlen($value) > 255 => 'The name must not exceed 255 characters.', default => null });
Si la closure devuelve una cadena, se muestra como error y se pide de nuevo el valor. Con null se considera válido.También puedes usar reglas de validación de Laravel en formato array.
$name = text( label: 'What is your name?', validate: ['name' => 'required|max:255|unique:users']);
Para transformar la entrada antes de validarla, usa transform.
$name = text( label: 'What is your name?', transform: fn (string $value) => trim($value), validate: fn (string $value) => match (true) { strlen($value) === 0 => 'The name must not be empty.', default => null });
Con form() agrupas varios prompts y puedes cancelarlos todos antes de completarlos.
use function Laravel\Prompts\form;$responses = form() ->text('What is your name?', required: true, name: 'name') ->password('What is your password?', validate: ['password' => 'min:8'], name: 'password') ->confirm('Do you accept the terms?') ->submit();$name = $responses['name'];$password = $responses['password'];$confirmed = $responses[2];
Existen funciones que muestran mensajes con estilos predefinidos.
use function Laravel\Prompts\info;use function Laravel\Prompts\warning;use function Laravel\Prompts\error;use function Laravel\Prompts\alert;use function Laravel\Prompts\note;note('Prepare for launch.');info('User created successfully.');warning('This action cannot be undone.');error('Something went wrong.');alert('Critical failure detected!');
callout() muestra una etiqueta y contenido dentro de un marco. Ideal para destacar información importante (resúmenes de despliegue, detalles de error, actualizaciones de estado…).
use function Laravel\Prompts\callout;callout( label: 'Environment Configured', content: 'Your application is running in production mode with 4 workers.',);
Con el argumento type ('warning' o 'error') cambias el estilo visual.
callout( label: 'Deprecation Notice', content: 'The `--prefer-stable` flag will be removed in v4.0.', type: 'warning',);callout( label: 'Database Connection Failed', content: 'Could not connect to MySQL on 127.0.0.1:3306.', type: 'error',);
Con info añades un pie con metadatos (IDs, timestamps…).
callout( label: 'Deployment Summary', content: 'Your application was deployed to production.', info: 'deploy-id: d4f8a2c',);
En lugar de un string, puedes pasar un array para crear un callout estructurado. La clase Element incluye factories para títulos, listas con viñetas, listas numeradas, listas clave-valor y enlaces.
use Laravel\Prompts\Elements\Element;use function Laravel\Prompts\callout;callout('Deployment Summary', [ 'Your application was deployed to production at 2024-03-15 14:32 UTC.', Element::heading('What Changed'), Element::bulletedList([ 'Migrated 3 pending database migrations', 'Cleared and rebuilt route cache', 'Restarted 4 queue workers', ]), Element::heading('Next Steps'), Element::numberedList([ 'Verify the health check endpoint at /up', 'Monitor error rates for the next 15 minutes', 'Confirm background jobs are processing', ]),]);
Con Element::keyValueList puedes mostrar datos con etiquetas.
callout('Database Connection Failed', [ 'Could not connect to the database server.', Element::keyValueList([ 'Host' => '127.0.0.1', 'Port' => '3306', 'Database' => 'forge', 'Status' => 'Connection refused', ]),], type: 'error');
Element::link genera hipervínculos clicables en terminales compatibles con OSC 8. Puedes pasar solo la URL o URL + etiqueta.
callout('Server Health Check', [ 'Multiple services are reporting degraded performance.', Element::heading('Affected Services'), 'Look here: '.Element::link('https://example.com/health', 'Health Dashboard'), Element::link('https://example.com/health'),]);
Si omites la etiqueta, se muestra la propia URL como texto del enlace.
progress() muestra visualmente el avance de un proceso iterativo.
use function Laravel\Prompts\progress;$users = progress( label: 'Updating users', steps: User::all(), callback: fn ($user) => $this->performTask($user), hint: 'This may take some time.');
task() muestra un spinner y un área de log scrollable en vivo mientras se ejecuta la callback. Ideal para procesos largos como instalación de dependencias o despliegues, para ir viendo qué ocurre en tiempo real.
use function Laravel\Prompts\task;task( label: 'Installing dependencies', callback: function ($logger) { // Proceso largo... });
La callback recibe una instancia de Logger para escribir líneas y mensajes de estado en tiempo real.
task() requiere la extensión PHP pcntl. Si no está disponible, se degrada a una visualización estática.
Con label actualizas la etiqueta durante la ejecución. Con subLabel muestras una sub-etiqueta atenuada debajo. Una cadena vacía la borra. Puedes fijar una sub-etiqueta inicial con el argumento subLabel.
Para procesos que generan texto de forma incremental (por ejemplo, respuestas de IA), usa partial para hacer streaming palabra a palabra y commitPartial para consolidar.
task( label: 'Generating response...', callback: function ($logger) { foreach ($words as $word) { $logger->partial($word . ' '); } $logger->commitPartial(); });
Por defecto se muestran hasta 10 líneas de log scrollable. Puedes cambiarlo con limit. Con keepSummary: true los mensajes de estado permanecen en pantalla al terminar.
stream() muestra texto progresivamente. Es ideal para respuestas generadas por IA o datos que llegan por chunks.
use function Laravel\Prompts\stream;$stream = stream();foreach ($words as $word) { $stream->append($word . ' '); usleep(25_000); // Simular retardo entre chunks}$stream->close();
append añade texto al stream con un efecto de aparición. Cuando termines, invoca close para consolidar la salida y restaurar el cursor.
Ancho: si etiquetas, opciones o mensajes de validación exceden las columnas disponibles, se truncan. Para una terminal de 80 columnas, mantente por debajo de 74 caracteres.Alto: en los prompts que aceptan scroll, el valor se ajusta automáticamente al alto de la terminal, dejando espacio para los mensajes de validación.
En entornos no compatibles (Windows sin WSL, por ejemplo) se activa un fallback automático.
Por defecto se usan los métodos integrados de Laravel como $this->ask() o $this->choice().
use Laravel\Prompts\Prompt;Prompt::fake(['Taylor', true]);$name = text('What is your name?');$confirmed = confirm('Do you accept the terms?');Prompt::assertOutputContains('What is your name?');
Con los helpers de test de Artisan puedes hacer aserciones también sobre las funciones informativas.
// Pesttest('report generation', function () { $this->artisan('report:generate') ->expectsPromptsInfo('Welcome to the application!') ->expectsPromptsWarning('This action cannot be undone') ->expectsPromptsError('Something went wrong') ->expectsPromptsAlert('Important notice!') ->expectsPromptsTable( headers: ['Name', 'Email'], rows: [ ['Taylor Otwell', '[email protected]'], ] ) ->assertExitCode(0);});
// PHPUnitpublic function test_report_generation(): void{ $this->artisan('report:generate') ->expectsPromptsInfo('Welcome to the application!') ->expectsPromptsWarning('This action cannot be undone') ->expectsPromptsTable( headers: ['Name', 'Email'], rows: [['Taylor Otwell', '[email protected]']] ) ->assertExitCode(0);}