Laravelの $app->make()
は何をしているのか調べてみたメモ。
バージョンは v9.8.1
です。
$app は Illuminate\Foundation\Application
のインスタンス。
Illuminate\Foundation\Application のコンストラクタの処理
コンストラクタではbasePathなどを設定しつつデフォルトのbindings, providers, aliasの登録をしている。
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Foundation/Application.php#L172-L181
registerBaseBindings()
はこんな感じな処理になっている。 static::setInstance()
はシングルトンとしてApplicationを登録。
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->singleton(PackageManifest::class, function () {
return new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
);
});
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Foundation/Application.php#L198-L212
instance()
はこんな感じで $this->instances にセットしている
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract);
$isBound = $this->bound($abstract);
unset($this->aliases[$abstract]);
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$this->instances[$abstract] = $instance;
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L468-L486
singleton()
は bind()
を呼び出す。第3引数にtrueをセット。
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L386-L389
bind()
はこんな感じで$concreteがstringの場合 getClosure()
をbindingsに登録する
public function bind($abstract, $concrete = null, $shared = false)
{
// snip...
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
if (! is_string($concrete)) {
throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
}
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// snip...
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L249-L279
getClosure()
はこんな感じで $container->resolve()
や $container->build()
を返すクロージャーを生成する
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L288-L299
registerBaseServiceProviders()
はこんな感じでServiceProviderを登録する
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Foundation/Application.php#L219-L224
register()
は $provider->register()
を呼び出したり、 $bindings
や $singletons
を bind()
したり singleton()
で設定する。
public function register($provider, $force = false)
{
// 省略...
$provider->register();
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Foundation/Application.php#L673-L713
例えば RoutingServiceProvider の register()
はこんな感じでRouterなどのクラスをappに設定している
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
$this->registerControllerDispatcher();
}
/**
* Register the router instance.
*
* @return void
*/
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}
Illuminate\Foundation\Application@make()
make()
すると resolve()
が呼ばれる。
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
// 省略...
$concrete = $this->getContextualConcrete($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// 省略...
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
// 省略...
// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L726-L789
$abstract = $this->getAlias($abstract);
でabstractからaliasを取得
既にインスタンス化されていたらそれを返す。
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
例えば app
は↑の処理で instance()
を呼び出して値をセットしているので、
make('app')
すると $this->instances に入っている Illuminate\Foundation\Application
のインスタンスが返る。
$concrete = $this->getConcrete($abstract);
でbindingsからconcreteのclosureを取得。 bindingsがなければ $abstract
の文字列を返す。
$object = $this->build($concrete);
でclosureを呼び出してクラス化するか、ReflectionClass
を使って $abstract
のインスタンスを生成する。
build()
はClosureの場合はClousureを呼び出し、そうでなければ文字列とみなしてReflectionClassで動的にインスタンスを生成する。
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface or Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L867-L918
$reflector->newInstanceArgs($instances)
で動的にクラスを生成している。
引数は resolveDependencies()
で生成している。
resolveDependencies()
は resolveClass()
や resolvePrimitive()
で引数を生成する。
protected function resolveDependencies(array $dependencies)
{
$results = [];
foreach ($dependencies as $dependency) {
// If the dependency has an override for this particular build we will use
// that instead as the value. Otherwise, we will continue with this run
// of resolutions and let reflection attempt to determine the result.
if ($this->hasParameterOverride($dependency)) {
$results[] = $this->getParameterOverride($dependency);
continue;
}
// If the class is null, it means the dependency is a string or some other
// primitive type which we can not resolve since it is not a class and
// we will just bomb out with an error since we have no-where to go.
$result = is_null(Util::getParameterClassName($dependency))
? $this->resolvePrimitive($dependency)
: $this->resolveClass($dependency);
if ($dependency->isVariadic()) {
$results = array_merge($results, $result);
} else {
$results[] = $result;
}
}
return $results;
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L928-L957
例えば resolveClass()
は make()
をコールして再帰的にインスタンスを生成している。
protected function resolveClass(ReflectionParameter $parameter)
{
try {
return $parameter->isVariadic()
? $this->resolveVariadicClass($parameter)
: $this->make(Util::getParameterClassName($parameter));
}
// If we can not resolve the class instance, we will check to see if the value
// is optional, and if it is we will return the optional parameter value as
// the value of the dependency, similarly to how we do this with scalars.
catch (BindingResolutionException $e) {
if ($parameter->isDefaultValueAvailable()) {
array_pop($this->with);
return $parameter->getDefaultValue();
}
if ($parameter->isVariadic()) {
array_pop($this->with);
return [];
}
throw $e;
}
}
https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Container/Container.php#L1022-L1048
build()
後は shared
が設定されていればinstancesに設定してシングルトン的に振る舞うようになる
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
今回のまとめ
- DIコンテナ的に良い感じにインスタンス化してくれる
- bindingsが設定されていればabstract => concreteの紐付けもできる
- aliasの紐付けもできる
- シングルトンなインスタンスも設定できる