클래스의 의존성이 없거나 콘크리트 클래스에 대한 의존성만 가지는 경우 별도의 설정할 필요 없이 서비스 컨테이너를 통해 의존성을 주입할 수 있어 파일의 크기를 작게 유지할 수 있다
<?php
class Service
{
// ...
}
Route::get('/', function (Service $service) {
die(get_class($service));
});
Laravel을 구성하는 대부분의 클래스 (controller, event listener, middleware …)들은 서비스 컨테이너에 의해 자동으로 의존성이 주입된다
아래처럼 대부분 타입힌팅 의존성을 사용해 주입하기에 우리는 서비스 컨테이너와 직접적인 상호작용이 없이도 클래스들을 사용할 수 있고 이러한 의존성 주입은 대부분 보이지 않게 일어나기 때문에 편리하게 사용이 가능하다
use Illuminate\Http\Request;
Route::get('/', function (Request $request) {
// ...
});
추가적으로 우리가 직접 바인딩을 통해 서비스 컨테이너에 의존성 주입 설정을 할 수가 있다
$this→app 속성을 이용해 컨테이너에 접근 후 bind 메서드로 바인딩한다
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;
$this->app->bind(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
// PodcastParser를 파라미터로 받는 Transistor를 서비스 컨테이너에 등록
// PodcastParser는 서비스 컨테이너에 등록되어있을 수도 아닐 수도 있다
// 서비스 컨테이너에 등록되어 있지 않으면 해당 클래스를 찾아 매핑하기 때문
서비스 프로바이더 이외에서 서비스 컨테이너에 접근하고 싶다면 App facade를 사용한다
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;
App::bind(Transistor::class, function (Application $app) {
// ...
});
bindIf 메서드를 통해 바인딩이 등록되지 않았을 경우에만 바인드 하도록 설정도 가능하다
$this->app->bindIf(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
singleton 바인딩은 최초 한 번 바인딩 되면 이후 해당 객체에 대해서 호출할 때 항상 같은 인스턴스가 호출되는 것을 보장한다
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;
$this->app->singleton(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
bindIf 와 마찬가지로 singletonIf 를 사용해 등록되지 않았다면 바인딩하도록 할 수 있다
$this->app->singletonIf(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
Laravel의 lifecycle 한 주기마다 새롭게 바인딩되는 scoped 메서드. 한 주기를 시작할 때마다 플러시 된다
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;
$this->app->scoped(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
클래스가 아닌 인스턴스를 바인딩할 수도 있다. 이후에는 resolve 때마다 바인딩한 인스턴스가 호출된다
use App\Services\Transistor;
use App\Services\PodcastParser;
$service = new Transistor(new PodcastParser);
$this->app->instance(Transistor::class, $service);
인터페이스를 구현체에 바인딩할 수 있다. 이렇게 하면 클래스에서 해당 인터페이스를 구현할 때 구현체가 필수 요소가 되도록 할 수 있다.
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;
$this->app->bind(EventPusher::class, RedisEventPusher::class);
같은 인터페이스의 다른 구현체를 사용해야하는 여러 클래스가 있을 경우 아래와 같이 선언 가능하다
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
정수와 같은 기본값을 주입해야 하는 경우 아래처럼 사용 가능하다
use App\Http\Controllers\UserController;
$this->app->when(UserController::class)
->needs('$variableName')
->give($value);
태그를 포함한 모든 컨테이너에 대한 바인딩 가능
$this->app->when(ReportAggregator::class)
->needs('$timezone')
->giveConfig('app.timezone');
config 주입도 가능하다
$this->app->when(ReportAggregator::class)
->needs('$timezone')
->giveConfig('app.timezone');
가변 생성자 인수를 이용해 객체를 파라미터로 받는 경우 아래처럼 사용 가능
<?php
use App\Models\Filter;
use App\Services\Logger;
class Firewall
{
/**
* The filter instances.
*
* @var array
*/
protected $filters;
/**
* Create a new class instance.
*/
public function __construct(
protected Logger $logger,
Filter ...$filters,
) {
$this->filters = $filters;
}
}
contextual 바인딩을 통해 resolve된 클래스 인스턴스의 배열을 리턴하는 closure를 give 메서드를 통해 사용할 수 있다
$this->app->when(Firewall::class)
->needs(Filter::class)
->give(function (Application $app) {
return [
$app->make(NullFilter::class),
$app->make(ProfanityFilter::class),
$app->make(TooLongFilter::class),
];
});
조금 더 편리한 사용을 위해 클래스 이름만 제공해도 된다
$this->app->when(Firewall::class)
->needs(Filter::class)
->give([
NullFilter::class,
ProfanityFilter::class,
TooLongFilter::class,
]);
$this->app->when(ReportAggregator::class)
->needs(Report::class)
->giveTagged('reports');
바인딩한 클래스들을 특정 키테고리로 묶고 싶다면 tag 메서드를 이용한다
$this->app->bind(CpuReport::class, function () {
// ...
});
$this->app->bind(MemoryReport::class, function () {
// ...
});
$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
위와 같이 tag를 통해 묶으면 tag name을 통해 쉽게 resolve가 가능하다
$this->app->bind(ReportAnalyzer::class, function (Application $app) {
return new ReportAnalyzer($app->tagged('reports'));
});
extend 메서드 사용 시 resolve된 서비스에 대한 수정이 가능
$this->app->extend(Service::class, function (Service $service, Application $app) {
return new DecoratedService($service);
});
make Method서비스 컨테이너에서 클래스 인스턴스를 resolve하기 위한 메서드
use App\Services\Transistor;
$transistor = $this->app->make(Transistor::class);
클래스의 종속성 일부를 컨테이너가 확인할 수 없는 경우 makeWith 메서드를 통해 전달할 수 있다
use App\Services\Transistor;
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
bound 메서드로 바인딩 여부 확인 가능
if ($this->app->bound(Transistor::class)) {
// ...
}
$app 변수에 접근할 수 없는 환경에 있는 경우 App 클래스를 use해 사용하면 된다
use App\Services\Transistor;
use Illuminate\Support\Facades\App;
$transistor = App::make(Transistor::class);
$transistor = app(Transistor::class);
Laravel Container 자체를 주입하는 것도 가능하다
use Illuminate\Container\Container;
/**
* Create a new class instance.
*/
public function __construct(
protected Container $container
) {}
서비스 컨테이너에게 자동 주입을 위임할 수 있다. constructor에 타입 힌트 형식으로 의존성을 주입하면 컨테이너가 자동으로 주입한다
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
use App\Models\User;
class UserController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected UserRepository $users,
) {}
/**
* Show the user with the given ID.
*/
public function show(string $id): User
{
$user = $this->users->findOrFail($id);
return $user;
}
}
호출과 동시에 의존성 자동 주입을 하고 싶은 경우
<?php
namespace App;
use App\Repositories\UserRepository;
class UserReport
{
/**
* Generate a new user report.
*/
public function generate(UserRepository $repository): array
{
return [
// ...
];
}
}
컨테이너를 통해 generate 메서드 호출하고 싶은 경우
use App\UserReport;
use Illuminate\Support\Facades\App;
$report = App::call([new UserReport, 'generate']);
call 메서드는 모든 PHP 호출 기능을 허용
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\App;
$result = App::call(function (UserRepository $repository) {
// ...
});
서비스 컨테이너는 객체 resolve마다 이벤트를 발생시킨다. 이를 resolving 메서드로 받을 수 있다.
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
// Called when container resolves objects of type "Transistor"...
});
$this->app->resolving(function (mixed $object, Application $app) {
// Called when container resolves object of any type...
});
Laravel의 서비스 컨테이너는 PSR-11 인터페이스를 상속받는다. 따라서 Laravel의 컨테이너 인스턴스를 사용하기 위해 PSR-11 컨테이너를 타입힌트 할 수 있다
use App\Services\Transistor;
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get(Transistor::class);
// ...
});