質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.60%

【Laravel】auth()->user() のuser()はどこからきているのか

受付中

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 6,503

score 19

$user = auth()->user();

これでUserインスタンスが取得できるという結果はわかるのですが、過程がわかりません。

auth()という関数は、helpers.phpに以下あるように綿々と処理が連鎖していく様子はわかるのですが、

function auth($guard = null)
    {
        if (is_null($guard)) {
            return app(AuthFactory::class);
        }

        return app(AuthFactory::class)->guard($guard);
    }

↓ 更にappメソッドが呼ばれてる

function app($abstract = null, array $parameters = [])
    {
        if (is_null($abstract)) {
            return Container::getInstance();
        }

        return Container::getInstance()->make($abstract, $parameters);
    }

・・・・・

auth()にチェインしている user() という関数はいったいどこで定義されているものなのでしょうか。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

0

処理を追いながら解説します。

サービスコンテナが絡むので、その辺りの理解があると理解しやすいと思います。

参考: サービスコンテナ

※ 以下、Laravel5.5のソースを参考にしています。最新版だと少し違う可能性がありますが、流れは概ね同じはずです。

サービスコンテナ

auth()->user()を呼び出すと、ヘルパ関数app()の下の分岐に入ります。

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

        // ここで、$abstract = AuthFactory::class
        // AuthFactoryはhelpers.phpファイル上部のuseで定義されているエイリアス
        // クラスの実体は Illuminate\Contracts\Auth\Factory

Laravelのサービスコンテナ、\Illuminate\Container\Containerクラスのmake()というメソッドが呼ばれています。
このメソッドは、サービスコンテナに事前に登録されている規則に従ってオブジェクトを生成します。

\Illuminate\Container\Container::make()の第一引数は、コンテナに登録されているオブジェクトを呼び出すためのキーとなる文字列です。
インターフェース名やクラス名、識別用の文字列(ex. 'auth', 'config'など)のいずれかですね。

コンテナから生成されるオブジェクト

今回の場合は、\Illuminate\Contracts\Auth\Factoryというインターフェース名が引数に与えられていますので、\Illuminate\Contracts\Auth\Factoryというキーに紐つけられたクラスのインスタンスが生成されます。

Laravelのデフォルトの状態では、\Illuminate\Contracts\Auth\Factoryインターフェースを実装したIlluminate\Auth\AuthManagerクラスが紐つけられています。
この紐付けはServiceProviderの仕組みで行われています。詳細処理の解説は省略しますが、\Illuminate\Auth\AuthServiceProviderや\Illuminate\Foundation\Application::registerCoreContainerAliases()あたりですかね。

マジックメソッドによる処理の移譲

ここまででauth()は\Illuminate\Auth\AuthManagerのインスタンスを返すことがわかりました。
つまり、\Illuminate\Auth\AuthManager::user()が呼ばれています。

このメソッドは直接定義されていないので、マジックメソッドの__call()が呼ばれます。
参考: PHP: オーバーロード

__call()の中で、Guardクラスのインスタンスの同名メソッドに処理が移譲されています。

    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }

guard()メソッドの実装を見ると、Illuminate\Contracts\Auth\Guardインターフェースの実装クラスのうちデフォルトのものが呼ばれる形になります。

    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

初期設定では認証のドライバーは'session'になっているので、
処理を追うと
AuthManager::guard() → AuthManger::resolve() → AuthManager::createSessionDriver()
の流れで\Illuminate\Auth\SessionGuardが生成されていることがわかります。

以上から、ご質問への答えとしては

  • user()という関数の実体は\Illuminate\Auth\SessionGuard::user()である
  • ただし設定によってはGuardのインターフェースを実装した別のクラスの関数になる

ということなります。

まとめ

  1. サービスコンテナの設定通りに、\Illuminate\Contracts\Auth\Factoryに対して\Illuminate\Auth\AuthManagerが取り出される
  2. \Illuminate\Auth\AuthManager::user()はデフォルトのGuardにマジックメソッドにより委譲される
  3. Guard::user()が呼び出される

サービスコンテナによるオブジェクトの解決とマジックメソッドの多用は双方Laravelの特徴です。
この2つの合わせ技は慣れないとかなり処理を追いにくいですね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

デバッガ等で調べればなんのクラスかは簡単に調べられますが、過程を知りたいと言うことなので追いかけてみます。かなり道のりは遠いので頑張って読んでください。

まずapp()関数についてマニュアルを見ましょう。

app()
  app関数は、サービスコンテナのインスタンスを返します。
  $container = app();
  コンテナにより依存解決する、クラス名かインターフェイス名を渡すこともできます。
  $api = app('HelpSpot\API');

この場合はパラメタAuthFactory::classを与えてますので、このクラス名またはインタフェース名をサービスコンテナで依存解決します。「サービスコンテナによる依存解決」をざっくりいうと、あらかじめあるクラスのインスタンスを生成する方法に名前を付けて登録して管理するのがサービスコンテナ、サービスコンテナに名前を与えてそのクラスのインスタンスを作ってもらうのが依存解決です。名前はなんでもいいのですが、普通はクラス名かインタフェース名を使います。また特によく使われる物には短い名前が特別に用意されていることもあります。

サービスコンテナについて詳しくはマニュアルの「サービスコンテナ」を参照してください。

AuthFactoryhelper.phpの冒頭で以下のようにuseされていますので実際にはIlluminate\Contracts\Auth\Factoryを表します。

use Illuminate\Contracts\Auth\Factory as AuthFactory;

さて、Illuminate\Contracts\Auth\Factory::class'Illuminate\Contracts\Auth\Factory'という文字列に展開されますが、この名前で登録されているクラスはなにかというのが問題になります。

検索してみると\Illuminate\Foundation\Application::registerCoreContainerAliasesという関数内に見つかります。

            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],

registerCoreContainerAliasesはその名の通り登録名に別名を用意する関数で、'Illuminate\Contracts\Auth\Factory''auth'という名前の別名であるということになります。

では'auth'は何になるのでしょうか。これは先ほど言ったよく使われるので短い名前で用意しているものの一つで、マニュアルの「ファサード」の「ファサードクラス一覧」に一覧表があります。この表の「サービスコンテナ結合」カラムが用意されている短い名前で、ここからauthを探すと\Illuminate\Auth\AuthManagerクラスであることがわかります。

さて、\Illuminate\Auth\AuthManagerからuser関数を探すと...ありません。でもよくみると__call関数は用意されています。

    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }

つまり$this->guard()が返したなにかのクラスのインスタンスに対してuser()を呼ぶわけです。

ということで$this->guard()を見るとこうなっています。

    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

引数を省略した場合は$this->getDefaultDriverが返した値を$nameとして`$this->resolve($name)の値を返します。

getDefaultDriverは簡単で

    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

ですからconfig/app.phpの中を見ると(独自に書き換えていなければ)'web'であることがわかります。

さて、パラメタwebresolveを呼ぶとどうなるでしょうか。

   /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

getConfigは簡単です。auth.guards.webはさっきのconfig/app.phpをみて[ 'driver' => 'session', 'provider' => 'users', ]です。

    protected function getConfig($name)
    {
        return $this->app['config']["auth.guards.{$name}"];
    }

カスタムドライバの登録はしてないとして結局呼ばれるのは$driverMethod = 'create'.ucfirst($config['driver']).'Driver';で求められた名前の関数です。driverの値はsessionなのでcreateSessionDriver関数ということになります。

ではcreateSessionDriver関数を見てみましょう。

    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        // When using the remember me functionality of the authentication services we
        // will need to be set the encryption instance of the guard, which allows
        // secure, encrypted cookie values to get generated for those cookies.
        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        return $guard;
    }

作成したあとの初期化処理が入っていてちょっと長いですが、返されるのは\Illuminate\Auth\SessionGuardクラスのインスタンスです。このクラスのuser()関数が探していたものです。お疲れ様でした。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/04 10:13

    詳細なご回答ありがとうございました!

    重ねて質問なのですが、こういった中身の実装というのは使っているうちに自然と理解できてくるものなのでしょうか?
    Laravelを使って簡単な掲示板を作れるようにはなったのですが、コードを書いて取り出せる結果は分かっても過程がよくわからないので、全く「身についている」感覚になりません。
    なので、Laravelソースコードを読み込まなきゃいけないのか?そもそもPHPの知識が足りない(=Laravelに手を出してはいけない)のか?それとも、ガンガンLaravelを使ってコードを書きまくれば感覚として分かってくるものなのか?結局何をすればいいのか?というのがわかりません。。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る