Laracastsがこんなんポストしてました。
Laravelではサービスコンテナ(プロバイダ)を使ってサービスを取得する方法がたくさんあります。
(まさか8種類もあるとは思わなかったですが。。)
「なにがちがうん?」ってなったので調べました。
勢いで調べたので、ちがったらごめんなさい。
app({サービス名})
app()
はhelpers.phpにあります。コード的にはこうなってます。
/**
* Get the available container instance.
*
* @template TClass
*
* @param string|class-string<TClass>|null $abstract
* @param array $parameters
* @return ($abstract is class-string<TClass> ? TClass : ($abstract is null ? \Illuminate\Foundation\Application : mixed))
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Container::getInstance();
}
return Container::getInstance()->make($abstract, $parameters);
}
app({サービス名})
は引数をつけているので、Containerオブジェクトのmakeメソッドを読んでサービスを取得しています。コード的にはこんな感じ。
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
あー、自身のresolveメソッド読んでますね。そこもみてみましょう。
/**
* Resolve the given type from the container.
*
* @param string|callable $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Illuminate\Contracts\Container\CircularDependencyException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
// 中略
$concrete = $this->getContextualConcrete($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
// 中略
// 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.
$object = $this->isBuildable($concrete, $abstract)
? $this->build($concrete)
: $this->make($concrete);
// 中略
return $object;
}
最終的に自身のbuild()を呼んでいます。これにより対応するオブジェクトが返ってきて、返却されます。
resolve({サービス名})
resolve()
もhelpers.php内にあります。コードは以下のようになってます。
/**
* Resolve a service from the container.
*
* @template TClass
*
* @param string|class-string<TClass> $name
* @param array $parameters
* @return ($name is class-string<TClass> ? TClass : mixed)
*/
function resolve($name, array $parameters = [])
{
return app($name, $parameters);
}
つまり、app({サービス名})
と同じ。
app()->make({サービス名})
app()
は引数なしの場合は、 Containerインスタンスを返すだけでした。
if (is_null($abstract)) {
return Container::getInstance();
}
取得したContainerインスタンスのmakeメソッドを呼んでいるので、app()
の引数ありの場合の
return Container::getInstance()->make($abstract, $parameters);
これを実行してるのと同じです。
つまり app({サービス名})
と同じ
app()->resolve({サービス名})
前述の通り、 app()
引数なしはContainerインスタンスを返すだけです。
そのresolveメソッドを呼んでますが、Containerのmake内でもresolveメソッドを呼んでるので、
つまり、 app({サービス名})
と同じ。
app()->get({サービス名})
違うメソッド名きました! get()
はContainerインスタンスのgetメソッドを呼んでます。
中身は以下の通り
/**
* {@inheritdoc}
*
* @return mixed
*/
public function get(string $id)
{
try {
return $this->resolve($id);
} catch (Exception $e) {
if ($this->has($id) || $e instanceof CircularDependencyException) {
throw $e;
}
throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $e);
}
}
メソッドないで、自身のresolveメソッドを呼んでいます。
つまり、 app({サービス名})
と同じ。
app()[{サービス名}]
Containerインスタンスに対して配列としてアクセスしています。これはContainerクラスがArrayAccessインターフェイスを実装しているからです。ArrayAccessなのでoffsetGetメソッドを見れば良さそうです。
/**
* Get the value at a given offset.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key): mixed
{
return $this->make($key);
}
makeメソッド呼んでました。
つまり、 app({サービス名})
と同じ。
public function __construct({サービス名} $service)
Symfonyのオートワイヤリングと同じく、コンストラクタに指定されたサービスを自動注入します。一番好き。
HTTPリクエストだったりコマンドだったりを実行すると、ApplicationクラスのhandleRequest, handleCommandメソッドが実行されます。今回はHTTPリクエストだった場合で記載しますが、内部を辿っていくと、ControllerDispatcherクラスの以下の部分で、引数に指定された変数を解析しています。
/**
* Resolve the parameters for the controller.
*
* @param \Illuminate\Routing\Route $route
* @param mixed $controller
* @param string $method
* @return array
*/
protected function resolveParameters(Route $route, $controller, $method)
{
return $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
}
そこからさらに辿ると同クラスのtransformDependencyメソッドを呼び出しています。
/**
* Attempt to transform the given parameter into a class instance.
*
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param object $skippableValue
* @return mixed
*/
protected function transformDependency(ReflectionParameter $parameter, $parameters, $skippableValue)
{
// 中略
if ($className && ! $this->alreadyInParameters($className, $parameters)) {
$isEnum = (new ReflectionClass($className))->isEnum();
return $parameter->isDefaultValueAvailable()
? ($isEnum ? $parameter->getDefaultValue() : null)
: $this->container->make($className); // ←ここ!
}
return $skippableValue;
}
結果、引数がクラスの場合は、Containerクラスのmakeメソッドを使ってオブジェクトを取得しているので、
つまり、app({サービス名})
と同じ。
Facades{サービス名}::method()
Facades\
をnamespaceの前につけて、useすると、Facadeを介して静的メソッドとして実行することができます。
これは、AliasLoader
クラスのloadメソッドがオートローダー登録メソッドとして追加されることで実現します。このloadメソッドを辿っていくと、 Facades\
をnamespaceの前につけたクラスをロードする際に、以下のファイルを使って、そのサービスクラス専用のFacadeクラスを作成します。
<?php
namespace DummyNamespace;
use Illuminate\Support\Facades\Facade;
/**
* @mixin \DummyTarget
*/
class DummyClass extends Facade
{
/**
* Get the registered name of the component.
*/
protected static function getFacadeAccessor(): string
{
return 'DummyTarget';
}
}
このファイルのDummy***
部分を置換していました。よって、Facadeとして動作するわけですが、Facade内では、以下の処理を実行して元クラスのオブジェクトを呼び出します。
/**
* Resolve the facade root instance from the container.
*
* @param string $name
* @return mixed
*/
protected static function resolveFacadeInstance($name)
{
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
if (static::$app) {
if (static::$cached) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
return static::$app[$name];
}
}
static:$app
は Applicationクラスです。よって、app()[{サービス名}]
と同じ。
つまり、app({サービス名})
と同じ。
まとめ
結果、app({サービス名})
とやってることは同じでした。好きなものを使いましょう。
ただ、使い方はプロジェクトで統一しておいた方がよいです