LoginSignup
6

More than 5 years have passed since last update.

バージョンはよく確認したほうがいい

Posted at

導入

なし崩し的に導入した laravel framework 4.2 で管理画面を作りたかったのでコピペしつつ書いてみた。

app/controllers/LoginController.php
  public function postLogin()
  {
    $inputs = Input::only('name', 'password');
    $result = $this->validateLogin($inputs);
    if ($result->fails()) {
      return Redirect::back()
        ->withErrors($result)
        ->withInput();
    }   

    $credentials = array(
      'name' => Input::get('name'),
      'password' => Input::get('password'),
    );  
    if (Auth::attempt($credentials)) {
      return 'loginned';
    }   

    $data = Input::only('name');
    $data['flash'] = 'Invalid name or password';
    return View::make('admin.login', $data);
  }

結果

569f88286c5e7aaea379f827a3ed64bc.png

ファッ!?
何故かログインに失敗する。

laravelのソースコードを読んでみる

larave/laravel GitHub
laravel/framework GitHub

Auth::attempt()の実体を求めてfunction attemptで検索するとIlluminate/Auth/Guard.phpが引っかかる。

Illuminate/Auth/Guard.php
    public function attempt(array $credentials = array(), $remember = false, $login = true)
    {
        $this->fireAttemptEvent($credentials, $remember, $login);
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        // If an implementation of UserInterface was returned, we'll ask the provider
        // to validate the user against the given credentials, and if they are in
        // fact valid we'll log the users into the application and return true.
        if ($this->hasValidCredentials($user, $credentials))
        {
            if ($login) $this->login($user, $remember);
            return true;
        }
        return false;
    }

あっヤバイ・・・わかんない。
そもそも呼び出したのはAuth::attempt()なのにGuardクラスになってる。
こいつどうやって実体化してるの・・苦しい・・。

laravelのソースコードを最初から読んでみる

困った時の最初から読み。
まあ手続き型のプログラムだし最初から読めば分かるでしょ。

  • public/.htaccss
    • 基本。他のフレームワークと同じ。
  • public/index.php
    • bootstrap/autoload.phpを呼んでbootstrap/start.php$appを生成して $app->run()
  • bootstrap/autoload.php
    • ああなんかちょっともう吐きそう・・。
    • vendor/autoload.php
      • vendor/composer/autoroad_real.php
        • うわぁ・・・。知らんけどautoloadしてるんやろはいはいわかったわかった次いこ。
    • Illuminate\Support\ClassLoader::register();
      • なんか重要そうなので探してみる。
      • Illuminate/Support/ClassLoader.php
        • 知らんけどクラス呼ぶときのrequireの手間とかあるあるエラーとか防いでそう。
        • この時点で色んなクラスが登録されてるのか良くわかんない。
  • bootstrap/start.php
    • $appnew Illuminate\Foundation\Applicationだ!
    • あとIlluminate/Foundation/start.phpも呼ばれてる。

という感じでframework側へ

frameworkのソースコードを読んでみる

「クソっ俺は管理画面を作りたいんであってframeworkはどうでもいいんだよ!」という心の声を押し殺しながら・・・

  • Illuminate/Foundation/Application.php

    • newから呼ばれているので普通に__construct()が呼ばれるんでしょう。phpの仕様わからないけど。
    • extends Container (Illuminate/Container/Container.php) はなんとなくクラスを配列っぽくアクセスしたり管理してるっぽい気がした。難しい。
  • Illuminate/Foundation/start.php

    • $app->instance()がやたら呼ばれてる。このあたりでいよいよ基礎的な要素が実体化されつつあるようなないような。
    • $app->registerCoreContainerAliases();
    • クラス呼び出しのエイリアスを貼ってるっぽい。
    • Auth::attempt()Authもすごくこれっぽい。結局AuthManager::attempt()が呼ばれてるっぽい。

魔境へ

  • Illuminate/Auth/AuthManager.php
    • attempt()が無いので仕方なくextends Manager
  • Illuminate/Support/Manager.php
Illuminate/Support/Manager.php
public function __call($method, $parameters)
    {
        return call_user_func_array(array($this->driver(), $method), $parameters);
    }

出たか__call
Auth::attempt($credentials)AuthManager->driver()->attempt($credentials)に変換されている。

Illuminate/Support/Manager.php
public function driver($driver = null)
    {
        $driver = $driver ?: $this->getDefaultDriver();
        // If the given driver has not been created before, we will create the instances
        // here and cache it so we can return it next time very quickly. If there is
        // already a driver created by this name, we'll just return that instance.
        if ( ! isset($this->drivers[$driver]))
        {
            $this->drivers[$driver] = $this->createDriver($driver);
        }
        return $this->drivers[$driver];
    }
Illuminate/Auth/AuthManager.php
public function getDefaultDriver()
    {
        return $this->app['config']['auth.driver'];
    }
app/config/auth.php
  'driver' => 'eloquent',
Illuminate/Support/Manager.php
protected function createDriver($driver)
    {
        $method = 'create'.ucfirst($driver).'Driver';
        // We'll check to see if a creator method exists for the given driver. If not we
        // will check for a custom driver creator, which allows developers to create
        // drivers using their own customized driver creator Closure to create it.
        if (isset($this->customCreators[$driver]))
        {
            return $this->callCustomCreator($driver);
        }
        elseif (method_exists($this, $method))
        {
            return $this->$method();
        }
        throw new \InvalidArgumentException("Driver [$driver] not supported.");
    }

createDriver("eloquent")からcreateEloquentDriver()が呼ばれている。魔法使い過ぎヤバイ。

Illuminate/Auth/AuthManager.php
    public function createEloquentDriver()
    {
        $provider = $this->createEloquentProvider();
        return new Guard($provider, $this->app['session.store']);
    }

function attemptの検索で出てきたGuardさんここで出番。
こういう繋がりでAuth::attempt()からGuard::attempt()が呼ばれている。
もう一度見直してみる。

Guardさんのprovider

Illuminate/Auth/Guard.php
    public function attempt(array $credentials = array(), $remember = false, $login = true)
    {
        $this->fireAttemptEvent($credentials, $remember, $login);
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        // If an implementation of UserInterface was returned, we'll ask the provider
        // to validate the user against the given credentials, and if they are in
        // fact valid we'll log the users into the application and return true.
        if ($this->hasValidCredentials($user, $credentials))
        {
            if ($login) $this->login($user, $remember);
            return true;
        }
        return false;
    }
Illuminate/Auth/Guard.php
protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

DBアクセスが発生するとしたらproviderに頼んでるあたりだ。

Illuminate/Auth/Guard.php
public function __construct(UserProviderInterface $provider,
                                SessionStore $session,
                                Request $request = null)
    {
        $this->session = $session;
        $this->request = $request;
        $this->provider = $provider;
    }

Guardさんのproviderはnew時の第1引数らしい。
確かにさっきAuthManager.phpで生成してた!うっ頭が・・・もう・・。

Illuminate/Auth/Guard.php
protected function createEloquentProvider()
    {
        $model = $this->app['config']['auth.model'];
        return new EloquentUserProvider($this->app['hash'], $model);
    }

ここだ!
整理するとAuthManager->driver()->attempt($credentials)の中のDB照合してそうな処理である

$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
$this->provider->validateCredentials($user, $credentials);

providerEloquentUserProviderだった!

魔境は脱したか・・?

Illuminate/Auth/EloquentUserProvider.php
    public function retrieveByCredentials(array $credentials)
    {
        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->createModel()->newQuery();
        foreach ($credentials as $key => $value)
        {
            if ( ! str_contains($key, 'password')) $query->where($key, $value);
        }
        return $query->first();
    }
Illuminate/Auth/EloquentUserProvider.php
public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');
        return new $class;
    }
Illuminate/Auth/EloquentUserProvider.php
public function validateCredentials(UserInterface $user, array $credentials)
    {
        $plain = $credentials['password'];
        return $this->hasher->check($plain, $user->getAuthPassword());
    }
  • createModel()$this->modelで与えられたクラスをnewしている。
  • validateCredentials()の方は$this->hasher->check()を呼び出している。
  • 両者ともに次のようにnew時の引数で与えられている。
Illuminate/Auth/EloquentUserProvider.php
    public function __construct(HasherInterface $hasher, $model)
    {
        $this->model = $model;
        $this->hasher = $hasher;
    }

戻って見直すと

Illuminate/Auth/Guard.php
protected function createEloquentProvider()
    {
        $model = $this->app['config']['auth.model'];
        return new EloquentUserProvider($this->app['hash'], $model);
    }

さて、

app/config/auth.php
  'model' => 'User',

$modelは"User"になるのでnew Userになるっぽい。
クエリ生成してfirst()のあたりはぱっと見は問題なさそう。

$this->hasher->check()Guard->app['hash']->check()になるっぽい?

Illuminate/Foundation/Application.php
'hash'           => 'Illuminate\Hashing\HasherInterface',

アレ?Interfaceで実体が無い・・。
仕方ないからcheck()で検索してみようか。

Illuminate/Hashing/BcryptHasher.php
public function check($value, $hashedValue, array $options = array())
    {
        return password_verify($value, $hashedValue);
    }

実体ぽいのが居た。
BcryptHasherがどうやって実体化しているのかはさっぱり分からないのだけれども、HashServiceProvider->register()がどっかから呼ばれているらしい。
HashServiceProviderが直接呼ばれているソースは無いので、Workbench{{name}}ServiceProviderあたりから呼ばれているのだろうか・・さっぱりわからない。

検証

Illuminate/Hashing/BcryptHasher.php
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = array())
    {
        return password_verify($value, $hashedValue);
    }

さてこいつがtrueを返せばきっとログインに成功するだろう。
試しにreturn true;を書いてログインしてみた。

5ad41dd745c7ba4cd30e2d48ee3237fb.png

行けた!ここか!こいつか!
対話式で確認してみる

% php -a                                                                                               (git)-[develop]
Interactive shell

php > password_verify('password', 'XXXXXXXXXXXXXXXXXXXXXXX');
PHP Fatal error:  Call to undefined function password_verify() in php shell code on line 1
php > exit

undefined function!?
ググってみる。

password_verify - php.net

password_verify
(PHP 5 >= 5.5.0)
password_verify — パスワードがハッシュにマッチするかどうかを調べる
% php -v
PHP 5.4.35 (cli) (built: Nov 21 2014 00:43:27) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies

あ・・・はい。

教訓

バージョンと名のつくものは依存性に気をつけましょう。(おしまい)

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
6