Artisan이란
Artisan은 Laravel에 포함되어 있는 커맨드라인 인터페이스(CLI) 도구입니다.
프로젝트 루트의 artisan 스크립트로 존재하며, 개발과 운영을 효율화하는 다수의 명령어를 제공합니다.
사용 가능한 명령어 목록을 확인하려면 list 명령어를 사용합니다.
명령어의 사용법을 확인하려면 help를 앞에 붙입니다.
Laravel Sail을 사용하는 경우, php artisan 대신에 sail artisan을 사용하십시오.
명령어는 Docker 컨테이너 내에서 실행됩니다../vendor/bin/sail artisan list
Tinker
Laravel Tinker는 Eloquent 모델이나 Job, 이벤트 등 애플리케이션 전체를 커맨드라인 상에서 인터랙티브하게 조작할 수 있는 REPL 환경입니다.
기동 후에는 PHP 코드를 그대로 실행할 수 있습니다.
// 사용자를 조회해 확인
>>> App\Models\User::find(1)
// 팩토리로 레코드를 생성
>>> App\Models\User::factory()->create()
// 서비스 클래스를 직접 호출
>>> app(App\Services\OrderService::class)->process(1)
Tinker는 데이터를 실제로 변경합니다. 운영 환경에서의 실행은 충분히 주의하십시오.
로컬이나 스테이징 환경에서의 동작 확인, 디버깅 용도에 적합합니다.
커스텀 명령어 작성
명령어 생성
make:command로 명령어 클래스의 골격을 생성합니다.
php artisan make:command ImportProducts
app/Console/Commands/ImportProducts.php가 생성됩니다.
명령어의 기본 구조
생성된 명령어 클래스에는 $signature, $description, handle()이라는 3가지 주요 요소가 있습니다.
<?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 : 실제로는 DB에 저장하지 않음(확인용)}';
| 표기법 | 종류 | 설명 |
|---|
{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'); // 플래그 옵션(bool)
$this->info("파일: {$file}");
if ($dryRun) {
$this->warn('드라이 런 모드: DB에의 저장은 스킵합니다');
}
}
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('운영 DB에 임포트하시겠습니까?')) {
$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 : 확인만(DB에 저장하지 않음)}';
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); // 1행째를 헤더로 취득
$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