0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-02-13

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

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

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

app({サービス名})

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

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メソッド読んでますね。そこもみてみましょう。

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内にあります。コードは以下のようになってます。

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

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

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

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

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

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

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

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

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

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メソッドを見れば良さそうです。

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

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メソッドを呼び出しています。

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クラスを作成します。

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内では、以下の処理を実行して元クラスのオブジェクトを呼び出します。

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?