PHP – クラスの確認

概要

  • instanceof:値のクラスをチェック
  • is_a():値のクラスをチェック
  • is_subclass_of():値の親クラスをチェック
  • class_uses():トレイトを使っているかどうかチェック

instanceof/is_a()

instanceof

インスタンス instanceof クラス等名

instanceofは、第1オペランドが第2オペランドのクラスに合致しているかどうかをチェックする演算子。第2オペランドにはクラス名などをそのまま記述する。is_a()関数と同じ機能。

以下の例の様に、instanceofは、

  • そのインスタンスのクラス、継承した親クラス、インプリメントしたインターフェイスに対して真となる
  • トレイトについてはuseしていても真とはならない

is_a()

is_a(インスタンス, 'クラス等名');

is_a()は第1引数のインスタンスが第2引数のクラスに合致しているかどうかチェックする。第2引数のクラス名は文字列として与える。

以下の例のように、親クラス、インターフェイス、トレイトに対する判定はinstanceofと同じ。

is_subclass_of()

is_subclass_of(インスタンス, 'クラス等名');

is_subclass_of()は、第1引数のインスタンスのクラスが第2引数のクラスを継承しているかどうかをチェックする。引数の渡し方はis_a()と同じ。

以下の例のように、

  • 引数のインスタンスのクラスそのものは偽
  • 継承した親クラスとインターフェースに対しては真
  • トレイトは偽

class_uses()

class_uses(インスタンス);

class_uses()は、引数インスタンスのクラスがuseしているトレイト連想配列として返す。連想配列のキー・値とも、トレイト名の文字列。

トレイトをuseしているかどうかをチェックするには、class_uses()の結果の連想配列に対して、in_array()array_key_exists()でトレイト名が含まれるかどうかをチェック。

親クラスでトレイトをuseしていても、子クラスのclass_uses()では捕捉されない。

method_exists()/property_exists()

method_exists(インスタンス, ’クラス等名’)
property_exists(インスタンス, ’クラス等名’)

method_exists()は、第1引数で指定したインスタンスのクラスに第2引数で指定したメソッドが定義されているかどうかをチェックする。同様に、property_exists()はプロパティーが定義されているかどうかをチェックする。

以下の例で、method_exists()について確認する。

  • 指定したメソッドが定義されていればtrue、定義されていなければfalse
  • 親クラスのメソッドでも存在する(true)と判定(publicprivateに関わらない)
  • トレイトのメソッドも存在すると判定
  • 親クラスでuseされたトレイトのメソッドは、子クラスでも存在すると判定

 

PHP – array_key_exists() – 配列のキーの存在確認

概要

array_key_exists()$keyが配列のキーとして存在するかどうかをチェックする。比較にあたって、常に型変換が自動で行われる(‘==’と等価)。型までのチェックはされない。

array_key_exists($key, 配列)

連想配列への適用例

以下は連想配列に対する実行例。文字列と数値が自動で変換されている。

配列への適用例

配列に対しても適用可能で、この場合はインデックスが存在するかどうかがチェックされる。このときも自動的に型が変換される。

 

PHP – in_array() – 配列の値の存在確認

概要

in_array()は、指定した$needleが配列($heystack)の要素として含まれるかどうかをチェックする。

in_array($needle, 配列, $strict=false)

  • デフォルトでは$strict=falseで、型変換を行って内容比較する
  • $strict=trueを指定すると、型の一致まで確認する

配列の要素の確認

以下の例ではデフォルトの$strict=falseとなっていて、文字列・数値の変換が自動的に行われている('=='と同じ効果)。

$strict=trueを指定すると、文字列と数値の違いも判定される('==='と同じ効果)。

連想配列の値の確認

in_array()で連想配列を指定すると、キー・値のうち指定した$needleが「値として含まれるか」チェックする。キーとして含まれていてもfalseになる。

デフォルトの$strict=falseでは型変換が自動的に行われる。

$strict=trueに指定すると、型の一致までチェックする。

注意点

  • in_array()は空の配列やnullに対してfalseを返す
  • nullに対しては警告が出される

 

PHP – トレイト – メソッドの汎化

概要

インターフェイスはクラスがメソッドを実装することを明示・保証するが、その実装はインプリメントしたクラスに託されている。

一方トレイトは、予めメソッドを具体に実装しておき、これを用いるクラス中でuse文によって導入される。

トレイトの使い方

定義

traitキーワードでトレイトを定義し、その中で具体のメソッドを定義する。

トレイトの実装

トレイトを導入するクラスで、useキーワードで導入するトレイトを宣言し、メソッドを呼び出す。

実装例

ベースになるコード

インターフェイスで例示したRPGキャラクターのコードを使う。

  • 基底クラスCharacterClass:名前を持ち武器攻撃のみできる
  • インターフェイスSpellcaster:呪文を唱えるメソッドspell()を宣言
  • Swordfighterクラス:CharacterClassを継承
  • Wizardクラス:CharacterClassを継承し、Spellcasterをインプリメント
  • Priestクラス:CharacterClassを継承し、Spellcasterをインプリメント

要件

  • SwordfighterPriestに、クリティカルアタックの機能を持たせる
  • クリティカルアタックはどのクラスにおいても共通の技

トレイトの追加

以下のトレイトを追加する。

トレイトの導入

SwardfighterクラスとPriestクラスでトレイトを導入する。

トレイトのメソッドの利用

クラスのインスタンスでトレイトのメソッドを呼び出す。

留意点

  • プロパティーも宣言・定義できる
  • トレイトを導入するクラスを意図した$thisを使える
  • インスタンスのクラスがトレイトを導入している場合、class_uses()メソッドで導入しているトレイト名を得られる

class_uses~実装しているトレイトの確認

たとえば上の実装例で、別のトレイトも加えてclass_uses()で内容を確認してみる。トレイト名をキー・値とする連想配列となっていることがわかる。

これまでの例で、各クラスのインスタンスを配列に入れて、クラスに応じた動作をさせる。

トレイトを実装しているかどうかのチェックは、class_uses()で導入しているトレイトの連想配列を得て、そのキーに対象となるトレイト名があるかどうかをarray_key_exists()でチェック。

 

PHP – インターフェイス

概要

  • PHPではクラスの多重継承はできないが、複数のインターフェイスをインプリメントできる
  • インターフェイスでは実装すべき関数の宣言のみ行い、実装はこれをインプリメントした関数で行う
  • これにより、インターフェイスを実装するクラスが持つべきメソッドが明示・保証される

基本形

インターフェイスの宣言・定義

インターフェイスの実装

実装例

RPGのキャラクターをイメージしたキャラクターを実装していく。

親クラス

全てのキャラクターはCharacterClassクラスを継承し、

  • 名前を持つ
  • 武器で攻撃するattack()メソッドを持つ

Swordfighterクラス

SwordfighterクラスとWizardクラスはCharacterClassを継承し、武器による攻撃ができる。

Spellcasterインターフェイス

Spellcasterインターフェイスは呪文を使える種族を表し、呪文を唱えるspell()メソッドを持つ。spell()メソッドの実装は、インプリメントするクラスで定義する必要がある。

Wizardクラス

WizardクラスはCharacterClassクラスを継承するとともに、Spellcasterインターフェイスをインプリメントする。Wizardは武器による攻撃ができるほか、spell()メソッドで攻撃魔法を実装している。

Priestクラス

PriestクラスはCharacterClassクラスを継承するとともに、Spellcasterインターフェイスをインプリメントする。Priestは武器による攻撃ができるほか、spell()メソッドで味方の治療を実装している。

ポリモーフィズムによる実行

SwordfighterWizardPriestのインスタンスを生成し、$party_members配列に保存する。

  • 全てのメンバーに武器で攻撃させる
  • その後、Spellcasterのメンバーに呪文使用させる

インターフェイス実装の確認

あるクラスが指定したインターフェイスをインプリメントしているかどうかは、クラスのチェックと同じ型演算子instanceofで確認できる。

 

PHP – 抽象クラス/ポリモーフィズム

抽象クラス

抽象クラスは

  • abstract classキーワードでクラスを定義する
  • abstract定義されたメソッドを含む

たとえば以下のクラスは、RPGのキャラクターをイメージしていて、

  • 固有の名前を持つ
  • 名前を返すpublicメソッドを持つ
  • attack()というabstract宣言されたpublicメソッドを持つ

このクラスからインスタンスを生成しようとすると、Cannot instantiate abstract class CharacterClassとエラーになる。

抽象クラスの継承

先の抽象クラスCharacterClassを継承して、以下の2つのクラスを定義する。

こうすると、Swordfighterクラス、Wizardクラスのインスタンスを生成することができる。

ポリモーフィズム

先の2つのクラス(SwordfighterWizard)のインスタンスを、以下のように配列にしてattack()メソッドを実行してみる。

配列の各要素が具体にどのクラスかを指定しなくても、それぞれのクラスに応じたattack()メソッドが適切に呼ばれている(ポリモーフィズム)。

抽象クラスとポリモーフィズム

  • 抽象クラスはabstractメソッドを含む
  • 抽象クラスを継承したクラスのうち、インスタンスを生成するクラスでは、必ずabstractメソッドを実装しなければならない
  • 実行時にabstractメソッドに対応したメソッドを呼び出すと、インスタンスのクラスを逐一確認しなくても、そのインスタンスのクラスに応じたメソッドが実行される
  • したがって、abstractメソッドの機能の実装は各継承クラスで責任を持ち、それを呼び出す側でケアする必要はない(例外処理などを除けば)

 

PHP – 継承

設定

以下のようなクラス構成を考える。

  • RPGに出てくるようなキャラクターとして、剣士と魔法使いを考える
  • 共通点として、いずれも名前とエネルギーを持つ
  • 剣士特有の能力として、剣で切りかかって戦う動作ができる
  • 魔法使い特有の能力として、攻撃魔法を唱える動作ができる
  • それぞれの動作を行うと、それに応じたエネルギーが消費される

親クラス~汎化

キャラクターの種類によらず共通するものを、親クラスCharacterClassとしてまとめる。

  • privateなプロパティーとして、$name(名前)、$energy(エネルギー)を持つ
  • インスタンス生成時に名前を与えて生成し、エネルギーの初期値は職業によらず100
  • それぞれの値を得るpublicなゲッターを備えている
  • エネルギー値を変更するpublicメソッドを備えている
  • 自身の攻撃の番であることを表示するpublicメソッドを備えている

継承

CharacterClassクラスを継承し、剣士(Swordfighter)と魔法使いのクラスを定義する。

  • 戦士の動作メソッドattack()で剣で切りかかり、エネルギーを15消費する
  • 魔法使いの動作メソッドspell()で攻撃魔法を唱え、エネルギーを10消費する

剣士としてアーサー、魔法使いとしてハリーという名前でインスタンスを生成し、順番に2回ずつ攻撃させる。

メソッドのオーバーライド

CharacterClassクラスにshowTurn()メソッドを定義したが、これと同じ名前のメソッドをSwordfighterクラスとWizardクラスに定義する。

そして同じように実行すると以下のようになり、それぞれの子クラスで定義したshowTurn()が呼ばれて剣士・魔法使いと表示され、その後に親クラスのメソッドの表示がされている。

ここで行ったのがメソッドのオーバーライド。

  • 親クラスのメソッドと同じ名前のメソッドを子クラスで定義した場合、子クラスのメソッドが呼ばれる
  • 子クラスのメソッドの中でparent::メソッド名とすると、親クラスのメソッドが呼ばれる。

実行時のメソッドの探索順は以下の通り。

  1. 現在のクラスで該当するメソッドを探す
  2. 無ければ親クラスのメソッドを順次遡って探す
  3. parent::で呼ばれた場合、親クラスのメソッドを探す

コンストラクター

オーバーライド

上記のコードに更に変更を加えて、初期エネルギー値を戦士が100、魔法使いが80となるようにする。

まずCharacterClassのコンストラクターを変更して、$energyについても引数で値をあてるようにする。

そしてSwordfighterWizardそれぞれのクラスにコンストラクターを追加。インスタンス生成時には名前のみを指定し、それぞれのクラスに応じた初期エネルギー値を親クラスのコンストラクターに渡して、名前とともにセットする。

実行結果は以下の通り。

クラスの継承とコンストラクター/デストラクターについて

  • インスタンス生成時、子クラスのコンストラクターが見つからなければ、親クラスのコンストラクターが使われる
  • 子クラスのコンストラクターを定義した場合、暗黙では親クラスのコンストラクターは呼ばれない
  • から親クラスのコンストラクター/デストラクターを呼び出すときは、parent::_construct/__destructで呼び出す
  • 通常、子クラスのコンストラクターの中で
    • parent::constructor()はコードの最初に書く
    • parent::destructor()はコードの最後に書く

プロパティーのprivate宣言について

  • 親クラスのプロパティー$name$energyprivate宣言にしているので、継承した子クラスから直接はアクセスできない
    • このため、publicなメソッドを準備して、アクセスさせている
  • プロパティーをpublic宣言すると子クラスから$this->nameのようにアクセスできる
    • しかしpublic宣言すると、クラス外部で任意に名前やエネルギーの値を変更できてしまう
  • protected宣言にすれば、継承したクラスから$this->nameのようにアクセスできる
    • ここではプロパティーの変更を厳格にし、アクセスメソッドを準備しているのでprivateにした

 

PHP – クラス

クラスの定義

  • classキーワードでクラスを定義
  • プロパティーはprivate/protected/publicで宣言
  • コンストラクターはpublic function __construct()で定義(アンダースコア2つ)
  • メソッドはpublic/protected/private functionで定義
  • インスタンスの生成はnew クラス名(...);

プロパティー

宣言・定義

プロパティー宣言時に値の初期値を定義可能。

private/protected/public プロパティー = 初期値;

参照・代入

クラス内でのプロパティー参照・代入

$this->プロパティー名($除き)

publicプロパティーのインスタンスからの参照・代入

インスタンス変数->プロパティー名($除き)

メソッド

メソッドはオーバーロードできない

PHPでは、異なるシグネチャーでメソッドをオーバーロードすることはできない。コンストラクターについても同じくオーバーロードできない。

以下のクラスではメソッドをシグネチャーの違いによってオーバーロードしようとしている。

しかし複数宣言はできないというエラーになる。

デフォルト引数は使える

異なるシグネチャーのメソッドは、デフォルト引数を使うことで実現できる。

デフォルト引数が指定されている場合に引数を指定すると、先頭の引数から値が設定されていく。引数名を指定して任意の引数の実を指定することはできない。

コンストラクター/デストラクター

public function __construct(...) { ... }
public function __destruct(... { ... }

  • いずれも動詞形で、先頭にアンダースコアを2つつける

クラスを継承した場合、コンストラクター/デストラクターを定義しなければ、親クラスのそれらが呼ばれるが、定義した場合には明示的に親クラスのコンストラクター/デストラクターを呼び出す必要がある。

parent::__construct(...)
parent::__destruct(...)

staticプロパティー

プロパティーをstatic宣言することで、クラスとして保持されるようになる。

private static宣言

クラス外からアクセス不能なクラスレベル変数。

  • クラス内で(すなわち各インスタンスから)self::プロパティー名($付き)でアクセスできる
  • 全インスタンスで1つの値を共有する

以下の例では全インスタンスに共通のカウンターをクラス変数として持ち、

public static宣言

クラス外からもアクセス可能なクラスレベル変数。

  • クラス内外を問わず、クラス名::プロパティー名($付き)でアクセスできる

以下のコードではクラスレベルのカウンターをpublic宣言し、クラス外からもアクセス可能としている。

8行目はself::$countでもよいが、クラス外からもアクセス可能なことを強調してMyClass::$countとしている。

staticメソッド

public static宣言

クラス外からもアクセス可能なクラスレベルメソッド。

以下のコードでは、private static宣言されたカウンターを、public static宣言されたメソッドで参照している。このようにすると、private宣言されたカウンターが外部で変更される恐れがなくなる。

private static宣言

private宣言したメソッドはstatic宣言されたクラスメソッドでもインスタンスメソッドでもあまり変わりはないように思える。

こちらの記事で、staticメソッドからインスタンスのプロパティーにアクセスできないことを利用して、メソッドが余計な副作用を持たないことを明示できるとあって、なるほどと思った。

 

Laravel – バリデーションメッセージの日本語化

概要

バリデーションエラーはデフォルトで英語だが、これを日本語にしてみる。

方法の1つとして、ロケールjaに対するメッセージファイルを編集する方法を整理する。

ロケールファイルを一括して日本語化する方法はこちら

元になるアプリケーション

ルーティング

GETリクエストに対してフォームを含むビューを表示し、POSTリクエストに対してバリデーション処理を実行する。

ビュー

フォームでユーザー名とメールアドレスを入力し、POST処理の結果バリデーションエラーがあれば表示する。

コントローラー

フォーム表示

GETリクエストに対してフォーム画面を表示する。

POST処理

POSTリクエストに対して、ユーザー名とメールアドレスのバリデーションを実行。

この場合のバリデーションエラーは以下の様に表示される。

あるいは

ロケール設定による日本語化

ReaDouble.comのバリデーションのドキュメントでエラーメッセージのカスタマイズについて説明されていて、ローカライゼーションについては多言語化ドキュメントを参照するよう示唆されている。

これに基づいて、バリデーションエラーメッセージの日本語化をローカライゼーション周りで実装する方法を整理する。

言語の選択

config/app.phpでロケール設定を変更する。

  • localeをデフォルトのenからjaに変更
  • localejaで見つからないときのfallback_localeがデフォルトでenになっていることを確認
  • これにより、日本語ロケールの設定がある場合は日本語が適用され、見つからない場合は英語の設定が適用される

バリデーションエラーメッセージのコピー・編集

resources/en/validation.phpで英語のバリデーションメッセージが定義されている。

日本語ロケール設定のため、jaディレクトリーを作成し、そこにコピーしたvalidation.phpを編集する。

  • resources/jaディレクトリーを作成
  • jaディレクトリー下にvalidation.phpをコピー
  • 必要なバリデーションメッセージを日本語化

:attributeはエラーが発生したフィールドのname属性の値で置き換えられる。また:maxはバリデーションで設定した値に置き換えられる。

これにより、バリデーションエラーが生じたときの表示は以下の様に日本語化される。

あるいは

属性の日本語化

上記のメッセージでは、:attributeが置き換えられるname属性のnameemailがそのまま現れている。

これらは、validation.phpの最後にある'attributes'に連想配列を記述することで、日本語表現に置き換えることができる。

今回の例の場合、name属性はname(ユーザー名)とemail(メールアドレス)なので、これらを日本語に置き換えるよう定義する。

この結果、バリデーションエラーメッセージは以下の様になる。

あるいは

エラーメッセージを日本語化したもの以外のバリデーションに対しては、元のまま残っている英語の内容が使われる。

たとえばバリデーションを追加する。

そうするとバリデーションエラーは以下のようになる。

全体のセンテンスは英語だが、name属性emailattributesで変更した内容に置き換えられている。

 

 

Laravel – 動的メッセージ表示

概要

バリデーションエラーや登録成功などのメッセージを一時的に表示する方法を整理する。

ダイアログによりブラウザー表示領域とは別に表示する方法と、表示領域にメッセージを出して動的に消去する方法の2つ。

エラー・成功メッセージの登録

以下のような例で考えていく。

  • GETリクエストから表示用コントローラー・アクションにルーティング
  • アクションからフォームを含むページを呼び出す
  • フォームにユーザー名とメールアドレスの入力テキストボックスを配置

  • 送信ボタンが押された場合のPOSTに対して、バリデーション用コントローラー・アクションにルーティング
  • バリデーション用アクションでは、ユーザー名とメールアドレスのバリデーションを記述
    • バリデーション結果が妥当な場合はflash()で成功メッセージを登録
    • バリデーション結果が妥当でない場合は、それ以降は実行されず$errorsに結果が保存される
  • いずれの場合も元のGETルートにリダイレクトされる

状況に応じて登録された/空のままの$errorsflashに対して、ビュー側で表示する。

ダイアログ表示

$errorsflashをダイアログで表示する方法。

ビューのhead要素内で、これらの状態に応じたメッセージを生成し、Javascriptのalertの引数に渡している。

複数のエラーメッセージを改行文字を挟んでつなげるのに、PHPのinplode()関数を使っている。

スライドアップによる一時表示

エラーや成功メッセージをブラウザーの上部などに表示し、一定時間後にそれをスライドアップなどで消去する。

メッセージ表示領域の設定と、Javascriptによる動的な消去の2段階で整理する。

メッセージ表示エリアの設定

ビューのbody要素最上部で、ディレクティブによってメッセージ表示領域を動的に生成している。その際に、エラー/成功に応じたクラスを要素に設定している。

  • $errors->any()でエラーが登録されていれば、エラー表示領域を生成
  • session()->has()で成功メッセージが登録されていれば成功メッセージ表示領域を生成

ビューのhead要素内で、エラーメッセージ領域/成功メッセージ領域それぞれのクラスに応じたスタイルを設定している。

なお、エラーメッセージは複数のメッセージを改行で表示させたいので、PHPのImplode()関数で区切りを<br>としたうえで{!! ... !!}で囲んでエスケープを抑制している。

メッセージ表示エリアの消去

エラー/成功のクラスに対して、JQueryでこれらをスライドアップして消去するスクリプトを追加する。

  • 5行目でJQueryをCDNで読み込み
  • script要素内でスライドアップの動作を定義