Passer au contenu principal

Introduction

Laravel Prompts est un package PHP qui ajoute des formulaires interactifs élégants aux applications CLI. Placeholders, validation… une expérience proche des formulaires web. Utilisable directement dans les commandes Artisan pour interroger l’utilisateur.
Supporte macOS, Linux et Windows (WSL). Bascule sur un fallback dans les autres environnements.

Installation

Laravel Prompts est livré avec Laravel — rien à installer. Pour un autre projet PHP :
composer require laravel/prompts

Prompts de base

text — saisie texte

use function Laravel\Prompts\text;

$name = text('What is your name?');
Placeholder, valeur par défaut, aide :
$name = text(
    label: 'What is your name?',
    placeholder: 'E.g. Taylor Otwell',
    default: $user?->name,
    hint: 'This will be displayed on your profile.'
);
Rendez la saisie obligatoire.
$name = text(
    label: 'What is your name?',
    required: 'Your name is required.'
);
Validation via closure (retourner un message → erreur, null → OK).
$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
    }
);
Ou règles Laravel :
$name = text(
    label: 'What is your name?',
    validate: ['name' => 'required|max:255|unique:users']
);

textarea

use function Laravel\Prompts\textarea;

$story = textarea('Tell me a story.');

number

Flèches haut/bas pour ajuster.
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

Comme text, masqué.
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 — Oui/Non

use function Laravel\Prompts\confirm;

$confirmed = confirm('Do you accept the terms?');
$confirmed = confirm(
    label: 'Do you accept the terms?',
    default: false,
    yes: 'I accept',
    no: 'I decline',
    hint: 'The terms must be accepted to continue.'
);

select

use function Laravel\Prompts\select;

$role = select(
    label: 'What role should the user have?',
    options: ['Member', 'Contributor', 'Owner'],
    default: 'Owner'
);
Tableau associatif : la clé est retournée.
$role = select(
    label: 'What role should the user have?',
    options: [
        'member'      => 'Member',
        'contributor' => 'Contributor',
        'owner'       => 'Owner',
    ],
    default: 'owner'
);
Ajustez scroll (5 par défaut).
$role = select(
    label: 'Which category would you like to assign?',
    options: Category::pluck('name', 'id'),
    scroll: 10
);

multiselect

use function Laravel\Prompts\multiselect;

$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: ['Read', 'Create', 'Update', 'Delete']
);
Obligatoire :
$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: ['Read', 'Create', 'Update', 'Delete'],
    required: 'At least one permission must be selected.'
);

suggest

Suggestions + saisie libre.
use function Laravel\Prompts\suggest;

$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle']
);
Dynamique :
$name = suggest(
    label: 'What is your name?',
    options: fn (string $value) => collect(['Taylor', 'Tobi', 'Dries'])
        ->filter(fn ($name) => str_starts_with($name, $value))
        ->values()
        ->all()
);

search — recherche dynamique

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

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()
        : []
);

pause

use function Laravel\Prompts\pause;

pause('Press ENTER to continue.');

autocomplete — Complétion inline

autocomplete propose un texte fantôme, validable avec Tab ou flèche droite.
use function Laravel\Prompts\autocomplete;

$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim']
);
Options complètes :
$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.'
);
Closure dynamique :
$file = autocomplete(
    label: 'Which file?',
    options: fn (string $value) => collect($files)
        ->filter(fn ($file) => str_starts_with(strtolower($file), strtolower($value)))
        ->values()
        ->all(),
);

Validation

Chaque prompt accepte 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
    }
);
Retour chaîne = erreur, null = succès. Règles Laravel :
$name = text(
    label: 'What is your name?',
    validate: ['name' => 'required|max:255|unique:users']
);
Transformez avant validation avec 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
    }
);

Formulaires

form() regroupe des prompts et permet un cancel commun.
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];

Sortie informative

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!');

Callouts

callout() encadre un contenu avec un label. Idéal pour un résumé de déploiement, une erreur, un statut.
use function Laravel\Prompts\callout;

callout(
    label: 'Environment Configured',
    content: 'Your application is running in production mode with 4 workers.',
);
type: 'warning' ou 'error' pour changer le style.
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',
);
info ajoute un pied de contenu (ID, timestamp…).
callout(
    label: 'Deployment Summary',
    content: 'Your application was deployed to production.',
    info: 'deploy-id: d4f8a2c',
);

Contenu riche

Passez un tableau. Element fournit heading, listes, key/value, liens…
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',
    ]),
]);
keyValueList :
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 produit un lien cliquable dans les terminaux qui supportent OSC 8.
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'),
]);
Sans label, l’URL sert de texte.

Tables

use function Laravel\Prompts\table;

table(
    headers: ['Name', 'Email'],
    rows: User::all(['name', 'email'])->toArray()
);

Spin (loader)

Affiche un indicateur pendant l’exécution d’une closure.
use function Laravel\Prompts\spin;

$response = spin(
    message: 'Fetching response...',
    callback: fn () => Http::get('http://example.com')
);
spin() nécessite l’extension pcntl. Sans, aucun spinner.

Barres de progression

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.'
);
Contrôle manuel :
$progress = progress(label: 'Uploading files', steps: count($files));

$progress->start();

foreach ($files as $file) {
    $this->uploadFile($file);
    $progress->advance();
}

$progress->finish();

Task

task() combine spinner et zone de logs scrollable, utile pour installations, déploiements, etc.
use function Laravel\Prompts\task;

task(
    label: 'Installing dependencies',
    callback: function ($logger) {
        // Traitement long...
    }
);
Le callback reçoit un Logger.
task() nécessite pcntl. Sans, fallback statique.

Lignes de log

task(
    label: 'Installing dependencies',
    callback: function ($logger) {
        $logger->line('Resolving packages...');
        $logger->line('Downloading laravel/framework');
    }
);

Statuts

success, warning, error affichent des messages épinglés au-dessus.
task(
    label: 'Deploying application',
    callback: function ($logger) {
        $logger->line('Pulling latest changes...');
        $logger->success('Changes pulled!');

        $logger->line('Running migrations...');
        $logger->warning('No new migrations to run.');

        $logger->line('Clearing cache...');
        $logger->success('Cache cleared!');
    }
);

Mise à jour de label

label change la ligne principale, subLabel un sous-titre. subLabel('') l’efface. subLabel: initial via argument.
task(
    label: 'Deploying',
    callback: function ($logger) {
        $logger->subLabel('Building assets...');
        // ...
        $logger->subLabel('Running migrations...');
        // ...
        $logger->subLabel('');
    },
    subLabel: 'Preparing...'
);

Streaming

Pour les sorties incrémentales (IA…), partial puis commitPartial.
task(
    label: 'Generating response...',
    callback: function ($logger) {
        foreach ($words as $word) {
            $logger->partial($word . ' ');
        }

        $logger->commitPartial();
    }
);

Limite et résumé

Par défaut 10 lignes visibles. Personnalisez via limit. Pour conserver les messages de statut après la fin, keepSummary: true.
task(
    label: 'Deploying',
    callback: function ($logger) {
        $logger->success('Assets built');
        $logger->success('Migrations complete');
    },
    limit: 20,
    keepSummary: true,
);

Streams

stream() affiche du texte progressivement, avec effet fade-in.
use function Laravel\Prompts\stream;

$stream = stream();

foreach ($words as $word) {
    $stream->append($word . ' ');
    usleep(25_000); // Simulation de délai
}

$stream->close();
append ajoute avec effet ; close finalise et restaure le curseur.

Terminal

Titre

use function Laravel\Prompts\title;

title('My Application');
Chaîne vide pour réinitialiser :
title('');

Nettoyer l’écran

use function Laravel\Prompts\clear;

clear();

Considérations

Largeur : au-delà de la largeur du terminal, tout est tronqué. Pour 80 colonnes, ~74 caractères max. Hauteur : les prompts acceptant scroll s’auto-ajustent selon la hauteur (marge pour la validation).

Fallback

Sur les environnements non supportés (Windows non WSL…), Prompts bascule automatiquement — typiquement sur $this->ask(), $this->choice(), etc.

Tests

Compatible Pest et PHPUnit.
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?');
Helpers Artisan pour les fonctions d’info :
// Pest
test('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);
});
// PHPUnit
public 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);
}

Pages associées

Console Artisan

Utilisez Prompts dans vos commandes Artisan.
Dernière modification le 13 juillet 2026