什么是 Artisan
Artisan 是 Laravel 内置的命令行接口(CLI)工具。
它以项目根目录下的 artisan 脚本形式存在,提供了大量提升开发和运维效率的命令。
要查看可用命令列表,请使用 list。
若想查看命令用法,前面加 help 即可。
使用 Laravel Sail 时,请以 sail artisan 取代 php artisan 执行。
命令会在 Docker 容器内运行。./vendor/bin/sail artisan list
Tinker
Laravel Tinker 是一个 REPL 环境,能在命令行中交互式操作整个应用,包括 Eloquent 模型、Job、事件等。
启动后可以直接执行 PHP 代码。
// 查看用户
>>> App\Models\User::find(1)
// 通过工厂创建记录
>>> App\Models\User::factory()->create()
// 直接调用服务类
>>> app(App\Services\OrderService::class)->process(1)
Tinker 会实际修改数据。在生产环境执行时请务必谨慎。
它适合用于本地或 staging 环境的功能确认和调试。
创建自定义命令
生成命令
使用 make:command 生成命令类的骨架。
php artisan make:command ImportProducts
会生成 app/Console/Commands/ImportProducts.php。
命令的基本结构
生成的命令类包含 $signature、$description、handle() 三个主要要素。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class ImportProducts extends Command
{
/**
* 命令名与参数/选项定义
*/
protected $signature = 'import:products';
/**
* 命令说明(会显示在 php artisan list 中)
*/
protected $description = '从 CSV 导入商品数据';
/**
* 命令的执行体
*/
public function handle(): void
{
$this->info('开始导入...');
// 编写处理逻辑
$this->info('完成。');
}
}
Laravel 13 也支持使用 PHP 属性来书写。use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature('import:products')]
#[Description('从 CSV 导入商品数据')]
class ImportProducts extends Command
{
public function handle(): void
{
// ...
}
}
定义参数与选项
$signature 中可以一并定义命令名、参数与选项。
protected $signature = 'import:products
{file : 要导入的 CSV 文件路径}
{--limit= : 要导入的最大条数}
{--dry-run : 不真正写入数据库(仅确认)}';
| 语法 | 类别 | 说明 |
|---|
{file} | 必需参数 | 缺省会报错 |
{file?} | 可选参数 | 缺省时为 null |
{file=products.csv} | 带默认值的参数 | 缺省时使用默认值 |
{--queue} | 布尔选项 | 指定为 true,未指定为 false |
{--limit=} | 带值的选项 | 通过 --limit=100 传值 |
{--limit=50} | 带默认值的选项 | 缺省时为 50 |
{--Q|queue} | 带简写的选项 | 也可用 -Q |
读取参数与选项
在 handle() 中通过 argument() 和 option() 获取值。
public function handle(): void
{
$file = $this->argument('file'); // 获取参数
$limit = $this->option('limit'); // 获取选项
$dryRun = $this->option('dry-run'); // 布尔选项
$this->info("文件:{$file}");
if ($dryRun) {
$this->warn('干运行模式:跳过写入数据库');
}
}
使用 input() 方法可以像 HTTP 请求那样通过类型化访问器读取命令参数或选项。
use App\Enums\ReportType;
public function handle(): void
{
// 以 CommandInput 实例获取参数/选项
$from = $this->input()->date('from');
$type = $this->input()->enum('type', ReportType::class);
$limit = $this->input()->integer('limit');
}
要按名称直接取值可传入名字(会同时在参数与选项中查找)。
$queue = $this->input('queue', 'default');
argument() / option() 返回字符串,而 input() 的类型化访问器会转换为合适的类型。适合需要类型安全的日期、数字、Enum 等场景。
与用户交互
输出消息
有专门的方法用于向控制台输出带颜色的文本。
$this->info('处理正常完成'); // 绿色
$this->warn('该操作不可撤销'); // 黄色
$this->error('发生错误'); // 红色
$this->line('普通文本'); // 无颜色
$this->comment('补充信息'); // 浅灰色
询问用户
可以从用户交互式接收输入。
// 文本输入
$name = $this->ask('请输入负责人姓名');
// 带默认值
$env = $this->ask('选择运行环境', 'production');
// 密码等敏感信息(不会在屏幕上显示)
$password = $this->secret('请输入 API 密钥');
// Yes / No 确认
if (! $this->confirm('确认导入到生产数据库?')) {
$this->info('已取消。');
return;
}
// 从选项中选择
$format = $this->choice('请选择输出格式', ['csv', 'json', 'xml'], 0);
进度条
在处理大量数据时可以直观展示进度。
use App\Models\Product;
// 传入集合即自动显示进度条
$this->withProgressBar(Product::cursor(), function (Product $product) {
$this->processProduct($product);
});
若想手动细致控制,可使用如下方式:
$total = Product::count();
$bar = $this->output->createProgressBar($total);
$bar->start();
Product::cursor()->each(function (Product $product) use ($bar) {
$this->processProduct($product);
$bar->advance();
});
$bar->finish();
$this->newLine();
实用场景
数据导入命令
以下是一个从 CSV 导入商品数据的实用命令示例。
生成命令
php artisan make:command ImportProducts
实现命令
<?php
namespace App\Console\Commands;
use App\Models\Product;
use Illuminate\Console\Command;
class ImportProducts extends Command
{
protected $signature = 'import:products
{file : CSV 文件路径}
{--limit= : 最大导入条数}
{--dry-run : 仅确认(不写入数据库)}';
protected $description = '从 CSV 文件导入商品数据';
public function handle(): void
{
$filePath = $this->argument('file');
$limit = $this->option('limit') ? (int) $this->option('limit') : null;
$dryRun = $this->option('dry-run');
if (! file_exists($filePath)) {
$this->error("找不到文件:{$filePath}");
return;
}
// 使用 PHP 内置函数读取 CSV
$handle = fopen($filePath, 'r');
$headers = fgetcsv($handle); // 第一行为表头
$rows = [];
while (($row = fgetcsv($handle)) !== false) {
$rows[] = array_combine($headers, $row);
if ($limit !== null && count($rows) >= $limit) {
break;
}
}
fclose($handle);
$count = 0;
$this->withProgressBar($rows, function (array $row) use ($dryRun, &$count) {
if (! $dryRun) {
Product::updateOrCreate(
['sku' => $row['sku']],
[
'name' => $row['name'],
'price' => $row['price'],
]
);
}
$count++;
});
$this->newLine();
if ($dryRun) {
$this->warn("共 {$count} 条(干运行:已跳过保存)");
} else {
$this->info("已导入 {$count} 条。");
}
}
}
运行命令
# 常规导入
php artisan import:products storage/products.csv
# 限制条数
php artisan import:products storage/products.csv --limit=100
# 干运行确认
php artisan import:products storage/products.csv --dry-run
定期维护命令
以下是一个定期删除过期数据的维护命令示例。
<?php
namespace App\Console\Commands;
use App\Models\Order;
use Illuminate\Console\Command;
class PruneOldOrders extends Command
{
protected $signature = 'orders:prune {--days=90 : 删除多少天前的数据}';
protected $description = '删除超过指定天数的已完成订单';
public function handle(): void
{
$days = (int) $this->option('days');
if (! $this->confirm("确认删除 {$days} 天以上的已完成订单吗?")) {
$this->info('已取消。');
return;
}
$deleted = Order::where('status', 'completed')
->where('created_at', '<', now()->subDays($days))
->delete();
$this->info("已删除 {$deleted} 条订单。");
}
}
结合调度执行
创建的命令可以在 routes/console.php 中安排调度执行。
详见任务调度。
use Illuminate\Support\Facades\Schedule;
// 每天凌晨 2 点执行
Schedule::command('orders:prune --days=90')->dailyAt('2:00');
// 每周一 9 点执行
Schedule::command('import:products storage/weekly.csv')->weeklyOn(1, '9:00');
命令测试
可以使用 Laravel 的测试功能来验证 Artisan 命令。
<?php
namespace Tests\Feature\Commands;
use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PruneOldOrdersTest extends TestCase
{
use RefreshDatabase;
public function test_it_deletes_old_completed_orders(): void
{
// 创建 90 天前的已完成订单
Order::factory()->create([
'status' => 'completed',
'created_at' => now()->subDays(100),
]);
// 近期已完成订单(不应被删除)
Order::factory()->create([
'status' => 'completed',
'created_at' => now()->subDays(10),
]);
$this->artisan('orders:prune', ['--days' => 90])
->expectsConfirmation('确认删除 90 天以上的已完成订单吗?', 'yes')
->expectsOutput('已删除 1 条订单。')
->assertExitCode(0);
$this->assertDatabaseCount('orders', 1);
}
public function test_it_cancels_when_user_declines(): void
{
Order::factory()->create(['status' => 'completed', 'created_at' => now()->subDays(100)]);
$this->artisan('orders:prune', ['--days' => 90])
->expectsConfirmation('确认删除 90 天以上的已完成订单吗?', 'no')
->expectsOutput('已取消。')
->assertExitCode(0);
$this->assertDatabaseCount('orders', 1);
}
}
artisan() 可用的断言方法示例:| 方法 | 说明 |
|---|
expectsOutput('...') | 校验指定文本是否被输出 |
expectsQuestion('?', '答案') | 模拟 ask() 输入 |
expectsConfirmation('?', 'yes') | 模拟 confirm() 应答 |
expectsChoice('?', '选项') | 模拟 choice() 选择 |
assertExitCode(0) | 校验退出码(0 = 成功) |
assertFailed() | 校验退出码不为 0 |
常用命令汇总
# 新建命令
php artisan make:command CommandName
# 可用命令列表
php artisan list
# 查看命令用法
php artisan help command:name
# 启动 Tinker
php artisan tinker