- Введение
- Установка
- Доступные подсказки
- Информационные сообщения
- Таблицы
- Индикатор загрузки
- Полоса прогресса
- Особенности терминала
- Неподдерживаемые среды и альтернативы
#Введение
Laravel Prompts — это PHP-пакет для добавления красивых и удобных форм в ваши консольные приложения с функциями, похожими на браузер, включая placeholder и валидацию.
Laravel Prompts идеально подходит для получения ввода пользователя в ваших консольных командах Artisan, но может использоваться и в любом другом PHP-проекте с командной строкой.
Laravel Prompts поддерживает macOS, Linux и Windows с WSL. Для дополнительной информации смотрите документацию по неподдерживаемым средам и альтернативам.
#Установка
Laravel Prompts уже включён в последнюю версию Laravel.
Laravel Prompts также можно установить в другие PHP-проекты с помощью менеджера пакетов Composer:
composer require laravel/prompts
#Доступные подсказки
#Текст
Функция text задаёт пользователю вопрос, принимает ввод и возвращает его:
use function Laravel\Prompts\text;
$name = text('What is your name?');
Вы также можете указать placeholder, значение по умолчанию и информационную подсказку:
$name = text(
label: 'What is your name?',
placeholder: 'E.g. Taylor Otwell',
default: $user?->name,
hint: 'This will be displayed on your profile.'
);
#Обязательные значения
Если требуется обязательный ввод, передайте аргумент required:
$name = text(
label: 'What is your name?',
required: true
);
Для настройки сообщения валидации можно передать строку:
$name = text(
label: 'What is your name?',
required: 'Your name is required.'
);
#Дополнительная валидация
Для дополнительной логики валидации можно передать замыкание в аргумент 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
}
);
Замыкание получит введённое значение и может вернуть сообщение об ошибке или null, если валидация прошла успешно.
#Пароль
Функция password похожа на text, но ввод пользователя скрывается при наборе в консоли. Это удобно для ввода конфиденциальных данных, например паролей:
use function Laravel\Prompts\password;
$password = password('What is your password?');
Вы также можете указать placeholder и информационную подсказку:
$password = password(
label: 'What is your password?',
placeholder: 'password',
hint: 'Minimum 8 characters.'
);
#Обязательные значения
Если требуется обязательный ввод, передайте аргумент required:
$password = password(
label: 'What is your password?',
required: true
);
Для настройки сообщения валидации можно передать строку:
$password = password(
label: 'What is your password?',
required: 'The password is required.'
);
#Дополнительная валидация
Для дополнительной логики валидации можно передать замыкание в аргумент validate:
$password = password(
label: 'What is your password?',
validate: fn (string $value) => match (true) {
strlen($value) < 8 => 'The password must be at least 8 characters.',
default => null
}
);
Замыкание получит введённое значение и может вернуть сообщение об ошибке или null, если валидация прошла успешно.
#Подтверждение
Если нужно запросить у пользователя подтверждение "да" или "нет", используйте функцию confirm. Пользователь может выбрать ответ стрелками или нажать y или n. Функция возвращает true или false.
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.'
);
#Требовать "Да"
При необходимости можно обязать пользователя выбрать "Да", передав аргумент required:
$confirmed = confirm(
label: 'Do you accept the terms?',
required: true
);
Для настройки сообщения валидации можно передать строку:
$confirmed = confirm(
label: 'Do you accept the terms?',
required: 'You must accept the terms to continue.'
);
#Выбор
Если нужно, чтобы пользователь выбрал из заранее заданного набора вариантов, используйте функцию select:
use function Laravel\Prompts\select;
$role = select(
'What role should the user have?',
['Member', 'Contributor', 'Owner'],
);
Можно указать вариант по умолчанию и информационную подсказку:
$role = select(
label: 'What role should the user have?',
options: ['Member', 'Contributor', 'Owner'],
default: 'Owner',
hint: 'The role may be changed at any time.'
);
В аргумент options можно передать ассоциативный массив, чтобы возвращался выбранный ключ вместо значения:
$role = select(
label: 'What role should the user have?',
options: [
'member' => 'Member',
'contributor' => 'Contributor',
'owner' => 'Owner'
],
default: 'owner'
);
До пяти вариантов отображаются без прокрутки. Можно изменить это, передав аргумент scroll:
$role = select(
label: 'Which category would you like to assign?',
options: Category::pluck('name', 'id'),
scroll: 10
);
#Валидация
В отличие от других функций, select не принимает аргумент required, так как нельзя не выбрать вариант. Однако можно передать замыкание в validate, чтобы показать вариант, но запретить его выбор:
$role = select(
label: 'What role should the user have?',
options: [
'member' => 'Member',
'contributor' => 'Contributor',
'owner' => 'Owner'
],
validate: fn (string $value) =>
$value === 'owner' && User::where('role', 'owner')->exists()
? 'An owner already exists.'
: null
);
Если options — ассоциативный массив, замыкание получит выбранный ключ, иначе — выбранное значение. Замыкание может вернуть сообщение об ошибке или null, если валидация прошла.
#Множественный выбор
Если нужно, чтобы пользователь мог выбрать несколько вариантов, используйте функцию multiselect:
use function Laravel\Prompts\multiselect;
$permissions = multiselect(
'What permissions should be assigned?',
['Read', 'Create', 'Update', 'Delete']
);
Можно указать варианты по умолчанию и информационную подсказку:
use function Laravel\Prompts\multiselect;
$permissions = multiselect(
label: 'What permissions should be assigned?',
options: ['Read', 'Create', 'Update', 'Delete'],
default: ['Read', 'Create'],
hint: 'Permissions may be updated at any time.'
);
В аргумент options можно передать ассоциативный массив, чтобы возвращались выбранные ключи вместо значений:
$permissions = multiselect(
label: 'What permissions should be assigned?',
options: [
'read' => 'Read',
'create' => 'Create',
'update' => 'Update',
'delete' => 'Delete'
],
default: ['read', 'create']
);
До пяти вариантов отображаются без прокрутки. Можно изменить это, передав аргумент scroll:
$categories = multiselect(
label: 'What categories should be assigned?',
options: Category::pluck('name', 'id'),
scroll: 10
);
#Обязательный выбор
По умолчанию пользователь может не выбирать варианты или выбрать несколько. Чтобы обязать выбрать хотя бы один, передайте аргумент required:
$categories = multiselect(
label: 'What categories should be assigned?',
options: Category::pluck('name', 'id'),
required: true,
);
Для настройки сообщения валидации можно передать строку в required:
$categories = multiselect(
label: 'What categories should be assigned?',
options: Category::pluck('name', 'id'),
required: 'You must select at least one category',
);
#Валидация
Можно передать замыкание в validate, чтобы показать вариант, но запретить его выбор:
$permissions = multiselect(
label: 'What permissions should the user have?',
options: [
'read' => 'Read',
'create' => 'Create',
'update' => 'Update',
'delete' => 'Delete'
],
validate: fn (array $values) => ! in_array('read', $values)
? 'All users require the read permission.'
: null
);
Если options — ассоциативный массив, замыкание получит выбранные ключи, иначе — выбранные значения. Замыкание может вернуть сообщение об ошибке или null, если валидация прошла.
#Подсказка
Функция suggest используется для автодополнения возможных вариантов. Пользователь может ввести любой ответ, независимо от подсказок:
use function Laravel\Prompts\suggest;
$name = suggest('What is your name?', ['Taylor', 'Dayle']);
В качестве альтернативы вы можете передать замыкание в качестве второго аргумента функции suggest. Замыкание будет вызываться каждый раз, когда пользователь вводит новый символ. Замыкание должно принимать строковый параметр, содержащий ввод пользователя на данный момент, и возвращать массив вариантов автодополнения:
$name = suggest(
'What is your name?',
fn ($value) => collect(['Taylor', 'Dayle'])
->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
)
Можно указать placeholder, значение по умолчанию и информационную подсказку:
$name = suggest(
label: 'What is your name?',
options: ['Taylor', 'Dayle'],
placeholder: 'E.g. Taylor',
default: $user?->name,
hint: 'This will be displayed on your profile.'
);
#Обязательные значения
Если требуется обязательный ввод, передайте аргумент required:
$name = suggest(
label: 'What is your name?',
options: ['Taylor', 'Dayle'],
required: true
);
Для настройки сообщения валидации можно передать строку:
$name = suggest(
label: 'What is your name?',
options: ['Taylor', 'Dayle'],
required: 'Your name is required.'
);
#Дополнительная валидация
Для дополнительной логики валидации можно передать замыкание в аргумент validate:
$name = suggest(
label: 'What is your name?',
options: ['Taylor', 'Dayle'],
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
}
);
Замыкание получит введённое значение и может вернуть сообщение об ошибке или null, если валидация прошла успешно.
#Поиск
Если вариантов много, функция search позволяет пользователю ввести поисковый запрос для фильтрации результатов, а затем выбрать вариант стрелками:
use function Laravel\Prompts\search;
$id = search(
'Search for the user that should receive the mail',
fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: []
);
Замыкание получит введённый текст и должно вернуть массив вариантов. Если возвращён ассоциативный массив, будет возвращён ключ выбранного варианта, иначе — значение.
Можно указать placeholder и информационную подсказку:
$id = search(
label: 'Search for the user that should receive the mail',
placeholder: 'E.g. Taylor Otwell',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
hint: 'The user will receive an email immediately.'
);
До пяти вариантов отображаются без прокрутки. Можно изменить это, передав аргумент scroll:
$id = search(
label: 'Search for the user that should receive the mail',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
scroll: 10
);
#Валидация
Для дополнительной логики валидации можно передать замыкание в аргумент validate:
$id = search(
label: 'Search for the user that should receive the mail',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
validate: function (int|string $value) {
$user = User::findOrFail($value);
if ($user->opted_out) {
return 'This user has opted-out of receiving mail.';
}
}
);
Если замыкание options возвращает ассоциативный массив, то замыкание получит выбранный ключ, иначе — выбранное значение. Замыкание может вернуть сообщение об ошибке или null, если валидация проходит.
#Множественный поиск
Если вариантов много и нужно выбрать несколько, функция multisearch позволяет фильтровать результаты поиском, а затем выбирать варианты стрелками и пробелом:
use function Laravel\Prompts\multisearch;
$ids = multisearch(
'Search for the users that should receive the mail',
fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: []
);
Замыкание получит введённый текст и должно вернуть массив вариантов. Если возвращён ассоциативный массив, будут возвращены ключи выбранных вариантов, иначе — значения.
Можно указать placeholder и информационную подсказку:
$ids = multisearch(
label: 'Search for the users that should receive the mail',
placeholder: 'E.g. Taylor Otwell',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
hint: 'The user will receive an email immediately.'
);
До пяти вариантов отображаются без прокрутки. Можно изменить это, передав аргумент scroll:
$ids = multisearch(
label: 'Search for the users that should receive the mail',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
scroll: 10
);
#Обязательный выбор
По умолчанию пользователь может не выбирать варианты или выбрать несколько. Чтобы обязать выбрать хотя бы один, передайте аргумент required:
$ids = multisearch(
'Search for the users that should receive the mail',
fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
required: true,
);
Для настройки сообщения валидации можно передать строку в required:
$ids = multisearch(
'Search for the users that should receive the mail',
fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
required: 'You must select at least one user.'
);
#Валидация
Для дополнительной логики валидации можно передать замыкание в аргумент validate:
$ids = multisearch(
label: 'Search for the users that should receive the mail',
options: fn (string $value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: [],
validate: function (array $values) {
$optedOut = User::where('name', 'like', '%a%')->findMany($values);
if ($optedOut->isNotEmpty()) {
return $optedOut->pluck('name')->join(', ', ', and ').' have opted out.';
}
}
);
Если замыкание options возвращает ассоциативный массив, то ему будут переданы выбранные ключи; в противном случае — выбранные значения. Замыкание может вернуть сообщение об ошибке или null, если валидация проходит.
#Пауза
Функция pause отображает информационный текст и ждёт, пока пользователь подтвердит продолжение нажатием Enter / Return:
use function Laravel\Prompts\pause;
pause('Press ENTER to continue.');
#Информационные сообщения
Функции note, info, warning, error и alert используются для отображения информационных сообщений:
use function Laravel\Prompts\info;
info('Package installed successfully.');
#Таблицы
Функция table упрощает отображение данных в виде строк и столбцов. Нужно лишь указать имена столбцов и данные для таблицы:
use function Laravel\Prompts\table;
table(
['Name', 'Email'],
User::all(['name', 'email'])
);
#Индикатор загрузки
Функция spin отображает индикатор загрузки с необязательным сообщением во время выполнения указанного callback. Она показывает процесс и возвращает результат callback после завершения:
use function Laravel\Prompts\spin;
$response = spin(
fn () => Http::get('http://example.com'),
'Fetching response...'
);
Для анимации индикатора загрузки функция spin требует расширение PHP pcntl. Если расширение недоступно, будет показан статичный индикатор.
#Полосы прогресса
Для длительных задач полезно показывать полосу прогресса, информирующую пользователя о степени выполнения. С помощью функции progress Laravel отображает полосу и продвигает её при каждой итерации по заданному итерируемому значению:
use function Laravel\Prompts\progress;
$users = progress(
label: 'Updating users',
steps: User::all(),
callback: fn ($user) => $this->performTask($user),
);
Функция progress работает как map и возвращает массив с результатами каждой итерации callback.
Callback может принимать экземпляр \Laravel\Prompts\Progress, позволяя изменять метку и подсказку на каждой итерации:
$users = progress(
label: 'Updating users',
steps: User::all(),
callback: function ($user, $progress) {
$progress
->label("Updating {$user->name}")
->hint("Created on {$user->created_at}");
return $this->performTask($user);
},
hint: 'This may take some time.',
);
Иногда нужен более ручной контроль над продвижением полосы. Сначала задайте общее количество шагов, затем вызывайте метод advance после обработки каждого элемента:
$progress = progress(label: 'Updating users', steps: 10);
$users = User::all();
$progress->start();
foreach ($users as $user) {
$this->performTask($user);
$progress->advance();
}
$progress->finish();
#Особенности терминала
#Ширина терминала
Если длина метки, варианта или сообщения валидации превышает ширину терминала пользователя, она автоматически обрезается. Рекомендуется минимизировать длину таких строк для пользователей с узкими терминалами. Безопасная максимальная длина — 74 символа для терминала шириной 80 символов.
#Высота терминала
Для подсказок с аргументом scroll значение автоматически уменьшается, чтобы уместиться по высоте терминала пользователя, включая место для сообщения валидации.
#Неподдерживаемые среды и альтернативы
Laravel Prompts поддерживает macOS, Linux и Windows с WSL. Из-за ограничений Windows-версии PHP использование Laravel Prompts вне WSL на Windows в настоящее время невозможно.
По этой причине Laravel Prompts поддерживает альтернативные реализации, например Symfony Console Question Helper.
При использовании Laravel Prompts с фреймворком Laravel альтернативы для каждой подсказки уже настроены и автоматически активируются в неподдерживаемых средах.
#Условия использования альтернатив
Если вы не используете Laravel или хотите настроить условия использования альтернатив, можно передать булево значение в статический метод fallbackWhen класса Prompt:
use Laravel\Prompts\Prompt;
Prompt::fallbackWhen(
! $input->isInteractive() || windows_os() || app()->runningUnitTests()
);
#Поведение альтернатив
Если вы не используете Laravel или хотите настроить поведение альтернатив, можно передать замыкание в статический метод fallbackUsing каждого класса подсказки:
use Laravel\Prompts\TextPrompt;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
TextPrompt::fallbackUsing(function (TextPrompt $prompt) use ($input, $output) {
$question = (new Question($prompt->label, $prompt->default ?: null))
->setValidator(function ($answer) use ($prompt) {
if ($prompt->required && $answer === null) {
throw new \RuntimeException(is_string($prompt->required) ? $prompt->required : 'Required.');
}
if ($prompt->validate) {
$error = ($prompt->validate)($answer ?? '');
if ($error) {
throw new \RuntimeException($error);
}
}
return $answer;
});
return (new SymfonyStyle($input, $output))
->askQuestion($question);
});
Альтернативы настраиваются отдельно для каждого класса подсказки. Замыкание получит экземпляр класса подсказки и должно вернуть подходящее значение для неё.