简介

Laravel Prompts 是一个 PHP 包,用于为命令行应用程序添加美观且用户友好的表单,具备类似浏览器的功能,包括占位符文本和验证。

Prompts

Laravel Prompts 非常适合在 Artisan 控制台命令中接收用户输入,但也可用于任何命令行 PHP 项目。

Laravel Prompts 支持 macOS、Linux 以及搭载 WSL 的 Windows。如需更多信息,请参阅我们关于不支持环境与回退方案的文档。

安装

Laravel Prompts 已包含在 Laravel 的最新发布版本中。

Laravel Prompts 也可通过 Composer 包管理器安装到其他 PHP 项目中:

composer require laravel/prompts

可用的提示

文本

text 函数会向用户提出给定的问题,接收其输入,然后返回该输入:

use function Laravel\Prompts\text;

$name = text('What is your name?');

您还可以包含占位符文本、默认值和信息提示:

$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。

或者,您也可以利用 Laravel 的 验证器的强大功能。为此,请向 validate 参数提供一个包含属性名称及所需验证规则的数组:

$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.');

您还可以包含占位符文本、默认值和信息提示:

$story = textarea(
    label: 'Tell me a story.',
    placeholder: 'This is a story about...',
    hint: 'This will be displayed on your profile.'
);

必填值

如果您要求必须输入值,可以传入 required 参数:

$story = textarea(
    label: 'Tell me a story.',
    required: true
);

如果您想自定义验证消息,也可以传入一个字符串:

$story = textarea(
    label: 'Tell me a story.',
    required: 'A story is required.'
);

额外验证

最后,如果您希望执行额外的验证逻辑,可以将一个闭包传递给 validate 参数:

$story = textarea(
    label: 'Tell me a story.',
    validate: fn (string $value) => match (true) {
        strlen($value) < 250 => 'The story must be at least 250 characters.',
        strlen($value) > 10000 => 'The story must not exceed 10,000 characters.',
        default => null
    }
);

该闭包会接收已输入的值,可返回错误消息,若验证通过则返回 null。

或者,您也可以利用 Laravel 的 验证器的强大功能。为此,请向 validate 参数提供一个包含属性名称及所需验证规则的数组:

$story = textarea(
    label: 'Tell me a story.',
    validate: ['story' => 'required|max:10000']
);

数字

number 函数会向用户提出给定的问题,接收其数字输入,然后返回该输入。number 函数允许用户使用上下箭头键来调整数字:

use function Laravel\Prompts\number;

$number = number('How many copies would you like?');

您还可以包含占位符文本、默认值和信息提示:

$name = number(
    label: 'How many copies would you like?',
    placeholder: '5',
    default: 1,
    hint: 'This will be determine how many copies to create.'
);

必填值

如果您要求必须输入值,可以传入 required 参数:

$copies = number(
    label: 'How many copies would you like?',
    required: true
);

如果您想自定义验证消息,也可以传入一个字符串:

$copies = number(
    label: 'How many copies would you like?',
    required: 'A number of copies is required.'
);

额外验证

最后,如果您希望执行额外的验证逻辑,可以将一个闭包传递给 validate 参数:

$copies = number(
    label: 'How many copies would you like?',
    validate: fn (?int $value) => match (true) {
        $value < 1 => 'At least one copy is required.',
        $value > 100 => 'You may not create more than 100 copies.',
        default => null
    }
);

该闭包会接收已输入的值,可返回错误消息,若验证通过则返回 null。

或者,您也可以利用 Laravel 的 验证器的强大功能。为此,请向 validate 参数提供一个包含属性名称及所需验证规则的数组:

$copies = number(
    label: 'How many copies would you like?',
    validate: ['copies' => 'required|integer|min:1|max:100']
);

密码

password 函数与 text 函数类似,但用户在控制台输入时,其输入内容会被屏蔽。这在询问密码等敏感信息时非常有用:

use function Laravel\Prompts\password;

$password = password('What is your password?');

您还可以包含占位符文本和信息提示:

$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。

或者,您也可以利用 Laravel 的 验证器的强大功能。为此,请向 validate 参数提供一个包含属性名称及所需验证规则的数组:

$password = password(
    label: 'What is your password?',
    validate: ['password' => 'min:8']
);

确认

如果您需要向用户询问“是或否”的确认,可以使用 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(
    label: 'What role should the user have?',
    options: ['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
);

辅助信息

info 参数可用于显示当前高亮选项的附加信息。当传入闭包时,该闭包会接收当前高亮选项的值,并应返回一个字符串或 null:

$role = select(
    label: 'What role should the user have?',
    options: [
        'member' => 'Member',
        'contributor' => 'Contributor',
        'owner' => 'Owner',
    ],
    info: fn (string $value) => match ($value) {
        'member' => 'Can view and comment.',
        'contributor' => 'Can view, comment, and edit.',
        'owner' => 'Full access to all resources.',
        default => null,
    }
);

如果信息不依赖于当前高亮选项,也可以将静态字符串传递给 info 参数:

$role = select(
    label: 'What role should the user have?',
    options: ['Member', 'Contributor', 'Owner'],
    info: 'The role may be changed at any time.'
);

附加验证

与其他提示函数不同,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(
    label: 'What permissions should be assigned?',
    options: ['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
);

辅助信息

info 参数可用于显示当前高亮选项的附加信息。当传入闭包时,该闭包会接收当前高亮选项的值,并应返回一个字符串或 null:

$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: [
        'read' => 'Read',
        'create' => 'Create',
        'update' => 'Update',
        'delete' => 'Delete',
    ],
    info: fn (string $value) => match ($value) {
        'read' => 'View resources and their properties.',
        'create' => 'Create new resources.',
        'update' => 'Modify existing resources.',
        'delete' => 'Permanently remove resources.',
        default => null,
    }
);

要求选择值

默认情况下,用户可以选择零个或多个选项。您可以通过传递 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(
    label: 'What is your name?',
    options: fn ($value) => collect(['Taylor', 'Dayle'])
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
);

您还可以包含占位符文本、默认值和提示信息:

$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.'
);

辅助信息

info 参数可用于显示当前高亮选项的附加信息。当传入闭包时,该闭包会接收当前高亮选项的值,并应返回一个字符串或 null:

$name = suggest(
    label: '你叫什么名字?',
    options: ['Taylor', 'Dayle'],
    info: fn (string $value) => match ($value) {
        'Taylor' => '管理员',
        'Dayle' => '贡献者',
        default => null,
    }
);

必填值

如果你要求必须输入一个值,可以传入 required 参数:

$name = suggest(
    label: '你叫什么名字?',
    options: ['Taylor', 'Dayle'],
    required: true
);

如果你想自定义验证消息,也可以传入一个字符串:

$name = suggest(
    label: '你叫什么名字?',
    options: ['Taylor', 'Dayle'],
    required: '必须填写你的名字。'
);

额外验证

最后,如果你想执行额外的验证逻辑,可以将一个闭包传递给 validate 参数:

$name = suggest(
    label: '你叫什么名字?',
    options: ['Taylor', 'Dayle'],
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => '名字必须至少包含 3 个字符。',
        strlen($value) > 255 => '名字不能超过 255 个字符。',
        default => null
    }
);

该闭包会接收用户已输入的值,可以返回一条错误消息,若验证通过则返回 null。

或者,你可以借助 Laravel 的 验证器(validator)。为此,请向 validate 参数提供一个包含属性名称和所需验证规则的数组:

$name = suggest(
    label: '你叫什么名字?',
    options: ['Taylor', 'Dayle'],
    validate: ['name' => 'required|min:3|max:255']
);

搜索

如果供用户选择的选项很多,search 函数允许用户输入搜索查询来筛选结果,然后再使用方向键选择选项:

use function Laravel\Prompts\search;

$id = search(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : []
);

该闭包会接收用户到目前为止输入的文本,并且必须返回一个选项数组。如果返回的是关联数组,则将返回所选选项的键,否则返回其值。

当筛选一个打算返回值的数组时,应使用 array_values 函数或 values 集合方法,以确保数组不会变成关联数组:

$names = collect(['Taylor', 'Abigail']);

$selected = search(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => $names
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
        ->values()
        ->all(),
);

你也可以添加占位符文本和提示信息:

$id = search(
    label: '搜索应接收邮件的用户',
    placeholder: '例如:Taylor Otwell',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    hint: '该用户将立即收到一封电子邮件。'
);

在列表开始滚动之前,最多会显示五个选项。你可以传入 scroll 参数来自定义该数量:

$id = search(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    scroll: 10
);

次要信息

info 参数可用于显示当前高亮选项的附加信息。当传入一个闭包时,该闭包会接收当前高亮选项的值,并且应返回一个字符串或 null:

$id = search(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    info: fn (int $userId) => User::find($userId)?->email
);

额外验证

如果你想执行额外的验证逻辑,可以将一个闭包传递给 validate 参数:

$id = search(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    validate: function (int|string $value) {
        $user = User::findOrFail($value);

        if ($user->opted_out) {
            return '此用户已选择不接收邮件。';
        }
    }
);

如果 options 闭包返回的是关联数组,那么闭包会接收所选的键,否则会接收所选的值。闭包可以返回一条错误消息,若验证通过则返回 null。

多重搜索

如果可搜索的选项很多,且需要用户能够选择多个条目,multisearch 函数允许用户输入搜索查询来筛选结果,然后再使用方向键和空格键选择选项:

use function Laravel\Prompts\multisearch;

$ids = multisearch(
    '搜索应接收邮件的用户',
    fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : []
);

该闭包会接收用户到目前为止输入的文本,并且必须返回一个选项数组。如果返回的是关联数组,则将返回所选选项的键,否则返回它们的值。

当筛选一个打算返回值的数组时,应使用 array_values 函数或 values 集合方法,以确保数组不会变成关联数组:

$names = collect(['Taylor', 'Abigail']);

$selected = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => $names
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
        ->values()
        ->all(),
);

你也可以添加占位符文本和提示信息:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    placeholder: '例如:Taylor Otwell',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    hint: '该用户将立即收到一封电子邮件。'
);

在列表开始滚动之前,最多会显示五个选项。你可以通过提供 scroll 参数来自定义该数量:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    scroll: 10
);

次要信息

info 参数可用于显示当前高亮选项的附加信息。当传入一个闭包时,该闭包会接收当前高亮选项的值,并且应返回一个字符串或 null:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    info: fn (int $userId) => User::find($userId)?->email
);

要求必须填写值

默认情况下,用户可以选择零个或多个选项。你可以传入 required 参数,以强制要求选择一个或多个选项:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    required: true
);

如果你想自定义验证消息,也可以向 required 参数提供一个字符串:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    required: '你必须至少选择一位用户。'
);

额外验证

如果你想执行额外的验证逻辑,可以将一个闭包传递给 validate 参数:

$ids = multisearch(
    label: '搜索应接收邮件的用户',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    validate: function (array $values) {
        $optedOut = User::whereLike('name', '%a%')->findMany($values);

        if ($optedOut->isNotEmpty()) {
            return $optedOut->pluck('name')->join(', ', ', and ').' 已选择退出。';
        }
    }
);

如果 options 闭包返回关联数组,那么该闭包将接收选中的键;否则,它将接收选中的值。该闭包可以返回错误消息,如果验证通过则返回 null。

暂停

pause 函数可用于向用户显示提示文本,并等待用户按 Enter / Return 键确认继续:

use function Laravel\Prompts\pause;

pause('按 ENTER 键继续。');

自动补全

autocomplete 函数可用于为可能的选项提供内联自动补全。当用户输入时,与输入匹配的建议将作为提示文本显示,按 Tab 键或右箭头键即可接受:

use function Laravel\Prompts\autocomplete;

$name = autocomplete(
    label: '你的名字是什么?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim']
);

你也可以包含占位符文本、默认值和提示信息:

$name = autocomplete(
    label: '你的名字是什么?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    placeholder: '例如:Taylor',
    default: $user?->name,
    hint: '使用 Tab 键接受,上下键循环选择。'
);

动态选项

你也可以传递一个闭包,根据用户输入动态生成选项。每次用户输入一个字符时,该闭包都会被调用,并且应返回用于自动补全的选项数组:

$file = autocomplete(
    label: '选择哪个文件?',
    options: fn (string $value) => collect($files)
        ->filter(fn ($file) => str_starts_with(strtolower($file), strtolower($value)))
        ->values()
        ->all(),
);

必填值

如果要求必须输入值,可以传递 required 参数:

$name = autocomplete(
    label: '你的名字是什么?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    required: true
);

如果你想自定义验证消息,也可以传递一个字符串:

$name = autocomplete(
    label: '你的名字是什么?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    required: '必须填写你的名字。'
);

额外验证

最后,如果你想执行额外的验证逻辑,可以向 validate 参数传递一个闭包:

$name = autocomplete(
    label: '你的名字是什么?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => '名字至少需要 3 个字符。',
        strlen($value) > 255 => '名字不能超过 255 个字符。',
        default => null
    }
);

该闭包将接收已输入的值,可以返回错误消息,如果验证通过则返回 null。

在验证前转换输入

有时你可能希望在验证之前对提示输入进行转换。例如,你可能希望从任何提供的字符串中移除空白字符。为此,许多提示函数都提供了 transform 参数,该参数接受一个闭包:

$name = text(
    label: '你的名字是什么?',
    transform: fn (string $value) => trim($value),
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => '名字至少需要 3 个字符。',
        strlen($value) > 255 => '名字不能超过 255 个字符。',
        default => null
    }
);

表单

通常,你会按顺序显示多个提示,以便在执行其他操作之前收集信息。你可以使用 form 函数创建一组分组的提示,供用户填写:

use function Laravel\Prompts\form;

$responses = form()
    ->text('你的名字是什么?', required: true)
    ->password('你的密码是什么?', validate: ['password' => 'min:8'])
    ->confirm('你是否接受这些条款?')
    ->submit();

submit 方法将返回一个数字索引数组,其中包含表单中所有提示的响应。但是,你可以通过 name 参数为每个提示指定名称。当提供了名称时,可以通过该名称访问命名提示的响应:

use App\Models\User;
use function Laravel\Prompts\form;

$responses = form()
    ->text('你的名字是什么?', required: true, name: 'name')
    ->password(
        label: '你的密码是什么?',
        validate: ['password' => 'min:8'],
        name: 'password'
    )
    ->confirm('你是否接受这些条款?')
    ->submit();

User::create([
    'name' => $responses['name'],
    'password' => $responses['password'],
]);

使用 form 函数的主要优势是,用户可以使用 CTRL + U 返回表单中之前的提示。这允许用户修正错误或更改选择,而无需取消并重新启动整个表单。

如果你需要对表单中的提示进行更精细的控制,可以调用 add 方法,而不是直接调用提示函数之一。add 方法会传入用户之前提供的所有响应:

use function Laravel\Prompts\form;
use function Laravel\Prompts\outro;
use function Laravel\Prompts\text;

$responses = form()
    ->text('你的名字是什么?', required: true, name: 'name')
    ->add(function ($responses) {
        return text("{$responses['name']},你多大了?");
    }, name: 'age')
    ->submit();

outro("你的名字是 {$responses['name']},你 {$responses['age']} 岁。");

信息提示

note、info、warning、error 和 alert 函数可用于显示信息提示:

use function Laravel\Prompts\info;

info('软件包安装成功。');

提示框

callout 函数用于显示一个带标签和内容的框形消息。提示框非常适合显示需要突出的重要信息,例如部署摘要、错误详情或状态更新:

use function Laravel\Prompts\callout;

callout(
    label: '环境已配置',
    content: '你的应用程序正在生产模式下运行,启用了 4 个工作进程。',
);

你可以将 warning 或 error 作为 type 参数传入,以更改提示框的视觉样式:

callout(
    label: '弃用通知',
    content: '`--prefer-stable` 标志将在 v4.0 中移除。请改用 `--stability=stable`。',
    type: 'warning',
);

callout(
    label: '数据库连接失败',
    content: '无法连接到 127.0.0.1:3306 的 MySQL。',
    type: 'error',
);

info 参数会向提示框添加一行页脚,非常适合显示 ID 或时间戳等元数据:

callout(
    label: '部署摘要',
    content: '你的应用程序已部署到生产环境。',
    info: 'deploy-id: d4f8a2c',
);

富文本内容

除了传递字符串,你也可以传递字符串和元素组成的数组,以构建丰富的结构化提示框。Element 类提供了用于创建标题、无序列表、有序列表、键值列表和链接的工厂方法:

use Laravel\Prompts\Elements\Element;

use function Laravel\Prompts\callout;

callout('部署摘要', [
    '你的应用程序已于 2024-03-15 14:32 UTC 部署到生产环境。',
    Element::heading('变更内容'),
    Element::bulletedList([
        '迁移了 3 个待处理的数据库迁移',
        '已清除并重新构建路由缓存',
        '重启了 4 个队列工作进程',
    ]),
    Element::heading('后续步骤'),
    Element::numberedList([
        '验证 /up 处的健康检查端点',
        '监控接下来 15 分钟的错误率',
        '确认后台任务正在处理',
    ]),
]);

你也可以使用 Element::keyValueList 来显示带标签的数据:

callout('数据库连接失败', [
    '无法连接到数据库服务器。',
    Element::keyValueList([
        '主机' => '127.0.0.1',
        '端口' => '3306',
        '数据库' => 'forge',
        '状态' => '连接被拒绝',
    ]),
], type: 'error');

Element::link 方法可在支持 OSC 8 的终端中创建可点击的超链接。你可以仅提供 URL,也可以提供带有自定义标签的 URL:

callout('服务器健康检查', [
    '多个服务报告性能下降。',
    Element::heading('受影响的服务'),
    '请查看:'.Element::link('https://example.com/health', '健康仪表盘'),
    Element::link('https://example.com/health'),
]);

如果未提供标签,URL 本身将作为链接文本显示。

表格

table 函数可以轻松显示多行多列的数据。你只需提供表格的列名和数据:

use function Laravel\Prompts\table;

table(
    headers: ['名称', '电子邮件'],
    rows: User::all(['name', 'email'])->toArray()
);

Spin

spin 函数在执行指定回调时会显示加载动画以及可选消息。它用于指示正在进行的进程,并在完成后返回回调的结果:

use function Laravel\Prompts\spin;

$response = spin(
    callback: fn () => Http::get('http://example.com'),
    message: '正在获取响应...'
);

spin 函数需要 PCNTL PHP 扩展来实现加载动画。当该扩展不可用时,将显示静态版本的加载动画。

进度条

对于长时间运行的任务,显示进度条来告知用户任务的完成情况会很有帮助。使用 progress 函数,Laravel 会显示一个进度条,并在对给定可迭代值的每次迭代中推进进度:

use function Laravel\Prompts\progress;

$users = progress(
    label: '更新用户',
    steps: User::all(),
    callback: fn ($user) => $this->performTask($user)
);

progress 函数的作用类似于 map 函数,它会返回一个包含回调每次迭代返回值的数组。

回调也可以接受 Laravel\Prompts\Progress 实例,以便你在每次迭代中修改标签和提示信息:

$users = progress(
    label: '更新用户',
    steps: User::all(),
    callback: function ($user, $progress) {
        $progress
            ->label("正在更新 {$user->name}")
            ->hint("创建于 {$user->created_at}");

        return $this->performTask($user);
    },
    hint: '这可能需要一些时间。'
);

有时,你可能需要更手动地控制进度条的推进方式。首先,定义该进程将迭代的总步骤数。然后,在处理每个项目后,通过 advance 方法推进进度条:

$progress = progress(label: '更新用户', steps: 10);

$users = User::all();

$progress->start();

foreach ($users as $user) {
    $this->performTask($user);

    $progress->advance();
}

$progress->finish();

任务

task 函数在执行给定回调时,会显示一个带有标签的任务,包括加载动画和滚动的实时输出区域。它非常适合封装长时间运行的进程,如依赖安装或部署脚本,可实时展示正在进行的操作:

use function Laravel\Prompts\task;

task(
    label: '安装依赖',
    callback: function ($logger) {
        // 长时间运行的进程...
    }
);

回调会接收一个 Logger 实例,你可以使用它在任务的输出区域中显示日志行、状态消息和流式文本。

task 函数需要 PCNTL PHP 扩展来实现加载动画。当该扩展不可用时,将显示静态版本的任务。

记录日志行

line 方法会将单行日志写入任务的滚动输出区域:

task(
    label: '安装依赖',
    callback: function ($logger) {
        $logger->line('正在解析包...');
        // ...
        $logger->line('正在下载 laravel/framework');
        // ...
    }
);

状态消息

你可以使用 success、warning 和 error 方法来显示状态消息。这些消息会作为稳定、突出显示的内容显示在滚动日志区域上方:

task(
    label: '部署应用',
    callback: function ($logger) {
        $logger->line('正在拉取最新变更...');
        // ...
        $logger->success('已拉取变更!');

        $logger->line('正在执行迁移...');
        // ...
        $logger->warning('没有需要运行的新迁移。');

        $logger->line('正在清除缓存...');
        // ...
        $logger->success('已清除缓存!');
    }
);

更新标签

label 方法允许你在任务运行时更新其标签:

task(
    label: '正在启动部署...',
    callback: function ($logger) {
        $logger->label('正在拉取最新变更...');
        // ...
        $logger->label('正在执行迁移...');
        // ...
        $logger->label('正在清除缓存...');
        // ...
    }
);

显示子标签

subLabel 方法会在任务主标签下方显示一条暗淡的文本行,适用于传达诸如当前进行的步骤等临时状态。传入空字符串可清除子标签:

task(
    label: '部署',
    callback: function ($logger) {
        $logger->subLabel('正在构建资源...');
        // ...
        $logger->subLabel('正在执行迁移...');
        // ...
        $logger->subLabel('');
    }
);

你也可以通过 subLabel 参数提供初始子标签:

task(
    label: '部署',
    callback: function ($logger) {
        // ...
    },
    subLabel: '准备中...'
);

流式文本

对于会逐步产生输出的进程(如 AI 生成的响应),partial 方法允许你逐字或逐块地流式传输文本。流传输完成后,调用 commitPartial 来最终确定输出:

task(
    label: '正在生成响应...',
    callback: function ($logger) {
        foreach ($words as $word) {
            $logger->partial($word . ' ');
        }

        $logger->commitPartial();
    }
);

自定义输出限制

默认情况下,任务最多显示 10 行滚动输出。你可以通过 limit 参数对此进行自定义:

task(
    label: '安装依赖',
    callback: function ($logger) {
        // ...
    },
    limit: 20
);

保留摘要

默认情况下,任务完成后其输出会被清除。如果你想在任务结束后在屏幕上保留状态消息,可以传入 keepSummary 参数:

task(
    label: '部署',
    callback: function ($logger) {
        $logger->success('资源构建完成');
        // ...
        $logger->success('迁移完成');
    },
    keepSummary: true,
);

流

stream 函数会显示流入终端的文本,非常适合显示 AI 生成的内容或任何逐步到达的文本:

use function Laravel\Prompts\stream;

$stream = stream();

foreach ($words as $word) {
    $stream->append($word . ' ');
    usleep(25_000); // 模拟块之间的延迟...
}

$stream->close();

append 方法会将文本添加到流中,并以逐渐淡入的效果进行渲染。当所有内容都完成流式传输后,调用 close 方法来最终确定输出并恢复光标。

终端标题

title 函数会更新用户终端窗口或标签页的标题:

use function Laravel\Prompts\title;

title('正在安装依赖');

若要将终端标题重置为默认值,传入空字符串即可:

title('');

清除终端

clear 函数可用于清除用户的终端:

use function Laravel\Prompts\clear;

clear();

终端注意事项

终端宽度

如果任何标签、选项或验证消息的长度超过了用户终端的“列”数,它将被自动截断以适应显示。如果你的用户可能使用较窄的终端,请考虑尽量缩短这些字符串的长度。一个通常安全的最长长度为 74 个字符,可支持 80 个字符宽度的终端。

终端高度

对于任何接受 scroll 参数的提示,所配置的值会自动减少,以适应用户终端的高度,同时会为验证消息留出空间。

不支持的环境与回退方案

Laravel Prompts 支持 macOS、Linux 以及安装了 WSL 的 Windows。由于 Windows 版 PHP 存在限制,目前在 WSL 之外无法在 Windows 上使用 Laravel Prompts。

因此,Laravel Prompts 支持回退到其他替代实现,例如 Symfony Console Question Helper。

当在 Laravel 框架中使用 Laravel Prompts 时,已为您配置好每种提示的回退方案,并会在不支持的环境中自动启用。

回退条件

如果您未使用 Laravel,或者需要自定义何时使用回退行为,可以将一个布尔值传递给 Prompt 类的 fallbackWhen 静态方法:

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);
});

回退方案必须针对每个提示类单独配置。该闭包将接收提示类的实例,并且必须返回与提示相匹配的适当类型。

测试

Laravel 提供了多种方法,用于测试您的命令是否显示了预期的 Prompt 消息:

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!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', '[email protected]'],
                ['Jason Beggs', '[email protected]'],
            ]
        )
        ->assertExitCode(0);
});
public function test_report_generation(): void
{
    $this->artisan('report:generate')
        ->expectsPromptsInfo('Welcome to the application!')
        ->expectsPromptsWarning('This action cannot be undone')
        ->expectsPromptsError('Something went wrong')
        ->expectsPromptsAlert('Important notice!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', '[email protected]'],
                ['Jason Beggs', '[email protected]'],
            ]
        )
        ->assertExitCode(0);
}