Help us understand the problem. What is going on with this article?

Laravel Managerの例としてAuthorizeManagerを作る

More than 1 year has passed since last update.

5.0の頃にはあったこのドキュメントが今はなくなってる
https://readouble.com/laravel/5.0/ja/extending.html

今の各コンポーネントをよく見るとなんとかManagerでも実はIlluminate\Support\Managerを使ってない。
https://github.com/laravel/framework/tree/5.6/src/Illuminate

CacheManagerは5.0で変更されてる。
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Cache/CacheManager.php
https://github.com/laravel/framework/blob/5.0/src/Illuminate/Cache/CacheManager.php

SessionManagerはまだ使ってる。全く変更されてないだけ。
https://github.com/laravel/framework/blob/5.6/src/Illuminate/Session/SessionManager.php

5.6で追加されたLogManagerも違うので最近でも使われてない。

extendやdriverの概念は残ってて使い方は大体同じ。当時どういう方針の変化でこうなってるのかは不明。

よく調べるとLaravel本体では使われなくなってると分かったけど自分で使うならIlluminate\Support\Managerを継承するのが早い。

参考にするManager

もちろんSocialite
https://github.com/laravel/socialite

例として一番分かりやすいソーシャルログインを早々に公式から提供されたらちょうどいい例がなくて何も作れない。

AuthorizeManager

今回思い付いたAuthorizeManagerはこの前見つけたGoogle API Clientのauthorize()が元。
https://github.com/google/google-api-php-client/blob/ceb9e53f70bf16a39f7334f7910ac8c5180f1760/src/Google/Client.php#L342

認証済HTTPクライアントを返すManager。
普通にフォームからログインするようなサイトが主な対象。
スクレイピングの前段階として使える。
最近は主要なサイトはAPI提供してるしログインした上でのスクレイピングなんてほとんどしないけど、少ないからこそ例としてはちょうどいい。

  • APIがあるならAPIを使うべき。
  • 相手サーバーの負荷を考えない過度なスクレイピングは避けるべき。

基本的な使い方

先にこういう使い方ができるようにしたいという例。

    $credentials = [
        'mail'     => '',
        'password' => '',
    ];

    if (Authorize::driver('sample')->login($credentials)) {
        /**
         * @var \Goutte\Client $client
         */
        $client = Authorize::driver('sample')->client();
        dump($client);
    }

ログイン済Goutte\Clientが得られるので後はGoutteの仕事。プロジェクト内で使うならスクレイピング部分までDriverに含めるけどパッケージならなるべくシンプルな単機能に徹する。

初期のAuthorizeManager

Managerクラスとして必須なのはgetDefaultDriver()のみ。
https://github.com/laravel/framework/blob/3976a4388dc80272ed7e18b999e64757756116eb/src/Illuminate/Support/Manager.php

DefaultDriverがないならSocialiteのように例外を発生させればいい。AuthorizeManagerではただのGuzzleHttp\Clientを返すDefaultDriverを用意している。

namespace Revolution\Authorize;

use Illuminate\Support\Manager;

use Revolution\Authorize\Contracts\Factory;

class AuthorizeManager extends Manager implements Factory
{
    /**
     * Get the default driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return 'default';
    }

    /**
     * Create an instance of the specified driver.
     *
     * @return \Revolution\Authorize\Drivers\AbstractDriver
     */
    protected function createDefaultDriver()
    {
        return new Drivers\DefaultDriver();
    }
}

ここからDriverを増やしていく。

Driver Interface

型とかは厳密には決めてない。ログインに必要な情報はサイトごとに違うし、何のClientを返すかもDriverの自由。
普通はGoutteかGuzzleだろうけど別にGoogle_Clientでもいい。

interface Driver
{
    /**
     * Login.
     *
     * @param mixed $credentials
     *
     * @return bool
     */
    public function login($credentials = null): bool;

    /**
     * Client.
     *
     * @return mixed
     */
    public function client();
}

$credentialsはarrayでもobjectでも。

    $credentials = [
        'mail'     => '',
        'password' => '',
    ];
    $credentials = new Credentials([
        'mail'     => '',
        'password' => '',
    ]);

Credentialsクラスも用意してるけどDriver内ではdata_get()で見てるので本当にどっちでも同じ。
独自のDriverでは独自のクラスを使ってもいい。

Drivers

ログイン方法が分かるサイトをいくつか選んだだけ。

DefaultDriver

なにもせずGuzzleHttp\Client返すだけのdefault。

namespace Revolution\Authorize\Drivers;

use GuzzleHttp\Client;

class DefaultDriver extends AbstractDriver
{
    /**
     * @var Client
     */
    private $client;

    /**
     * DefaultDriver constructor.
     */
    public function __construct()
    {
        $this->client = new Client(['cookies' => true]);
    }

    /**
     * Login.
     *
     * @param mixed $credentials
     *
     * @return bool
     */
    public function login($credentials = null): bool
    {
        return true;
    }

    /**
     * Client.
     *
     * @return mixed
     */
    public function client()
    {
        return $this->client;
    }
}

Driverを作ってAuthorizeManagerにcreate〇〇Driver()を増やす作業の繰り返し。

NiconicoDriver

Goutteで普通にPOSTする例。

public function login($credentials = null): bool
{
    $crawler = $this->client->request('POST', 'https://account.nicovideo.jp/api/v1/login', [
        'mail_tel' => data_get($credentials, 'mail'),
        'password' => data_get($credentials, 'password'),
    ]);

    return $crawler->getUri() === 'https://account.nicovideo.jp/my/account';
}

A8netDriver

Goutteのformを使う例。

public function login($credentials = null): bool
{
    $crawler = $this->client->request('GET', 'https://www.a8.net/');

    $form = $crawler->filter('form[name=asLogin]')->form();

    $crawler = $this->client->submit($form, [
        'login'  => data_get($credentials, 'login'),
        'passwd' => data_get($credentials, 'password'),
        'moa'    => '/a8',
    ]);

    return $crawler->getUri() === 'https://pub.a8.net/a8v2/asMemberAction.do';
}

ValueCommerceDriver

特定のCookieが必要なちょっと特殊な例。

use Goutte\Client;
use Symfony\Component\BrowserKit\Cookie;

public function login($credentials = null): bool
{
    $crawler = $this->client->request('GET', 'https://aff.valuecommerce.ne.jp/?type=4');

    //これがないとJSかCookieが無効と判断される
    $this->client->getCookieJar()
                 ->set(new Cookie('I_do_Javascript', 'yes', strtotime('+1 day')));

    $form = $crawler->filter('form')->form();

    $crawler = $this->client->submit($form, [
        'login_form[emailAddress]'    => data_get($credentials, 'mail'),
        'login_form[encryptedPasswd]' => data_get($credentials, 'password'),
    ]);

    return $crawler->getUri() === 'https://aff.valuecommerce.ne.jp/home';
}

その他

Goutteでログインできるなら簡単。
最近はそれじゃログインできないサイトも多いのでそういう場合はheadless chromeとかが必要かも。

node.jsでのheadless chromeは前に試した。
https://github.com/kawax/headless-chrome-google-login

当時はPHP用はなかったけど今は見つけられるのでこういうのでなんとかなるかも(未確認)
https://github.com/chrome-php/headless-chromium-php
あくまでもClientとして何を返すかは自由なのでDriver次第。

拡張

Socialiteと同じくextend()で好きなように拡張できる。

プロジェクト内でのみ使う場合

どこかにCustomDriverを作る。
app/Authorize/CustomDriver.php

AppServiceProviderで登録。

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use Revolution\Authorize\Facades\Authorize;
use App\Authorize\CustomDriver;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Authorize::extend('custom', function ($app) {
            return new CustomDriver;
        });
    }
}

customとして使えるようになる。

if (Authorize::driver('custom')->login($credentials)) {
    /**
     * @var \GuzzleHttp\Client $client
     */
    $client = Authorize::driver('custom')->client();
}

composerパッケージにする場合

ServiceProviderを作って…とSocialiteと同じなので省略。

AuthorizeManager本体に入れたい場合

GitHubでプルリク。

終わり

この説明書くのが一番大変なのでこの辺で一旦公開…。(Qiita版のこの記事は後で修正しないけど)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away