Skip to main content
패키지에 진단·동기화·설치용 Artisan 명령어를 추가하려면 명령어 등록, 인스턴스 생성, 처리 실행을 나누어 생각해야 합니다. 실행 시에만 필요한 서비스를 생성자에서 해결하면 관계없는 Artisan 명령어의 실행까지 영향을 받을 수 있습니다. 이 페이지에서는 Laravel Framework v13.35.0의 구현을 확인하고, Laravel 패키지 개발에 있는 commands() 등록 예제를 깊이 살펴봅니다. 파일 생성 구현은 GeneratorCommand를, 최적화 작업에 참여하는 방법은 optimize 통합을 참조하세요.

프로바이더에서 명시적으로 등록하기

패키지 내의 명령어는 서비스 프로바이더의 boot()에서 클래스 이름을 등록합니다. 애플리케이션의 app/Console/Commands에 대한 자동 감지와 패키지가 제공하는 클래스의 등록은 서로 다른 진입점입니다.
이 예제는 프로바이더가 패키지 자동 감지 또는 애플리케이션 측의 명시적 등록으로 로드된다는 것을 전제로 합니다. 명령어 클래스를 두는 것만으로 패키지의 프로바이더까지 자동으로 등록되는 것은 아닙니다. runningInConsole()은 콘솔 환경인지를 판정할 뿐, 특정 명령어를 실행 중인지를 판정하는 것이 아닙니다. HTTP 요청 내에서 Artisan::call()하는 용도도 제공한다면 이 조건 때문에 등록되지 않을 가능성을 고려하고, 무조건 등록을 포함하여 사용 계약을 정합니다. 프로그램에서 실행하는 방법은 Artisan을 참조하세요.

commands()는 그 자리에서 명령어를 실행하지 않는다

ServiceProvider::commands()는 전달받은 클래스 이름을 사용하는 클로저를 Illuminate\Console\Application::starting()에 등록합니다. 이 시점에서는 명령어의 handle()을 호출하지 않습니다. 일반적인 Console Kernel에서는 나중에 Artisan Application이 생성되면 등록된 시작 콜백이 실행됩니다. 그때 resolveCommands()가 각 클래스를 resolve()에 전달하여 명령어 등록을 진행합니다. 여기서 중요한 점은 명령어의 인스턴스 생성이 그 명령어의 실행과 같은 시점이라고는 할 수 없다는 것입니다. 뒤에서 설명할 전용 지연 등록 경로를 사용하지 않는 일반 클래스에서는 resolve()가 컨테이너의 make()를 호출하므로, 등록 시점에 생성자와 그 의존성이 해결됩니다. 따라서 php artisan list나 다른 명령어를 실행하기만 해도 패키지 명령어의 생성자가 동작할 수 있습니다. commands()를 boot()에 두는 것 자체가 실행 시점까지 인스턴스 생성을 늦춘다는 보장은 되지 않습니다.

실행 시의 의존성은 handle()에 주입하기

예로 패키지 설정의 활성·비활성 여부만 표시하는 명령어를 만듭니다. Laravel 13의 공식 Artisan 문서와 마찬가지로 이름과 설명을 Signature·Description 어트리뷰트로 정의합니다.
이 예제에서는 생성자를 추가하지 않고, 설정 Repository를 handle()의 인수로 받습니다. 명령어 실행 경로에서 Command::execute()가 컨테이너의 call()을 사용하므로, 타입을 지정한 의존성이 주입됩니다. 외부 API 클라이언트나 동기화 서비스를 사용하는 경우에도, 실행 시에만 필요하다면 같은 위치에 주입할 수 있습니다. 다만 서비스 자체의 생성이나 다른 프로바이더의 boot()에 부작용이 있다면, 그것까지 자동으로 막아 주지는 않습니다.
생성자에서는 외부 API 연결, 데이터베이스 갱신, 공개 파일 덮어쓰기 등을 하지 마세요. 패키지 사용자는 설치 직후나 연결 설정이 완료되지 않은 상태에서도 list·help를 사용할 수 있어야 합니다.
생성자를 직접 정의하는 경우에는 parent::__construct()도 호출합니다. Laravel의 부모 생성자가 어트리뷰트, signature, description 등으로부터 명령어를 구성하기 때문입니다.

Signature와 지연 등록은 별개의 메커니즘

확인한 Application::resolve()에는 Symfony의 AsCommand 어트리뷰트의 name을 읽어 이름과 클래스의 대응을 command map에 저장하는 분기가 있습니다. 이 분기에서는 그 자리에서 컨테이너의 make()를 호출하지 않습니다. Console Kernel은 나중에 setContainerCommandLoader()를 호출하여 이 map을 사용하는 로더를 설정합니다. 한편, 위 예제에서 사용한 Laravel의 Signature 어트리뷰트는 이 AsCommand 판정과는 별개입니다. Signature를 붙이는 것만으로 그 클래스가 이 지연 등록 경로에 들어가는 것은 아닙니다. 이 차이는 내부 구현을 읽을 때의 주의점으로 다루고, 우선은 공식 명령어 정의와 handle()에 대한 의존성 주입으로 생성 비용과 부작용을 줄입니다. 지연 로더가 있는 경우에도 목록·도움말 등에서 명령어 정보가 필요해질 수 있으므로, “선택한 명령어의 실행 시 외에는 절대 생성되지 않는다”는 전제로 설계하지 않습니다.

명령어 등록까지 지연시키지 않기

서비스 바인딩을 지연 로드하는 것과 Artisan에 명령어를 알리는 것은 별개입니다. 프로바이더가 DeferrableProvider를 구현하고 있으면, 대상 서비스가 필요해질 때까지 boot() 자체가 실행되지 않을 수 있습니다. 명령어 등록만 그 boot()에 두고 Artisan 실행 시점까지 프로바이더가 로드되지 않으면, 명령어의 존재를 알리는 시작 콜백도 등록되지 않습니다. handle()로 의존성 주입을 옮긴 서비스가 지연 프로바이더를 로드한다고 해도, Artisan이 명령어를 찾기 전의 문제는 해결되지 않습니다. 명령어 공개를 확실히 하는 구성으로서, 즉시 로드되는 프로바이더에 등록을 두고 무거운 서비스의 바인딩만 별도의 지연 프로바이더로 분리할 수 있습니다. 구체적인 요건은 지연 서비스 프로바이더를 참조하세요.

업데이트 시 확인할 사항

명령어 이름, 인수, 옵션, 출력, 종료 코드는 사용자의 셸 스크립트나 CI에서 사용되는 공개 인터페이스입니다. 클래스 내부 변경과 같은 감각으로 바꾸지 말고, 호환성과 마이그레이션 절차를 확인합니다. 위의 StatusCommand라면 프로바이더를 로드한 테스트 환경에서 다음과 같이 출력과 종료 코드를 검증할 수 있습니다. 테스트 기반은 Orchestra Testbench로 Laravel 패키지를 테스트하기를, assertion의 자세한 내용은 콘솔 테스트를 참조하세요.
이와 더불어 무거운 서비스를 사용하는 명령어에서는 목록이나 도움말 실행 시 서비스의 비즈니스 메서드가 호출되지 않는다는 것도 검증합니다. 등록할 수 있다는 것만 확인해서는 관계없는 명령어에 미치는 영향까지 감지할 수 없습니다.

참조한 1차 정보

마지막 수정일 2026년 10월 12일