Table of Contents
1. 初めに
購入いただきありがとうございます。
1.1 当書について
Laravelの機能の中でも初心者には理解しづらく、一般に関心も薄いArtisanコマンドとイベントシステム、キューの3コンポーネントについて、チュートリアルを通じて学ぶ電子書籍です。チュートリアルではシンプルなWebサービスの「ハブサイト」を作成します。
ハブサイトとは、IFTTT(イフト)やZapier(ザピエル)のWebサービスのように、別々のWebサイトサービスを結び付けるサービスです。これらのサービスはアプリからの投稿や、定期的にWebサービスをチェックした結果に基づいて、別のサービスに情報を送ります。様々なWebサービスを結びつけ、便利に活用できるのです。起動側のサービスと受け手側のサービスを1対1で結びつけます。
両サイトはユーザー操作が簡単で、利用してみれば使い方は習得できます。ハブサービスがどのようなものであるか概念がつかめない方は、一度使用してみてください。
IFTTTは無料ですが、対応サービスの中には十分に機能を利用していなかったり、古いAPIに対応したままで現状のサービスではうまく動作しなかったりするものがあります。
Zapierは無料で使用できるタスク(2つのサービスを結びつけたもの)が5つしかなかったり、チェック間隔が長かったり、一日当りの実行数の制限が実用にはきつかったりします。ただし現状のWebサービスにきちんと対応しているため有料版を利用すると便利です。
Webサービスはより便利することで利用者を獲得するため、APIを利用し他サービスと連携できるように進化しています。たとえばカレンダーサービスのSunrizeカレンダーは、Google、FaceBook、GitHub、Wunderlistなど多くの「タスク」や「スケジュール」を扱うサービスと連携できます。しかし存在している多くのWebサービスと連携するのは大変です。全てのサービスに対し様々な連携を提供することは無理でしょう。
こうした状況でハブサイトは、さまざまなサービスを「連携」することに特化し運営されています。ユーザーは起点となるサービスと情報の受け手のサービスをペアで登録します。定期的に起点のサービスを調べ、登録した変化が発生したら、受け手側のサービスへ情報を転送します。
最近では、Yahoo Japanからハードとも連携できるmyThingsというサービスもランチされました。本書でも紹介しているチャットサービスのSlackは、日本時間の2015年12月16日に外部との連携サービスをSlackディレクトリーとして、Webに公開し、その拡張性を広く知らしめようとしています。こうした「連携」を提供するサービスはこれからも段々増えていくでしょう。
今回のチュートリアルは、Webページを作成しません。その代わりにLaravelの機能を利用し、最終的に次のような形態でHubサイトを実現します。
- Webサービスをチェックする機能をコマンドで実装する。
- 上記チェックコマンドをスケジューラーで定期的に実行する。
- チェック時、状況をイベントで発行する。
- イベントとそれを処理するリスナーの組み合わせをLaravelのメカニズムで指定する。
- リスナーにより、特定のWebサービスに通知する。
- イベントリスナーをキュー経由で実行してみる。
- Jobクラスを使用しチェック機能をキュー経由で実行してみる。
Hubサイトとして機能や設計的なベストではありませんが、Laravelの裏側に用意された機能と仕組みを学ぶためには面白い内容にしています。
これにより、以下の機能に触れてみましょう。
- Artisan CLI
- Artisanコマンドスケジューラー
- イベント(ただし、ブロードキャストは扱いません)
- キュー
今のところ、用語がわからなくても安心してください。この後に学習しましょう。
1.2 必要なスキルと要件
この書籍を読まれる方は、多少なりともLaravelに慣れたレベルの方を想定しています。
- PHPは一通りOK
- Laravelで作ったWebサービスを自力で実働環境へデプロイし、実行できる
「公式ドキュメントを読み、Web上で適切な情報を見つけ、わからない点はコアのPHPクラスを読んで解決できるレベルの方」には、本書は必要ありません。そこまでのレベルには行き着いていないが、Artisanコマンドやスケジューラー、イベントシステム、キューをどのように活用するのか具体的なイメージを掴みたい学習者向けの内容です。
Hubサイトですから、実際に様々なサービスと連携します。動作させるためのVPSなどは用意しなくても、インターネットに接続できる開発環境があれば、内容を試すことが可能です。もちろん、以下のような一般的なサーバー上で実行することも可能です。
- サクラやカゴヤ、z.com、AWS、Linode、DigitalOceanなどのVPSやクラウドサービス
- もしくはLaravelの動作要件を満たし、毎分のcron設定が可能な共有サーバー
- もしくはインターネットへ公開しているオンプレミスのWebサーバー
サーバーとして動作させるOSは、Linuxディストリビューションの”Ubuntu”を想定しています。その理由は公式Vagrant BoxであるHomesteadでも利用されているからです。しかし他のLinuxディストリビューションでも、本書が取り扱う範囲では大差はないかと思います。Unix系の場合は使用するコマンドやオプションが多少異なることもあります。Windowsサーバーでは、大幅に異なるでしょう。
1.3 コードについて
掲載したコードはGithubから入手可能です。通常の著作物の一部として公開しています。
書籍中のコード中の長い行はLeanpubの電子書籍生成システムにより、複数行へ分割されます。分割された行の最後へ’\‘が付加されます。(コード以外の部分で英単語が分割される場合は、行末がハイフン(-)になります。)
コードのフォーマットに特別な意図はありません。チュートリアルを実行する場合は、お好きなようにリフォーマットしてください。
テストの実行は本書の範囲外です。
書籍上で「何をやっているか」が一目で追いやすいように、原則メソッド内にロジックをだらだらと書いています。コードをきれいにまとめ上げてはいません。(コードをどのように書くべきか、構成すべきかは、個人やチームにより差がある部分です。コーディング規則で縛って「…すべき!…すべき!」といじめるつもりはありません。)
また、知識を吸収する学習段階で「べきべき」を持ちだすと、抵抗感により習得が遅れるものです。この書籍の内容を実用にするのであれば、最終的にぜひ皆さんの「…べき」に従いリファクタリングしてください。私も本書を書く気にさせた元々のシステムをこの本のバージョンで置き換えるつもりですが、その際に私の気に入るように手を加えるつもりです。(たとえば、外部Composerパッケージを使っている箇所はServiceにしていますが、本来はインターフェイスを導入し、交換性を上げるべきでしょう。しかし、インターフェイスによる分離や複雑なデザインパターンの導入は、コードの読みやすさを大きく損ねてしまいます。それは本書の目的とそぐいません。実用としてシステムを構築する場合は、外部パッケージはいつメンテが放棄されるか分かりませんので、交換性を備えておくのは賢い考えです。)
1.4 謝辞
図中の図形デザインは、Freepikにより作成されたものです。
All icons in this book are designed by Freepik.
1.5 改訂版について
Laravel5.0のリリースの後、長期メンテナンス版(3年間)のバージョン5.1LTSが発表されました。
メジャーバージョンは同じで、内部的な動作は5.0と大差ないのですが、5.1LTSのドキュメントで説明されなくなった5.0の機能もあります。そうした多くの機能はバージョン5.1LTSでも利用できます。
LTSリリースに合わせドキュメントも大幅に書き直されました。用語と概念はわかりやすいように整理されました。ドキュメントで紹介されている基本的な手法や用語が変更になりました。つまり5.0より概念はわかりやすく、かつ機能は使いやすくなっています。
そして何しろLTSです。長期サポート版です。バグフィックスは2年間、セキュリティー脆弱性の修正は3年間提供されます。安心して利用できます。
そこで、今回は5.1LTSに合わせ内容を改定しました。前バージョンの本書はzipファイルで提供します。
2. Laravelの準備
最初に開発環境へ真新しくLaravelをインストールしてください。Webサーバーの設定(ドキュメントルートをpublicフォルダーへ設定…なんたらかんたら)は当面不要です。
続いて、Laravelの準備を行いましょう。しばらくターミナル(端末、Windowsであればコマンドプロンプトやパワーシェルなどのコンソールプログラム)での作業になります。
コンパイル済みクラスファイルの削除
プロジェクトを実働環境で動かしているなら、Laravelが生成するコンパイル済みクラスファイルは動作スピードアップに役立ちます。しかし、開発環境ではエラーがどこで発生しているのかを追いかける邪魔になりますので、削除しておきましょう。以下のコマンドを実行してください。
日本語化
Laravelでは設定ファイルのコメントもドキュメントと考えています。コメントは英語で書かれています。しかし英語が苦手な方もいらっしゃるので翻訳コマンドを用意してあります。日本語のja言語ファイルも同時に作成します。
コメントが全部英語でも平気な方は、この手順をスキップしてください。
GitHubのlaravel-ja/comja5リポジトリーで公開しています。comja5コマンドを提供しています。このコマンドはインストール済みのLaravelに含まれるファイル中の、英文コメントなどを日本語へ文字列置換します。ですから、Laravelプロジェクトを新たにインストールした直後に実行してください。開発が進んでから翻訳実行すると、開発した部分に含まれる英文が意図せず日本語へ置き換えられる可能性があります。
comja5コマンドはプロジェクトごとに導入することもできますが、composerのグローバルコマンドとしてインストールすると、使い勝手が良いでしょう。
インストールが終わったら、以下のディレクトリーへ実行パスを設定してください。
これでcomja5コマンドが実行できるようになりました。Laravelのプロジェクトディレクトリーで以下のコマンドを実行すれば、コメントの翻訳し、日本語の言語ファイルを生成します。
生成される日本語はUTF8、行末はUnix/Linux形式です。Windowsでも大抵のエディターでは対応しているので通常問題はありません。
.envファイル
機密性が高い認証情報や、動作環境により設定ファイル中の値を変更したいものは、.envファイルに記述します。もし.envファイルが存在していなければ、.env.exampleファイルをコピーしてください。本書で提供するコードのZIPファイルに含まれている.env.exampleにはコメントを含めています。それを.envにコピーすると変更箇所がわかりやすくなります。
続いて以下を参考にし、ファイルを編集します。
APP_KEYにランダムな文字列が設定されておらず、代わりに”SomeRandomString”という文字列が設定されていれば、一度エディターを終了させ、次のコマンドで設定してください。
メールを送信できるように、SMTP関連の設定を行っておきましょう。最初の通知機能はメール送信の実装です。なにせメールも重要な「Webサービス」です。後ほど連携に利用します。
例えばGmailを利用する場合は、以下のようになります。
データベース設定
今回、システムの情報を保存する目的には直接データベースを使用しません。しかし、Laravel5.0から新しく導入されたデータベースによるキューを後ほど試してみます。そのためデータベースの準備が必要です。.envファイルやconfig/database.php設定ファイルを変更し、必要な接続情報などを指定してください。
データベースエンジンにSQLiteを使用する場合は、database.phpに指定したファイルを予め作成しておく必要があります。デフォルトのまま使用するのであれば、database/database.sqliteです。サイズは0バイト、つまり空のファイルにしておきます。touchコマンドで簡単に作成できます。
メール設定
メールの設定ファイルも変更しておきましょう。config/mail.phpをエディターで開きます。
ほとんどの設定は.envの編集で済ませました。変更する必要があるのは一行だけです。メールアドレスのバリデーションが行われるため、logドライバーを使用する場合も、メールアドレスとして正しい値を指定してください。
メール送信ができるか、テストしておきましょう。app/Http/routes.phpをエディターで開きます。ルートURLに対する定義、Route::get('/', 'WelcomeController@index');
の一行を次のように変更します。
toメソッドには、実際の送信先メールアドレスを指定してください。
php artisan serve
でPHPサーバーを起動し、’localhost:8000’へブラウザからアクセスし、メールが送信されるか確認しましょう。送信できない場合は、設定値を見直してください。
Mail::row()メソッドはビューを使用せず、文字列をそのままメールの本文として送信します。
アプリケーションの設定
アプリケーションの設定はconfig/app.phpファイルで行います。以下の2項目を設定してください。(記述していない項目は変更しません。)
Laravelがコアでも使用し、本書でも活用している日付時間操作ライブラリーのCarbonでも、新しいインスタンスを生成した場合のデフォルトタイムゾーンはここで指定した’timezone’設定値になります。Laravelが初期処理で、PHPの日付時間関数のデフォルトタイムゾーンをこの設定値で指定するためです。
3. リマインダー
いよいよ、Hubサイトの作成です。最初はリマインダー機能です。登録しておいた時間にメッセージを送ってくれます。すでに、Reminderイベントも作成していますしね。
今回、起動側は他のWebサービスを利用してイベントを発行しません。Laravel自身のAritsanスケジューラーの時間起動機能をそのまま利用しましょう。
送信先は一般的なサービスです。メールで自分のアドレスに通知します。他の人のアドレスに送っちゃ嫌がらせメールです。忘れちゃいけません、メールも立派なWebサービスですよ。面倒な送信プロトコルはLaravelに任せられますし、なにせ私達全員馴染み深いシステムですからね。最初に扱うには、ぴったりな主題です。
3.1 reminderコマンド
以降に作成するコマンドは、起動されると特定のWebサービスにアクセスし、新しいメールが来ているかとか、特定の情報があるかとか、通知すべきものがあるのかチェックします。
今回リマインダーに使用するreminderコマンドは、このようなチェックは一切せず、fire:reminderと同じようにイベントを通知するだけです。指定時間にArtisanスケジューラーから起動されます。機能的には一番単純なコマンドです。app/HubConnections/Commands/Reminder.phpです。
Artisanコマンドは既にお手の物でしょう。ビジネスロジックの本体部分はhandleメソッドでした。注目しましょう。
コンストラクターはサービスコンテナの働きで、タイプヒントされたクラスのインスタンスを自動的に依存解決し、渡してくれるんでしたよね。特に依存の解決方法を指定していなくても、インスタンス可能なクラスであれば、それを生成してくれます。
このコマンドで必要なのは、通知ロジックを実装しているNotifireクラスです。具体的にはApp\HubConnections\Notifiers\Reminderクラスです。それをサービスコンテナによる自動インスタンス注入機能により、受け取っています。
では、この通知ロジッククラスを見てみましょう。
コマンドのコンストラクターでタイプヒントを指定し、この通知クラスは自動注入されました。自動注入されるクラスのコンストラクターにタイプヒントが記述されていると、それらも再帰的に自動注入してくれます。そのため、このクラスでもイベントデスパッチャーとReminderイベントのインスタンスが取得できます。
デスパッチャーをよく見ると、Illuminate\Contracts\Events\Dispatcherです。これ、実はインターフェイスです。
「さすがララベル、インターフェイスまで解決してくれるなんて、魔法みたいだな。」
実のところインターフェイスも解決できます。ですがインターフェイスの場合、「このインターフェイスを解決するときは、このクラスをインスタンス化する」という指定が予め必要です。
Illuminate\Contractsの下には、システムが定義しているContract、つまり「契約」が置かれています。Laravelが提供している契約インターフェイスは起動時の初期処理により、サービスコンテナにその具象クラスが登録されます。それにより、契約としてのインターフェイス名を指定しても、その契約を実装している具象クラスのインスタンスを取得できます。
ではIlluminate\Contracts\Events\Dispatcherインターフェイスで取得できる実体のクラスとは何でしょう。それは今までイベントを発行する時に利用していた\Eventファサードクラスの実体と同じものです。Illuminate\Events\Dispatcherクラスです。実体クラスを確認するには公式ドキュメントのファサードと契約のページを参照してください。もしくは、契約のインターフェイスと実体クラスを結合しているIlluminate\Foundation\Applicationクラスを読んでください。
Laravelのファサードは静的メソッド記法を提供していますが、実際は静的メソッドではなくインスタンス化して利用する通常のクラスです。Laravelが必要に応じてインスタンス化し、静的記法のメソッドに対しマジックメソッドを使用し、staticではない通常のメソッドを呼び出しています。
ですから、\Event::fire()と今回のコード中に存在している$this->dispacher->fire()は、同じメソッドを呼び出しており、「イベントの発行」という同じ動作をします。
3.2 Reminderイベントクラス
イベントクラスです。単純化しています。
今までのイベントクラスと大差ありません。app/HubConnections/Eventsディレクトリーに保管していることに注意を払ってください。
3.3 MailSenderイベントリスナー
イベントリスナーは、App\HubConnections\Listeners\MailSenderです。名前が示している通り、メールを送信する役目です。
今のところ、ダミーです。とりあえずうまく動作しているかチェックするため、イベントをログへ書き込んでいます。イベントが間違いなく到達することを確認してから、内容を実装しましょう。
Log::infoメソッドの引数は文字列です。イベントクラスインスタンスを直接渡しています。イベントが持っている情報は、それぞれ異なります。リスナー側でイベントに依存する情報を編集すると、取り扱うイベントごとにロジックが必要になります。そこで、イベントのことはイベントに任せています。リスナーとしてはイベントに「文字列化のためのメソッド」が実装されていれば事が足ります。
文字列化のためのメソッドである__toString
が実装されていることを保証したいために、StringizableInterfaceインターフェイスをimplimentsし、実装を強要するHubConnectionBaseEventベースクラスをタイプヒントし、イベントを受け取っています。もしこの__toString
を実装していないクラスインスタンスが渡されると、実行時にエラーになります。
この実行時エラーは「引数に渡された内容が間違っている」ということを表します。誤りがあるまま素通りさせるのではなく、「これ間違っているよ」と叱ってもらうのです。素通りさせてしまうと、他の箇所でエラーになったりするため、原因を探さなくてはなりません。今回は単純なコードですが、複雑なロジックの場合、原因から離れた場所でトラブルになると解決するのが面倒になります。ですから、PHPの仕組みを使い、あらかじめ予防策を取っておきます。
では、EventServideProviderで、イベントとリスナーを結びつけましょう。
ここでは、結びつけているだけです。
では、Reminder Artisanコマンドを登録し、実行しましょう。App\Console\Kernelを変更します。
これでコマンドが登録できました。実行してください。
適当なメッセージを渡して、ログファイルに書き込まれるか確認してください。
うまく行ったら、MailSenderリスナークラスへメール送信のコードを入れましょう。
‘自分のメールアドレス’は自分のメールアドレスに書き換えてください。そうしないとエラーが発生します。(たとえlogメールドライバーを使用していてもです。メールアドレスとして有効な文字列にしてください。)
リスナーのhandleメソッドは、イベントを受け取るものと決まっています。そのため、サービスコンテナによる自動依存注入は行われません。コンストラクターの自動注入により、メーラーのインスタンスを受け取っています。
Mailファサードを使う方法もあります。Mailファサードを使用すれば、コンストラクターは必要なくなります。$this->mailer->rawを\Mail::rawと短く書けます。
ファサードはインスタンス化のコードが不必要ですし、ファサード名=使用するコア機能のため、直感的で読みやすいのです。一方の自動依存注入を利用したコードはエディターやIDEの補完などの手助けがフル活用でき便利です。どちらでもお好きな方をご利用ください。
3.4 使ってみる
さて、いつまでも自分でコマンドを叩いていたのでは全然便利ではありません。早速スケジューラーに登録しましょう。
起動間隔の指定に利用できるメソッドは、Illuminate\Console\Scheduling\Eventクラスを確認してみるとよいでしょう。メソッドのコードを読んでみると気がつくことがあります。
内部的にはexpressionプロパティーとして、cron形式の日時時間が保持されています。(時間部分5つ+何に使用しているか不明1つ、たぶん実行ユーザーを指定するためのもの)cron指定の5つのフィールドをそれぞれ設定できるspliceIntoPositionメソッドが用意されていますが、ほとんどのメソッドで活用されていません。そのため、以下のようなことが起きます。
dailyメソッドは単にexpressionプロパティーに、’0 0 * * * *‘をセットするだけです。weekdaysメソッドは曜日を表す5番目のフィールドだけを’1-5’に置き換えます。そのため、指定する順番により意図通りに動かないことになります。
確かにメソッドを使用したほうが意図が読み取りやすくなりますが、そのメソッドが値をただセットするのか、それとも特定のフィールドを置き換えるのかを全部覚えるのはやや大変です。
ところでcronの時間指定は面倒でしたか?ここまでやってきて、さほど難しくはないことが理解できたのではないでしょうか。cronの指定形式に慣れてしまえば、直接cronメソッドで記述してしまうほうが簡単です。私のスケジュールのゴミかご出しは以下のように書き直せます。
これも好みです。皆さんも自分で選択してください。
3.5 拡張
現在、起動側とリスナーは1対1の関係です。しかし、これからいろいろな条件で起動されたイベントで、メール送信を利用されるかも知れません。
そうすると、メールで指定するsubjectの内容は、通知する内容に応じたタイトルにできたほうが分かりやすいですよね。では、どうしましょうか?subjectごとにリスナーを作成しましょうか?それともコマンドで指定できるようにし、イベントに情報を含めましょうか?すると、その情報をどうやってリスナーで取得しましょうか?いろいろと考慮するしなくてはなりません。
実際に拡張をしなくても少し考えてみましょう。設計の練習になります。
同様にメールアドレスの変更をできるようにするには、どんな方法で実現しますか?
もし、このリマインダーをWebサイトから指定できるようにするなら、どう実現しますか?コマンドで追加できるようにするとしたらどうしましょうか?
どう拡張するか少し考えてみるだけでも、勉強になります。