Laravelのサービス取得方法たくさんあるけど、なにがちがうん？

Laracastsがこんなんポストしてました。

https://x.com/laracasts/status/1889715630833606903


Laravelではサービスコンテナ（プロバイダ）を使ってサービスを取得する方法がたくさんあります。
（まさか8種類もあるとは思わなかったですが。。）
「なにがちがうん？」ってなったので調べました。

:::note warn
勢いで調べたので、ちがったらごめんなさい。
:::

### app({サービス名})

`app()`はhelpers.phpにあります。コード的にはこうなってます。

```php: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メソッドを読んでサービスを取得しています。コード的にはこんな感じ。

```php:Container.php
    /**
     * 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メソッド読んでますね。そこもみてみましょう。

```php:Container.php
    /**
     * 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内にあります。コードは以下のようになってます。

```php: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インスタンスを返すだけでした。

```php:helpers.php
        if (is_null($abstract)) {
            return Container::getInstance();
        }
```

取得したContainerインスタンスのmakeメソッドを呼んでいるので、`app()` の引数ありの場合の

```php:helpers.php
        return Container::getInstance()->make($abstract, $parameters);
```

これを実行してるのと同じです。
つまり `app({サービス名})` と同じ

### app()->resolve({サービス名})

前述の通り、 `app()` 引数なしはContainerインスタンスを返すだけです。
そのresolveメソッドを呼んでますが、Containerのmake内でもresolveメソッドを呼んでるので、
つまり、 `app({サービス名})` と同じ。


### app()->get({サービス名})

違うメソッド名きました！ `get()` はContainerインスタンスのgetメソッドを呼んでます。
中身は以下の通り

```php:Container.php
    /**
     * {@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メソッドを見れば良さそうです。

```php:Container.php
    /**
     * 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クラスの以下の部分で、引数に指定された変数を解析しています。

```php:ControllerDispatcher.php
    /**
     * 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メソッドを呼び出しています。

```php:ControllerDispatcher.php
    /**
     * 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:facade.stub
<?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内では、以下の処理を実行して元クラスのオブジェクトを呼び出します。


```php:Facade.php
    /**
     * 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({サービス名})` とやってることは同じでした。好きなものを使いましょう。
ただ、使い方はプロジェクトで統一しておいた方がよいです