tanaka's Programming Memo

プログラミングについてのメモ。

Laravel5.3でのテストのメモ

自分向けのメモです。

LaravelにはPHPUnitによるテストの設定が組み込まれています。公式マニュアルで概要を確認して、データベースのチェックや、URLごとのテストについて調べました。

5.2から5.3になる段階で、データベースとモックに関する記述が増えていました。


実行時の注意点

以下のようなエラーが表示された場合の対処方法です。

PHP Fatal error:  Call to undefined method PHPUnit_Framework_TestResult::warnings() in /Users/yutanaka/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 297

Laravelに組み込まれているphpunitはやや古い可能性があります。グローバルのphpunitとバージョンが異なる場合に、上記のようなエラーが表示される場合があります。その際は、Laravelに組み込まれているphpunitを呼び出すように package.json に以下のようなスクリプト呼び出しを追加して、 npm test でテストを開始すると良いでしょう。

  "scripts": {
    // :
    "test": "vendor/phpunit/phpunit/phpunit"
  },

公式マニュアル概要

Testing - Laravel - The PHP Framework For Web Artisans を参照して、概要を確認します。

Introduction

  • Laravelアプリフォルダー直下のphpunit.xmlと、testsフォルダー内にサンプルが設定されている
  • アプリフォルダー内で phpunit で実行できる
テスト環境
  • テストを開始すると、Laravelが自動的に環境設定をtestingに設定する
  • Laravelは、テスト中はarrayドライバーでセッションやキャッシュを設定するので、テスト中のセッションやキャッシュは後に残ることがない
  • テスト環境は、phpunit.xmlファイルのtesting環境変数で変更して構わない。変更した後は、artisanコマンドのconfig:clearを実行して、設定のキャッシュをクリアすること
新しいテストの定義と実行
  • php artisan make:test UserTest などで、新しいテストを定義できる
  • testsフォルダー内に、新しいUserTestファイルが作成されるので、phpunitのコマンドを書いてテスト内容を記述する
  • 実行は、phpunitを呼び出す
  • setUpメソッドをオーバーライドする時は、setUp関数内で parent::setUp() として、親のsetUpを呼び出すこと

アプリケーションのテスト

Application Testing - Laravel - The PHP Framework For Web Artisans

  • Laravelは、HTTPリクエストを生成したり、出力をテストしたり、フォームを埋めたりするのが楽になるAPIを提供する
  • visit()メソッドは、GETリクエストをアプリケーションに発行する
  • see()メソッドは、アプリケーションの戻り値から指定した文字列が含まれるかをアサートする
  • dontSee()メソッドは、指定の文字列が戻り値に含まれないことをアサートする
  • visitRoute()メソッドは、ルート名でGETリクエストを生成できる
$this->visitRoute('profile');

$this->visitRoute('profile', ['user' => 1]);

アプリケーションとの連携

リンクをクリックする
  • visit()で画面を表示
  • click('<クリックしたい文字列>')
  • seePageIs('遷移先のURL')
<a href="/about-us">About Us</a>
  • 上記のHTMLがあった場合、以下のテストが使える
public function testBasicExample()
{
    $this->visit('/')
         ->click('About Us')
         ->seePageIs('/about-us');
}
  • seePageIs()の代わりに、 seeRouteIs() も使える
->seeRouteIs('profile', ['user' => 1]);
フォームの操作
  • type(<入力内容>, <フォームのname>)で、指定のnameの入力フォームに指定のテキストを入力する
  • select(<選択内容>, <フォームのname>)で、ラジオボタンやドロップダウンの選択
  • check(<フォームのname>)で、指定のチェックボックスにチェック
  • uncheck(<フォームのname>)で、指定のチェックボックスのチェックを外す
  • attach(<ファイルのパス>, <フォームのname>)で、指定のファイルをアップロード対象にする
public function testPhotoCanBeUploaded()
{
    $this->visit('/upload')
         ->attach($pathToFile, 'photo')
         ->press('Upload')
         ->see('Upload Successful!');
}
  • press(<ボタンやテキストや要素のname>)で、該当するものを押す
JSONテスト
  • json, get, post, put, patch, deleteメソッドで、指定のURLに、指定のパラメータをJSONで渡した呼び出しができる
  • seeJson()で、指定のデータが戻り値に含まれるかをチェックする。「含むか」なので、完全一致じゃなくてもテストは成功する
  • JSONの完全な一致をチェックしたい場合は、seeJsonEquals()メソッドを利用する
  • 戻り値の内容ではなく、構造をチェックしたい場合は、seeJsonStructure()メソッドを使う。指定していないキーがあっても、指定した構造が含まれていればテストは成功する
  • 何らかのキーに、指定の構造が含まれるかをチェックする場合は、 * を使う。ネストも可能
SessionとAuthentication
  • withSession()メソッドで、セッションを設定できる。ページを訪れる前に、テストしたい値をセッションに設定しておくことができる
  • actingAs()で、カレントユーザーを設定できる。事前に、ModelFactoryで、新規のユーザーモデルを作成して、それを引数に渡してユーザーを作成できる。Factoryでテスト用ユーザーを登録して、それをユーザーとして認証する例
<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
        $user = factory(App\User::class)->create();

        $this->actingAs($user)
             ->withSession(['foo' => 'bar'])
             ->visit('/')
             ->see('Hello, '.$user->name);
    }
}
  • actingAs()の第2引数を指定すると、毎回認証が必要な保護認証に対して、保護名を設定できる
ミドルウェアの無効化
  • WithoutMiddlewareトレイトを使うと、ミドルウェアを無効化して、テストを簡易にできる
    • クラス内に、use WithoutMiddleware;を宣言すると、そのテスト全てでミドルウェアが無効になる
<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use WithoutMiddleware;

    //
}
    • 特定のテストのみで無効にしたい場合は、テストメソッドの中で、$this->withoutMiddleware();を呼び出す
<?php

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->withoutMiddleware();

        $this->visit('/')
             ->see('Laravel 5');
    }
}
カスタムHTTPリクエスト
  • カスタムリクエストを作成して、戻り値のIlluminate\Http\Responseオブジェクトを取得したい場合は、call()メソッドを使う
public function testApplication()
{
    $response = $this->call('GET', '/');

    $this->assertEquals(200, $response->status());
}
  • POST, PUT, PATCHリクエストに必要な入力データは、配列で渡す
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
PHPUnitアサーション

以下、Laravelが提供するPHPUnitテスト用のメソッド。

  • assertResponseOk();
    • クライアントのレスポンスコードがOkかを判定
  • assertResponseStatus($code);
  • assertViewHas($key, $value = null);
    • 戻り値のビューに対して、$keyに$valueが設定されているかを判定
  • assertViewHasAll(array);
    • 戻り値が、指定の配列を持つかを判定
  • assertViewMissing($key);
    • 戻り値のビューに指定のkeyが含まれないことを判定
  • assertRedirectedTo($uri, $with = []);
    • 指定のURIにリダイレクトしたかを判定
  • assertRedirectedToRoute($name, $parameters = [], $with = []);
    • 指定のルートにリダイレクトされたかを判定
  • assertRedirectedToAction($name, $parameters = [], $with = []);
    • 指定のアクションにリダイレクトされたかを判定
  • assertSessionHas($key, $value = null);
    • セッションが指定のkeyとvalueを持つかを判定
  • assertSessionHasAll(array $bindings);
    • セッションが指定の配列の値を持つかを判定
  • assertSessionHasErrors($bindings = [], $format = null);
    • セッションが指定のエラーになっていないかを判定
  • assertHasOldInput();
    • セッションがold inputを持っていないかを判定
  • assertSessionMissing($key);
    • セッションが指定のkeyを持っていないことを判定

Databaseのテスト

Laravelは、データベース駆動のアプリケーション向けのテスト環境も提供しています。

  • seeInDatabase()メソッドで、指定のデータが、データベースの指定のテーブルに含まれるかを判定

テスト後のデータベースのリセット

各テスト後に、テスト時のデータベースへの変更をもとに戻す方法です。

Migrationを使う
  • 次のテストの前に、DatabaseMigrationsトレイトを使って、Migrationする
  • テストクラス内で、 use DatabaseMigrations; を定義すれば、テストごとに自動的にマイグレーションを実施する
トランザクションを使う
  • 全てのテストを、データベースのトランザクションで囲む方法もある
  • DatabaseTransactionsトレイトを使えば、Migrationsと同様にテストごとに自動的にこれを行う
  • このトレイトは、デフォルトのデータベース接続にのみ対応

モデルファクトリー

複数のテストにまたがって、共通の幾つかのレコードをデータベースに登録したい場合、手動で特定の値を列ごとに設定するのではなく、Eloquentのモデルファクトリーを利用することができます。

  • database/factories/ModelFactory.php ファイルに、データの定義例がある
  • $factory->define()メソッドに、デフォルトデータを戻すクロージャーを渡す
  • クロージャーは、Faker PHP ライブラリのインスタンスを返し、テストのための乱数を設定できる
  • ModelFactory.phpファイルには、自由にファクトリーを追加できる
  • UserFactory.phpやCommentFactory.phpなど、database/factoriesディレクトリーに加えることができる
ファクトリーの States
  • Statesとして、個別の修正を定義して、様々な組み合わせでモデルファクトリーに適用できる
  • Userモデルに delinquent というstate を持たせて、デフォルトの属性値を設定するなどができる
  • account_status属性に'delinquent'という値を持たせるためのステータスである delinquent を宣言する例
$factory->state(App\User::class, 'delinquent', function ($faker) {
    return [
        'account_status' => 'delinquent',
    ];
});

Factoryの利用

モデルの作成
  • ファクトリーを定義したら、テスト関数でfactory(モデルクラス)->make();とすれば、ファクトリーから生成したモデルのインスタンスを得られる
public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // Use model in tests...
}
  • factory()の第2引数に数を渡すと、指定の数のインスタンスを生成する
  • 同じく、ファクトリー名を渡すと、該当するファクトリーを生成する
// Create three App\User instances...
$users = factory(App\User::class, 3)->make();

// Create an "admin" App\User instance...
$user = factory(App\User::class, 'admin')->make();

// Create three "admin" App\User instances...
$users = factory(App\User::class, 'admin', 3)->make();
States を適用する
  • モデルに state を適用することができる
$users = factory(App\User::class, 5)->states('deliquent')->make();
  • 多数の state をモデルに適用するには、その名前を以下のように指定する
$users = factory(App\User::class, 5)->states('premium', 'deliquent')->make();
属性の上書き
  • モデルのいくつかのデフォルト値をオーバーライドしたい場合、makeメソッドに配列を渡す
  • 指定した値のみが上書きされて、それ以外の値はファクトリーに設定されているデフォルト値が設定される
$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);
モデルを生成して保存する
  • create メソッドは、モデルインスタンスを作成した上で、Eloquentの save メソッドで保存する
    // Create a single App\User instance...
    $user = factory(App\User::class)->create();

    // Create three App\User instances...
    $users = factory(App\User::class, 3)->create();
  • makeメソッドと同様に配列を渡すと値を設定することができる
リレーションしたデータの作成
  • create()してモデルを作成すると collection のインスタンスが返されるので、 each() メソッドなどを使って作成されたばかりのモデルのインスタンスを取り出して、リレーションさせる他のモデルを生成することができる
$users = factory(App\User::class, 3)
           ->create()
           ->each(function ($u) {
                $u->posts()->save(factory(App\Post::class)->make());
            });
リレーションと属性のクロージャ
  • ファクトリーの定義で、モデルにリレーションを設定するのにクロージャー属性を使うこともできる
  • 新しい Post を作成するのと同時に、新しい User のインスタンを作成する方法は以下の通り
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        }
    ];
});
  • これらのクロージャーは、引数として、そのクロージャーを含むファクトリーの属性の配列を受け取る
    • Postを生成した時に User も作成
    • user_type のクロージャーは $post 配列を受け取って、そこから user_id で生成した User の IDを取り出して検索して、Userに設定されているtypeと、Postのuser_typeをリレーションさせている
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        },
        'user_type' => function (array $post) {
            return App\User::find($post['user_id'])->type;
        }
    ];
});

モック

イベントのモック

  • Laravelで大量にイベントシステムを構築していた場合、テスト中にイベントをモック化できる
  • ユーザー登録をした時に、登録が完了したイベント(例えばUserRegistered)が発行すると、登録完了のメールが送信されてりする。それをキャンセルするとテストが楽である
  • $this->expectsEvents(App\Events\UserRegistered::class); とすると、UserRegisteredイベントをキャンセルできる
  • doesntExpectEvents()メソッドを使うと、指定のイベントが発動しなかったことを確認できる
  • withoutEvents()メソッドを呼び出すと、すべてのイベントハンドラを抑制できる

Fakeの利用

  • Eventファサードの fake メソッドを利用することでもモック化をして、すべてのイベントリスナーを無効にできる
  • それから、イベントが発生したかや、どのようなデータを返したかをチェックすれば良い
<?php

use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;
use Illuminate\Support\Facades\Event;

class ExampleTest extends TestCase
{
    /**
     * Test order shipping.
     */
    public function testOrderShipping()
    {
        Event::fake();

        // Perform order shipping...

        Event::assertFired(OrderShipped::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });

        Event::assertNotFired(OrderFailedToShip::class);
    }
}

ジョブのモック

アプリケーションがリクエストを作成した時に、作成したコントローラーが特定のジョブを呼び出すかを試す簡単なテストをしたい場合があるでしょう。そのような時の方法です。これにより、ルートとコントローラーのテストを分離することができます。

  • $this->expectsJobs(App\Jobs\PurchasePodcast::class); などのようにあらかじめ呼び出しておくことで、指定のジョブが呼び出されたことを確認する。行うのは確認のみで、ジョブ自体を実行することはしない
  • このメソッドは、DispatchesJobsトレイトか、dispatch()メソッドによって発行されたジョブにのみ反応する。Queue::push()で直に送信されたジョブは対象外
  • ジョブが呼び出されなかったことを確認するには、 $this->doesntExpectJobs(ShipOrder::class) などのようにする
  • $this->withoutJobs() をテストメソッド内で呼び出すと、そのテスト中に予約されたすべてのジョブは破棄される
Fakeの利用
  • Queueファサードの fake メソッドで、キューに積まれたジョブをモック化できる
  • その後、キューにジョブが積まれたかを確認したり、それらが受け取るデータを検査すれば良い
  • アサーションは、fakeを呼び出したあと実行する

メールのFake

  • Mailファサードの fake メソッドを使うと、メールの送信を抑制できる
  • その後、ユーザーに送られるはずの mailables をアサーションしたり、受け取ったデータを検査する
<?php

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Mail::fake();

        // Perform order shipping...

        Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        });

        // Assert a message was sent to the given users...
        Mail::assertSentTo([$user], OrderShipped::class);

        // Assert a mailable was not sent...
        Mail::assertNotSent(AnotherMailable::class);
    }
}

NotificationのFake

  • Notificationのfakeも使い方は同様

Mocking - Laravel - The PHP Framework For Web Artisans

Facadeのモック

Facade(ファサード)は、Laravelのサービスを静的に呼び出せるようにしたもの。これをモックにします。例えば、Cacheファサードをモックに入れ替えてテストができます。

  • shouldReceive()メソッドを呼び出すと、Mockeryクラスのモックのインスタンスが返される
  • Laravelのservice containerにより管理されるので、そのままクラスを利用するよりもテストしやすい
<?php

class FooTest extends TestCase
{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $this->visit('/users')->see('value');
    }
}
  • Requestファサードをモックにすると、テストを実行する時にcall()やpost()メソッドのようなHTTPヘルパーメソッドもキャンセルされてしまうので、モックにするべきではない

macOS Sierra で MySQLを動かす

MySQL5.7系は、macOS Sierraでは5.7.15以降にしないと動かないようです。

Does macOS sierra support any version of mysql? | Apple Developer Forums

MySQL5.7.11から5.7.16にアップデートした時の手順です。

MySQLを停止

  • あらかじめ、MySQLを停止しておく(動いていないはずだが、念のため)
mysqladmin shutdown

バックアップ

サーバーが動いていないので mysqldump は使えません。とりあえず、データフォルダーをコピーしておきます。

sudo cp -R /usr/local/var/mysql ~/mysql5.7.11.backup

ダウンロード

  • MySQL :: MySQL Community Downloadsを開く
  • MySQL Community Server(GPL)の DOWNLOAD をクリック
  • 下にスクロールして、適切なプラットフォームを選択して、[DMG Archive]を[Download]する
  • ログインするか、[No thanks, just start my download.]をクリック
  • ダウンロードが完了するのを待つ

インストール

  • ダウンロードした .dmg ファイルをダブルクリックする
  • .pkg ファイルをダブルクリックする
  • Installerダイアログが表示されたら[Continue]>[Agree]>[Install]で進める
  • [Install for all users of this computer]を選択して[Continue]>[Install]
  • temporaryパスワードが画面に表示されるので、書き留めておく。インストール後、そのパスワードでログインしたのち、新しいパスワードを設定する
  • インストールが完了したら[Close]を押す

パスを設定

  • 以下で、設定を呼び出す
cd ~
vi .bashrc
  • export PATHに、 /usr/local/mysql/bin が含まれていなかったら追加する

アップグレード

  • mysqld_safeでサーバーを起動
sudo mysqld_safe --user=mysql --datadir=/usr/local/var/mysql
  • サーバーが起動して待機状態になる。新しいターミナルを開く
  • 以下でアップグレードを実行する
sudo mysql_upgrade -u root -p
  • 一時パスワードでアップグレードを実行する
  • 成功したら、以下で一旦シャットダウンしてから、再起動する
mysqladmin -u root -p shutdown
sudo mysqld_safe --user=mysql --datadir=/usr/local/var/mysql
  • 使えるターミナルで、以下を実行
mysql -u root -p
  • パスワードを設定する
SET PASSWORD = PASSWORD('some password')
exit

以上で、mysqld_safeでサーバーを起動すれば完了です。

データをデフォルトの場所に移動

毎回データフォルダーを指定するのが面倒なので、データをバックアップして、デフォルトの場所に読み込み直します。

  • 現在のデータをバックアップ
mysqldump -u root -p
  --add-drop-table --routines --events
  --all-databases --force > data-for-upgrade.sql
  • MySQLをデフォルトのデータフォルダーで再起動
mysqladmin -u root -p
sudo mysqld_safe
  • バックアップしたデータを使ってリストアして、データをアップグレード
mysql -u root -p --force < data-for-upgrade.sql
sudo mysql_upgrade -u root -p

以上で完了です。この後は、以下でMySQLを簡単に起動できます。

sudo mysqld_safe

シャットダウンは以下の通りです。

mysqladmin -u root -p shutdown

Laravel(5.3) SocialiteでTwitterのOAuthを試す

Laravelの公式パッケージに TwitterGitHubなどで認証を行える Socialite があります。Twitter にログインして、ユーザー情報(ユーザーID、ユーザー名、アバターなど)を表示してみます。

情報源:GitHub - laravel/socialite

前提

  • macOS Sierra
  • PHP5.6以降
  • MySQL 5.7.11
  • node、Laravel、Composerはインストール済み

プロジェクトを準備

以下の手順で、プロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-socialite-test
cd laravel-socialite-test
php artisan serve

以上で準備完了です。Webブラウザで http://localhost:8000 にアクセスして、Laravelと表示されれば成功です。

Twitterにアプリを登録

試しにTwitterのアカウントを利用してみます。それをやるにはTwitterに連携させるアプリを登録する必要があります。以下、登録手順です。

  • Twitterにログインする
  • APIを登録するには、携帯の情報が必要なので、登録していない場合は以下の手順で登録する
    • プロフィールから設定を選択
    • 左のメニューから[モバイル]を選択
    • 携帯の番号を入力して[続ける]をクリック
    • 携帯に届いた認証コードを入力する
  • Twitter Application Management を開く
  • [Create New App]を押す
  • 以下を入力
    • Name にアプリ名を入力。SocialiteTest など
    • Description にアプリの内容を入力。Laravelの練習 など
    • Website に、アプリのトップページのURLなどを入力
      • ローカルホストは登録できなさそうなので、HPを持っていればそのURLを。なければGitHub Pagesなどで作成してください
    • Callback URL は、テスト用に http://localhost:8000/auth/twitter/callback としておく
    • Developer Agreementを確認したら[Yes, I have read and agree・・・]にチェックを入れる
    • [Create your Twitter Application]を押す

以上で、登録情報が表示されます。[Keys and Access Tokens]タブに必要な情報が表示されますので、選択してページをそのまま開いておいてください。

Socialiteを組み込む

Socialiteの読み込み

ターミナルで以下を実行して Socialite を読み込みます。

composer require laravel/socialite

Laravelに設定を加える

次にLaravelに設定をします。

  • config/app.php を開く
  • 'providers' の定義に、以下を追加
        // Laravel Socialite
        Laravel\Socialite\SocialiteServiceProvider::class,
  • 'aliases' の定義に、以下を追加
        'Socialite' => Laravel\Socialite\Facades\Socialite::class,
  • app.php を上書き保存
  • config/services.php を開く
  • returnする配列に、 facebook, twitter, linkedin, google, github, bitbucket というキーを定義して、必要な設定を定義する。Twitter を利用する場合は以下の通り
    'twitter' => [
        'client_id' => '登録情報の[Consumer Key (API Key)]の右の文字列をここに貼り付ける',
        'client_secret' => '登録情報の[Consumer Secret (API Secret)]の右の文字列をここに貼り付ける',
        'redirect' => 'http://localhost:8000/auth/twitter/callback',
    ],

コントローラーの作成

OAuthプロバイダーには2つのルートが必要です。一つ目はOAuthを提供するページにリダイレクトするルート、二つ目は認証が完了した時にコールバックで呼ばれるルートです。Socialite ファサードを利用して、認証した情報にアクセスができます。

  • ターミナルで以下を実行して、認証のためのコントローラーを作成する
php artisan make:controller Auth/AuthController
  • app/Http/Auth/Controllers/AuthController.php を開く
  • 以下のように修正する
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;

use Socialite;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * ユーザーをTwitterの認証ページにリダイレクトする
     *
     * @return Response
     */
    public function redirectToProvider()
    {
        return Socialite::driver('twitter')->redirect();
    }

    /**
     * Twitterからユーザー情報を取得する
     *
     * @return Response
     */
    public function handleProviderCallback()
    {
        $user = Socialite::driver('twitter')->user();

        $response =
            "id: ".$user->id
            ."<br/>Nickname: ".$user->nickname
            ."<br/>name: ".$user->name
            ."<br/>Email: ".$user->email
            ."<br/>Avater: <img src='".$user->avatar."'>"

            . "<br/><br/>";

        // OAuth Two Providers
        $response .= print_r($user, true);

        return $response;
    }
}

上記のコントローラーをルートに追加します。

  • routes/web.php を開く
  • 以下のルートを追加する
Route::get('auth/twitter', 'Auth\AuthController@redirectToProvider');
Route::get('auth/twitter/callback', 'Auth\AuthController@handleProviderCallback');

上書き保存をしたら、app.php を書き換えたので、サービスを起動し直します。

  • サービスを動かしていたターミナルで [control]+[C]で停止
  • php artisan serve でサービスを再起動

http://localhost:8000/auth/twitter にアクセスします。ここまで間違えがなければ Twitter の認証ページが開きます。[連携アプリを認証]を押すと認証が完了して、 http://localhost:8000/auth/twitter/callback にリダイレクトされます。このルートで AuthController.php の handleProviderCallback メソッドが呼ばれて、 Twitter から取り出したユーザー情報が画面に表示されます。

テストが終了したら

テストで作成したアプリが不要になったら、以下の手順で削除してください。

その他のプロバイダー

facebook, twitter, linkedin, google, github, bitbucket のサービスについては、基本的には同様の方法でアクセスできると思います。

上記以外のサービスを使いたい場合は、以下から探してください。

Laravel(5.3) イベントを試す

Events - Laravel - The PHP Framework For Web Artisans

Laravelの公式ドキュメントのイベントの項目を参考に、動かしてみました。

概要

Laravelのイベントシステムを使うと、様々なイベントを簡単に処理できます。アプリで発生するイベントに対する処理をリスナーとして登録しておくと、イベントを発生させることで、システムが登録されたリスナーを呼び出してくれます。一般的には、Eventクラスは app/Events ディレクトリー内に置き、リスナーは app/Listeners ディレクトリー内に置きます。これらのディレクトリーは最初は存在しません。 Artisan コマンドでイベントやリスナーを生成した時に自動的に作成されます。

イベントにより、複数の処理を切り離して実装することができます。一つのイベントは複数のリスナーを持つことができ、それらはお互いに依存しません。例えば、注文されたものを発送するたびに、ユーザーにSlackの通知を送信する場合を考えます。注文処理にSlackの通知のコードを組み込むこともできますが、OrderShippedイベントを発生させて、それを受け取ったリスナーがSlackの通知処理を行った方がシンプルです。

プロジェクトを準備

ログやエラー表示を実際に動かして、色々と試してみます。以下の手順で、プロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-event-test
cd laravel-event-test
php artisan serve

以上で準備完了です。Webブラウザを起動して、 http://localhost:8000 にアクセスすると、インストールしたLaravelプロジェクトが表示されれば成功です。

イベントの登録とリスナー

app/Providers/EventServiceProvider.php の $listen プロパティーに、アプリのすべてのイベントとリスナーを登録します。キーにはイベント、値にリスナーを配列で指定します。初期状態では、 SomeEvent というイベントに対して、 EventListener が登録されています。app/EventsフォルダーもApp/Listenersフォルダーも初期状態では存在しないので動作はしません。

    protected $listen = [
        'App\Events\SomeEvent' => [
            'App\Listeners\EventListener',
        ],
    ];

イベントとリスナーを登録する

ここに、自前のイベントやリスナーを登録することができます。公式ドキュメントに従って OrderShipped イベントを作成してみましょう。

  • app/Providers/EventServiceProvider.php を開く
  • $listen プロパティーを以下のように修正
protected $listen = [
    'App\Events\OrderShipped' => [
        'App\Listeners\SendShipmentNotification',
    ],
];

イベントとリスナーの作成

自分でフォルダーやファイルを作成して、イベントやリスナーを作成することもできますが、 artisan の event:generate コマンドを呼び出せば、先ほど $listen に登録したイベントとリスナーのファイルを自動的に生成してくれます。ターミナルで、以下を実行してください。

php artisan event:generate

app/Events/OrderShipped.php と app/Listeners/SendShipmentNotification.php が生成されます。すでにファイルが作成済みの場合は何もしません。

イベントの定義

イベントクラスは、イベントに関する情報を保持するシンプルなデータコンテナです。先ほど作成した OrderShipped イベントに Eloquent ORM オブジェクトを持たせる例を以下に示します(公式ドキュメントのものは古いようで、useや継承などがうまく動作しませんでした)。

<?php

namespace App\Events;

use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderShipped
{
    use InteractsWithSockets, SerializesModels;

    public $user;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

イベントクラスには処理はなく、データを保持しているだけです。 SerializesModels トレイトを利用することで、イベントオブジェクトが Eloquent モデルを保持することができます。

引き続き、リスナーや User モデルの生成を行います。

リスナーの定義

イベントリスナーは、 handle メソッドでイベントのインスタンスを受け取ります。 event:generate コマンドでイベントを生成すれば、適切なイベントクラスを組み込んで、 handle メソッドにコメントと引数が定義されます。 handle メソッド内で、イベントに対応するために必要な様々な処理を行います。

ここでは、イベントを受け取った時にユーザー名をログに出力するようにします。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;

class SendShipmentNotification
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        // Access the order using $event->user...
        Log::info('Event Fired : ' . $event->user->name);
    }
}

リスナーの依存関係の解決と、イベントの伝播の停止

  • リスナーの依存関係は、Laravelの service container により、自動的に解決されます
  • 処理中のリスナーを終えたら、それ以降のイベント処理を停止したい場合があります(エラーが発生したなど)。そのような時は、 handle メソッドの戻り値を false にしてください。

データの準備

今回のサンプルを動かすには、User テーブルが必要なので生成します。

  • .env を開く
  • DB_ から始まるデータベースの設定を、各々の環境通りに設定する
  • データベースに接続して、必要なデータベースを作成。以下、MySQLの手順
sudo mysql.server start
mysql -u ユーザー名 -p
  • パスワードを入力して、ログインする
  • 以下でデータベースを作成
create database laravel_event_test;
exit
  • MySQLから出たら、ターミナルで以下を実行する
php artisan migrate

エラーが表示されたら、データベースの作成ミスか、 .env の設定のミスなので、設定を見直してください。

イベントの発動(Firing Events)

イベントを発動させるには、 event ヘルパー関数にイベントのインスタンスを渡します。event 関数はグローバルに定義されているのでどこでも使えます。イベントを発動させると、登録されたリスナーが次々に呼び出されます。

ルートを呼び出したところで、イベントを発動させてみましょう。

  • routes/web.php を開く
  • getメソッドを以下のように修正する
Route::get('/', function () {
    // テスト用のユーザーデータをファクトリーで作成
    $user = factory(App\User::class)->create();

    // イベントを生成
    event(new OrderShipped($user));

    // トップ画面を描画
    return view('welcome');
});

上書き保存をしたら、 http://localhost:8000/ をWebブラウザーで開いてください。Laravelと表示されれば成功です。 storage/logs/laravel.log を開いて一番下を見ると、リスナーの handle メソッドが書き出した以下のようなログが追加されています。

[20xx-xx-xx xx:xx:xx] local.INFO: Event Fired : Dr. Mervin Schaefer IV  

日付や名前は、実行するたびに異なります。

以上で、最低限のイベントやリスナーの登録と発動までできました。これ以降は、イベントをより便利に使うための情報です。

一つのクラスに複数のイベントを定義する(Event Subscribers)

Event Subscribers クラスを使うと、一つのクラスに複数のイベントを定義して、クラス内にイベントハンドラー(リスナー)を持たせることができます。 Subscriberクラスは、 subscribe メソッドを持ち、イベント dispatcher インスタンスが渡されます。渡された dispatcher インスタンスを使って、イベントのリスナーメソッドを呼び出すことができます。

公式ドキュメントでは、Subscriberは app/Listeners ディレクトリーに作成しているので、それに従って、2つのイベントハンドラーを持った UserEventSubscriber を作成してみます。

Subscriberを作成する

  • app/Listeners ディレクトリー内に UserEventSubscriber.php を作成する
  • 作成したファイルを開いて、以下のコードを入力する
<?php

namespace App\Listeners;

use Illuminate\Support\Facades\Log;

class UserEventSubscriber
{
    /**
     * ユーザーのログインイベントを処理するハンドラー
     */
    public function onUserLogin($event) {
        Log::info('ログイン : ' . $event->user->name);
    }

    /**
    * ユーザーのログアウトイベントを処理するハンドラー
     */
    public function onUserLogout($event) {
        Log::info('ログアウト : ' . $event->user->name);
    }

    /**
     * Subscriberに、リスナーを登録する
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'App\Events\UserLogin',
            'App\Listeners\UserEventSubscriber@onUserLogin'
        );

        $events->listen(
            'App\Events\UserLogout',
            'App\Listeners\UserEventSubscriber@onUserLogout'
        );
    }
}

対応するイベントを作成する

イベントの内容は先に作成した OrderShipped.php と同じなので、複製して、クラス名だけ変更して利用します。

  • app/Events/OrderShipped.php を同じフォルダーに複製する
  • ファイル名を UserLogin.php に変更する
  • UserLogin.php を開いて、クラス名を UserLogin に変更する
  • 同様に、 app/Events/OrderShipped.php を同じフォルダーに複製する
  • ファイル名を UserLogout.php に変更する
  • UserLogout.php を開いて、クラス名を UserLogout に変更する

Subscriber を登録する

Subscriber を登録します。

  • app/Providers/EventServiceProvider.php を開く
  • 以下のプロパティーをクラス内に追加する
     protected $subscribe = [
         'App\Listeners\UserEventSubscriber',
     ];

イベントを発動させるルートを作成する

  • routes/web.php を開く
  • 先頭の方に、以下の use を追加する
use App\Events\UserLogin;
use App\Events\UserLogout;
  • 以下の2つのルートを追加する
Route::get('/login', function () {
    // テスト用のユーザーデータをファクトリーで作成
    $user = factory(App\User::class)->create();

    // イベントを生成
    event(new UserLogin($user));

    // トップ画面を描画
    return 'ログインしました。';
});

Route::get('/logout', function () {
    // テスト用のユーザーデータをファクトリーで作成
    $user = factory(App\User::class)->create();

    // イベントを生成
    event(new UserLogout($user));

    // トップ画面を描画
    return 'ログアウトしました。';
});

以上ができたら、 http://localhost:8000/login をWebブラウザーで開いてください。「ログインしました。」と表示されたら、 storage/logs/laravel.log を開いて、以下のようなログが残っていることを確認してください(日付や名前は、毎回異なります)。

[2016-10-09 08:41:59] local.INFO: ログイン : Mrs. Marjorie Klein Jr.  

次に、 http://localhost:8000/logout をWebブラウザーで開いてください。「ログアウトしました。」と表示されたら、先ほどと同じログファイルに、ログアウトのログが書き込まれます。

リスナーがそれほど規模が大きくなく、機能をまとめておきたい場合は、Subscriber を利用すると良いでしょう。

キューされたイベントリスナー(Queued Event Listeners)

e-mailを送ったり、HTTPリクエストを作成するなど、時間のかかるようなリスナーを実装する場合は、 キュー を使うリスナーが便利です。これを使う場合は、事前にキューの設定をして、 sync ドライバー以外の場合は、キューのdaemon を開始する必要があります。キューの記事はこちら(Laravel(5.3) のキューを試す)

ローカルのテスト用キューである sync ドライバーでは同期処理になるので、本来のキューの目的である非同期処理は行われません。

リスナーをキューに登録させる

リスナーをキューに登録するには、 ShouldQueue インターフェースをリスナークラスに実装( implements )します。 event:generate を使って作成したリスナーには、あらかじめ ShouldQueue のネームスペースが定義されているので、すぐに利用することができます。

キューへのアクセス

リスナーをキューに登録した後、それが実行される前に削除や解放をしたい場合は、 Illuminate\Queue\InteractsWithQueue トレイトを利用します。このトレイトは、Artisanでqueue:generateした場合、自動的に設定されています。

hander メソッド内で、 $this->release(30); などのように、キューへの指示が可能になります。

詳細は、キューのドキュメントをご確認ください。 →
Queues - Laravel - The PHP Framework For Web Artisans

Laravel(5.3) のキューを試す

Laravel公式ドキュメント:Queues - Laravel - The PHP Framework For Web Artisans
和訳ページ:キュー 5.2 Laravel

公式ドキュメントを参考に、Laravelのキューを動かしてみました。ドライバーは、まずは sync で動かして、その後に database を使いました。

はじめに

この項目は、公式ドキュメントからの意訳です。

Laravelのキュー(queue)は、 BeanstalkやAmazon SQS, Redis, RDBなどの様々なキューを統一したAPIで扱えるようにするものです。メールを送信するなどの時間がかかる処理を、その場で完了するのではなく、後回しにすることができます。これにより、アプリケーションのWebリクエストがすぐにレスポンスを返せるようになります。

Queueの設定は config/queue.php で定義します。このファイルを見れば、Laravelが利用できるキューの設定を見ることができます。ローカルユーザーは、 synchronous ドライバーが使えます。 キュードライバーが null の場合は、キューされたジョブは破棄されます。

前提

  • macOS Sierra
  • PHP5.6以降
  • MySQL 5.7.11
  • node、Laravel、Composerはインストール済み

プロジェクトを準備

以下の手順で、テスト用のプロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-queue-test
cd laravel-queue-test
php artisan serve

以上で準備完了です。Webブラウザを起動して、 http://localhost:8000 にアクセスすると、インストールしたLaravelプロジェクトが表示されます。

デフォルトでは、キューはローカルで同期的に動作する sync ドライバーが設定されています。本来のキューのメリットは得られませんが、ローカル環境ですぐに動作を確認することはできるので、まずは動かしてみましょう。

ジョブの作成

デフォルトでは、キューに積むことができるジョブは、 app/Jobs ディレクトリー内に置きます。最初はこのディレクトリーは存在せず、 artisan の make:job コマンドでジョブを作成する時に自動的に作成されます。

試しにログを出力するジョブとして、 LogExample を作成するには、ターミナルで以下のようにします。

php artisan make:job LogExample

app/Jobs/LogExample.php が作成されるので、エディターで開いてみましょう。作成されたジョブのクラスは、あらかじめ Illuminate\Contracts\Queue\ShouldQueue インターフェースが実装(implements)されます。このインターフェースを実装すると、Laravelは処理を同期的に実行せず、キューにジョブを積むようになります。

クラスの構造

Jobクラスはとてもシンプルです。通常は コンストラクター(__construct)と handle メソッドだけを持ちます。 handle メソッドは、キューによってジョブが実行される時に呼ばれます。

ログにメッセージを書き出すジョブを試しに作ります。

  • app/Jobs/LogExample.php を開く
  • Logを利用するために以下の use 文を最初の方に追加
use Illuminate\Support\Facades\Log;
  • 処理を handle メソッドに追加
    public function handle()
    {
        Log::info('ジョブテスト');
    }

ジョブを実行する

ジョブクラスを作成したら、 dispatch ヘルパーを使って呼び出します。dispatch ヘルパーには、ジョブのインスタンスを渡します。

  • routes/web.php を開く
  • ジョブを使うための use をファイルの上の方に追加する
use App\Jobs\LogExample;
  • getのルートに dispatch ヘルパーを追加して、以下のようにする
Route::get('/', function () {
    dispatch(new LogExample);

    return 'ログへの出力ジョブを実行しました。';
});

http://localhost:8000/ をWebブラウザーで開くと、「ログへの出力ジョブを実行しました。」というメッセージが画面に表示されて、ジョブが実行されます。 storage/logs/laravel.log を開いて確認してください。「ジョブテスト」というログが書き込まれています。

遅延実行

ジョブの実行を遅らせて実行することができます。ジョブのインスタンスの delay メソッドを呼びます。このメソッドは、 Illuminate\Bus\Queueable トレイトによって提供されます。 artisan でジョブを作成していれば、自動的に宣言されます。

ジョブの実行を5秒遅らせてみます。

  • routes/web.php を開く
  • getメソッド内のジョブの呼び出し部分を以下のように修正する
Route::get('/', function () {
    $job = (new LogExample)->delay(5);
    dispatch($job);

    return 'ログへの出力ジョブを実行しました。';
});

5秒経過してからログが書き込まれることを想定していましたが、すぐに書き込まれてしまいました。これは sync ドライバーはローカル上でキューの動作を確認するためのもので、非同期処理に対応していないからです。

データベースのキュードライバーに変更して、改めて動作を確認しましょう。

キュードライバーをデータベースに切り替える

以下、MySQLの例です。

  • ターミナルから、以下を実行して、MySQLを起動して、ログインする
sudo mysql.server start
mysql -u ユーザー名 -p
  • パスワードを入力してログインする
  • テスト実行用のデータベースを作成する
create database laravel-queue-test;
exit
  • .env をテキストエディターなどで開く
  • DB_から始まるデータベースの設定を、環境に合わせて設定
  • QUEUE_DRIVERの設定をデータベースに変更
QUEUE_DRIVER=database
  • .env を上書き保存する
  • ターミナルで以下を実行して、キュー用のテーブルを作成する
php artisan queue:table
php artisan migrate
  • 設定を変更したので、反映させるためにLaravelのサービスを再起動する
    • Laravelのサービスを動かしているターミナルで [control]+[C] でサービスを停止
    • php artisan serve でLaravelサービスを起動
  • キューを実行するために、以下をターミナルで実行
php artisan queue:work

改めて、 storage/logs/laravel.log を開いてから、 http://localhost:8000/ を読み込んでください。今度は、5秒ほど経過してから、ログが書き込まれます。

Amazon SQSでは、遅延実行は 15分までの制限があります。


以上で、Laravelでキューを使うことができるようになりました。他のドライバーの使い方や、より発展的な使い方は、公式サイトなどを参照してください。

      • -

ConnectionとQueue

config/queue.php を見ると、様々なバックエンドサービスに接続するための connections の設定があります。それらの設定によって、ドライバーや接続情報、キューの種類などを設定できます。異なるスタックや優先順位が違うキューを複数、用意することができます。

queue.php の connections 配列を見ると 'queue' 属性が設定されているものがあります。キューに積む時に、何も指定しない場合は 'default' のキューに送られます。明示的に指定したい場合は、 dispatchメソッドをチェーンさせて、 onQueue メソッドを使います。

// デフォルトのキューに送られます
dispatch(new Job);

// "emails"キューに送られます
dispatch(new Job)->onQueue('emails');

複数のキューにジョブを送らないアプリケーションでは、一つのシンプルなキューの方が良いですが、複数のキューにジョブを送ることができれば、ジョブに優先順位をつけたり、ジョブの処理をセグメント化することができるので使いやすくなります。Laravelのキューワーカーは、優先度によって処理を特定することができます。例えば、ジョブを high キューに送信するならば、そのワーカーに高い処理の優先度を与えると良いでしょう。

php artisan queue:work --queue=high, default

ドライバーの前提条件

Database

キュードライバーに database を選択した場合、ジョブをデータベースのテーブルに記録するので、そのためのテーブルが必要です。データベースへの接続設定を行い、Laravelアプリケーションのためのデータベースを作成した上で、以下でテーブルを作成します。

php artisan queue:table
php artisan migrate

その他のドライバーの前提条件

以下のそれぞれのドライバーに必要なものです。


ひとまず、以上。

Laravel(5.3) ErrorとLoggingのチュートリアル

Errors & Logging - Laravel - The PHP Framework For Web Artisans

Laravelの公式ドキュメントを元に、エラーとログの動作を確認します。

Laravelのエラーとログ処理

Laravelには、あらかじめエラーや例外を処理するための設定がなされています。 App\Exceptions\Handler クラスには、作成するアプリから呼ばれるすべての例外があり、ログをファイルに保存したり、ブラウザに例外内容を表示したりします。

ログの記録のために、Laravelは Monolog ライブラリを利用しています。単一のログファイル、ローテーションするログファイル、システムログにエラー情報を書き込むなどのログの記録方法が用意されていて、設定で選択できます。

(Errors & Logging - Laravel - The PHP Framework For Web Artisans より)

前提

  • macOS Sierra
  • PHP5.6以降
  • MySQL 5.7.11
  • node、Laravel、Composerはインストール済み

プロジェクトを準備

ログやエラー表示を実際に動かして、色々と試してみます。以下の手順で、プロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-error-test
cd laravel-error-test
php artisan serve

以上で準備完了です。Webブラウザを起動して、 http://localhost:8000 にアクセスすると、インストールしたLaravelプロジェクトが表示されれば成功です。

エラー表示とログを確認する

エラーは、サーバーにアクセスした時のHTTPエラーコードによって例外が発生した時、あるいは、アプリ内から abort() メソッドを呼び出した時に発生します。この時、設定したレベルに応じてログファイルへの情報書き出しと、画面にエラー内容の表示の2種類の出力が行われます。

また、ログファイルには、 Log ファサードを使って、情報を書き込むことができます。

これらを発生させて、画面に表示されるものや、ログファイルを覗いてみましょう。

例外を発生させてみる

http://localhost:8000/test にアクセスしてみましょう。このルートは定義していないので、ページが見つからないというエラー(Sorry, the page you are looking for could not be found.)が画面に表示されます。

ログファイルはどうなっているでしょう。ログファイルは、 storage/logs フォルダー内に生成されるはずですが見当たりません。これは、HTTP関連のエラーは、ログに出力されない設定になっているからです。変更してみましょう。

  • app/Exceptions/Handler.php を開く
  • $dontReport 配列を探して、以下のように HttpException クラスの行をコメントアウトする
    protected $dontReport = [
        \Illuminate\Auth\AuthenticationException::class,
        \Illuminate\Auth\Access\AuthorizationException::class,
//        \Symfony\Component\HttpKernel\Exception\HttpException::class,
        \Illuminate\Database\Eloquent\ModelNotFoundException::class,
        \Illuminate\Session\TokenMismatchException::class,
        \Illuminate\Validation\ValidationException::class,
    ];
  • 上書き保存する

再度 http://localhost:8000/test にアクセスするか、リロードしてください。今度は storage/logs/laravel.log が生成されます。開いて内容を確認してみましょう。画面に表示されたのと同様のことが書き込まれています。

abort()メソッドで、自分で例外を発生させる

abort()ヘルパーメソッドを利用することで、自分で例外を発生させることができます。HTTPステータスコードを引数に渡します。試してみましょう。

  • routes/web.php を開く
  • getの処理を以下のようにabortを追加
Route::get('/', function () {
    abort(500);

    return view('welcome');
});
  • 上書き保存する

http://localhost:8000 を開くと、ステータスコード500の時のエラー(Whoops, looks like something went wrong.)が発生します。また storage/logs/laravel.log に、ログが追加されます。

abort()の第2引数で文字列を渡すと、その文字列をエラーのテキストに含めることができます。 routes/web.php のgetルートを以下に書き換えます。

Route::get('/', function () {
    abort(500, '例外テスト');

    return view('welcome');
});
  • 上書き保存

以上で、 http://localhost:8000 を再読み込みすると、エラーメッセージに「例外テスト」という文字列が追加されます。

Logを出力する

Logファサードを利用すれば、アプリからログファイルに情報を出力することができます。 routes/web.php を開いて、 getメソッドの処理を以下のように修正してください。

Route::get('/', function () {
    Log::info('ログ出力');

    return view('welcome');
});

上書き保存をしたら http://localhost:8000 を再読み込みします。エラーにはならず、Laravelのページが表示されます。 storage/logs/laravel.log を開いて最後の行を見ると、「ログ出力」というメッセージが追加されています。アプリの動作を妨げないで情報だけを残したい場合などにこの機能を使います。

Log::info()のinfoの部分が出力するレベルを決めています。使えるメソッドは以下の通りです。それぞれの意味については後述します。

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

エラーの画面表示と、ログへの出力を確認しました。まとめると以下の通りです。

  • 例外が発生したら、ブラウザにエラーが表示された上で、 Handler.php の $dontReport 配列にリストアップされていない例外は、ログファイルに情報が書き込まれる
  • abort()メソッドを使うと、自前でHTTPステータスコードを指定して例外を発生させることができる
  • Log::info()などのLogファサードを利用することで、自前でログファイルに情報を書き込むことができる

HTTPメッセージをログに残すと煩わしいので、 Handler.php の $dontReport でコメントアウトした行を元に戻しておきましょう。

エラーの設定

設定の場所

config/app.php ファイルに、debub の項目があり、ユーザーにどの程度の情報を表示するかを設定します。通常は .env ファイルの APP_DEBUG に定義すると良いでしょう。

ローカル環境では、 APP_DEBUG は true に設定しておきます。公開環境では、重要な設定がエンドユーザーに見られるのは危険なので false にします。デバッグをオフにしてみましょう。

  • .env を開く
  • 「APP_DEBUG=true;」の部分を、以下のように書き換える
APP_DEBUG=false
  • 上書き保存をする

http://localhost:8000/test にアクセスしてください。先ほどと同様にエラーが画面に表示されます。これは、 .env は、アプリの起動時に読み込まれるため、アプリを再起動する必要があるからです。ターミナルで[control]+[C]を押してサービスを停止したら、以下でサービスを再起動します。

php artisan serve

http://localhost:8000/test をリロードすると、今度はエラーの詳細が表示されなくなります。動作を確認したら、[APP_DEBUG]をtrueに戻して、サービスを再起動して元に戻しておいてください。

ログファイルの種類

ログファイルは、 single, daily, syslog, errorlog の4種類の保存方法が用意されています。 .env ファイルの APP_LOG に、利用したい保存方法の文字列を設定します。デフォルトは single 、つまり1つのログファイルに出力するようになっています。

daily にした場合、デフォルトでは5日分のデータが保存されます。これを変更したい場合は、 config/app.php に、 'log_max_files' => 30 のように追加します。

.env の APP_LOG_LEVEL で、ログに残すエラーのレベルを設定できます。先ほど、Log::info()でログを書き出しましたが、info()もエラーレベルの一つです。レベルには以下があり、デフォルトは debug になっています。

  • debug, info, notice, warning, error, critical, alert, emergency

設定したレベルを含む、上記の右側の属性の情報がログに記録されます。 debug では、すべてのメッセージが出力されます。error では、 error, critical, alert, emergency のみが記録されます。

エラーレベルによって、出力が制御されることを確認してみましょう。

  • .env を開く
  • 以下のようにして、 APP_LOG_LEVEL を error にする
APP_LOG_LEVEL=error
  • .env を上書き保存
  • ターミナルで[control]+[C]で、サービスを停止
  • php artisan serve でサービスを再起動する

http://localhost:8000 にアクセスしたら、 storage/logs/laravel.log を確認してください。「ログ出力」という文字は出力されていません。設定を元に戻して、同じ操作をしたら、「ログ出力」が追加されます。

連想配列をログに出力

ログに、連想配列に保存されているデータを渡して記録することができます。 routes/web.php を開いて、 get()メソッドの処理を以下のように修正します。

Route::get('/', function () {
    Log::error('ログ出力', ['id' => 1, 'name' => 'MyName']);

    return view('welcome');
});

上書き保存をしたら、 http://localhost:8000 を開き直します。画面が表示されたら、ログファイル( storage/logs/laravel.log )を開きます。最後に配列の内容が追加されたログが書き込まれます。

[20xx-xx-xx xx:xx:xx] local.ERROR: ログ出力 {"id":1,"name":"MyName"} 

独自のHTTPエラーページ

Laravelは、HTTPステータスコードに応じた独自のエラーページを簡単に作成することができます。例えば、404エラーに対するエラーであれば、 resources/views/errors/404.blade.php を生成して、そこに表示内容を記述します。このディレクトリー内のviewのファイル名は、HTTPエラーコードに対応します。HttpExceptionは、 abort ヘルパーから呼ばれて、 $exception の変数として viewに渡されます。

実際に404エラーのページを作成してみましょう。まずは、 http://localhost:8000/test にアクセスして、現状を確認します。「Sorry, the page you are looking for could not be found.」というタイトルが表示されて、その下にエラーの詳細が表示されています。これを、「ページが見つかりませんでした。」という日本語のメッセージに変更します。

  • resources/views/errors/503.blade.php を複製して、 404.blade.php というファイル名にする
  • 404.blade.php を開いて、以下の通り修正する
    • 3行目
        <title>Page Not Found.</title>
    • 43行目
                <div class="title">ページが見つかりませんでした。</div>
  • 上書き保存

以上出来たら、 http://localhost:8000/test を開き直すか、リロードします。画面いっぱいに「ページが見つかりませんでした。」に変わります。

404.blade.php を削除すると、元に戻ります。

その他

ここまでで、最低限のエラーとログの使い方は紹介しました。これ以降は、公式ドキュメントに掲載されていて、ここまでで紹介していない機能について記載します。

例外ハンドラー

report メソッド

すべての例外ハンドラーは App\Exceptions\Handler クラスに定義されています。このクラスは、 report と render メソッドを持ちます。

report メソッドは、例外をログに残したり、BugsnagやSentryのような外部サービスへの記録のコードを追加することができます。デフォルトでは、ハンドラーのベースクラスに例外をそのまま渡して、規定のルールに基づいてログファイルに情報を追加します。

独自に作成した例外など、個別の例外処理を行いたい場合は、PHPのinstanceof を利用して、以下のようにします。

/**
 * Report or log an exception.
 *
 * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
 *
 * @param  \Exception  $exception
 * @return void
 */
public function report(Exception $exception)
{
    if ($exception instanceof CustomException) {
        // ここに、独自の例外用の処理を書く
    }

    return parent::report($exception);
}
render メソッド

renderメソッドは、Webブラウザーに描画できるように例外の内容を HTTP response に変換して返します。デフォルトでは、例外はレスポンスを生成したベースクラスにそのまま渡しているだけです。

reportと同様に、独自の例外を PHP の instanceof でクラスタイプを判別して、独自のエラー画面を返すことができます。

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

    return parent::render($request, $exception);
}

Monologのカスタム設定

Monologの設定を完全にコントロールしたい場合は、アプリの configureMonologUsing メソッドを利用します。 bootstrap/app.php ファイルの、 $app をreturnする直前に、このメソッドを呼びます。

$app->configureMonologUsing(function($monolog) {
  $monolog->pushHandler(...);
});

return $app;

Monologインスタンスへのアクセス

Monologは、ログに利用できる多くのハンドラーを持っています。もしそれらが必要な場合は、以下でMonologのインスタンスにアクセスできます。

$monolog = Log::getMonolog();


以上です。

Arduinoの電源関連メモ(追記あり)

(解決策を3回の移動平均に変更しました。2016/10/6)

今回の誤動作は、サーボモーターとは無関係でした。原因は、電源電圧の不安定さ。常に5Vが流れてくる訳ではなく、ちょくちょく、4.5Vとかのことがあります。センサーに利用しているフォトトランジスタは、入力電圧によって戻ってくる値が変わるので、この電圧の変化によって不安定な結果を返していました。

Arduinoで、電圧の変化とフォトトランジスタの値の変化を確認するスケッチを作成して確認したところ、次のような状況でした。

  • 5Vを下回る少し前に、フォトトランジスタの値が急激に下がる
  • 5Vを下回ったタイミングで、フォトトランジスタの値が大幅に大きくなる
  • 5Vに戻ったあと、やや不安定だが、ほぼ平時に戻る

以上から、移動平均を行うことで、極端な値の変化を抑制することができそうでした。そこで、3回の移動平均を求めて、その値で回転の判定を行ったところ、2日間連続で動かしても、誤動作は発生しませんでした。

3個分のint型の配列を用意して、インデックスを表すint型の変数も用意します。インデックスを変化させて、一番古い値の場所に、新しいセンサー値を記録して、配列の平均を求めれば、移動平均が求まります。

    • -

モーターの電源は他からとった方が安全なので、以下はやった方がより良いので、情報は残しておきます。

サーボモーター(SG90)をArduinoの5Vに接続すると、時々電力不足になるようで、センサーが誤動作することがあります。プログラム側でセンサーが0の値や突発的な変化を無視することで殆ど誤動作はなくせましたが、完全ではありませんでした。より安定して動かすには、サーボモーターの電源はArduino以外から取った方がよさそうです。

SG90用には以下のものを検討しています。動作電圧が4.8-5.0Vなので、USBの電源がちょうどよいです。普通の5Vのアダプターでもいいのですが、いざという時、USBならPCからも電源もらえるかもなので。

以上2つで、ブレッドボードに5Vの電源を用意して、サーボモーターを接続すればよさそうです。


Arduinoは消費電力が少ないので、電池駆動でも長持ちしそうな気がします。充電池2つを以下で昇圧すれば動きそうです。

ちなみに、今はArduinoの電源用に以下のACアダプターを使っています。


ACアダプターの電源を、Arduino用とモーター用で分ければ1つのACアダプターで済みそうな気もしていますが、今のところその知識がないので、一先ず上記で一旦やっつけようと思います。