現在開発しているプラグイン「CustomDataBaseTables」のバージョン2からは、動作環境をPHP5.4以上に変更した。その理由は、PHP5.4から実装されたtraitとClosure::bindTo、そして配列の短縮構文(従来のarray()に代わって[]と書けるようになった)を使っているからだ。
特にtraitとバインドによるクロージャー複製による恩恵は大きくて、この二つの合わせ技で動的なコンポーネントのレンダリング処理を仕込んでいる。この合わせ技については、また別の機会に紹介しようと思う。
今回は、使い勝手がすこぶる良いtraitを取り上げてみる。
traitを使うとクラスの横拡張ができる
PHPでは他の言語のようにクラスの多重継承という仕様がないので、依存関係が縦連結する継承方式しかない。でも、traitを使うことによって、今までのクラスの難点だった継承による階層構造を劇的に改善できる。そして、traitはMixinクラスのように再利用性のあるメソッドグループをサブクラス化して、クラスの本流である継承構造から分離させられるので、上手く利用すればソースの保守性も向上するのだ。
──と、まぁ、文字で説明されてもいまいちピンとこないので、図で書いてみた。
まず、従来のクラスの継承構造のイメージ:

機能別などで派生クラスを細分化したりすると、縦継承の階層がどんどん長くなって行ってしまい、処理によっては継承順とかも結構重要だったりして、クラス構造全体の見通しがよろしくない。
これが、traitを使うと:

traitによって共通的に使える機能を水平展開できるようになり、階層構造がかなりすっきりして見やすくなる。
また、従来のクラス構造だと、継承途中に派生クラスを追加もしくは削除したい時など、継承順を考慮しながら、前後のクラスのextendsを書き換える必要が出てくるが、traitとして追加する場合、追加したい階層のクラス内にuse文を書くだけで事足りる。
もし修正をクラス図に落とし込まなきゃならない開発案件の時とかを想像すると、継承構造の変更とか極力避けたくなる。そうすると、既存クラス内に拡張が追加されて単クラスが肥大化していく。同時にソースの保守性は低下してく…みたいな羽目になりかねない。
そんな時にtraitはかなりお手軽にクラスを拡張できるうえ、本流のクラスの継承構造を変更しないので、修正が非常に楽になる。
下記に「CustomDataBaseTables」での実際の使用例を紹介しておく(実際にはtraitごとに別ファイルになっている)。
trait CdbtAjax {
protected function ajax_init() {
(中略)
}
(省略)
}
final class CdbtFrontend extends CdbtDB {
(中略)
use CdbtAjax, DynamicTemplate, CdbtShortcodes, CdbtApis, CdbtExtras;
(中略)
}
インスタンス化するファイナルクラスは上記のCdbtFrontendともう一つ管理画面用のCdbtAdminがあって、それぞれのクラスで共通的に使う、Ajax処理やテンプレート操作、WordPressのショートコード制御などをtraitとして再利用している建て付けだ。
trait利用時の注意点
traitを利用する時に気をつけないといけない点はいくつかあるが、だいたい下記の3つぐらいに注意していれば困ることはあまりないかと。
traitの優先順序
trait利用時にオーバーライドされるメンバーがあった場合、優先順序は次のようになる。
現在のクラスのメンバー > traitのメンバー > 継承元のメンバー
※ traitが現在のクラスから呼び出されていた場合
同名メンバーのコンフリクト
複数のtraitを利用している時に、複数trait内に同名のメンバーが存在するとエラーになる。基本的に同名メンバーは定義しないに越したことはないのだが、機能が重複する部分があるもののあえて分離しておいたtraitを一緒に使わないといけないケースなどがあるかもしれない。
そういう場合はinsteadofを使って、同名メンバーのどっちを使うか指定するか、asを使ってメンバー名のエイリアスを定義し共存させる。
class FinalClass {
use Trait_1, Trait_2 {
Trait_2::method insteadof Trait_1;
Trait_2::method as aliase_method;
}
}
上記の例だと、Trait_1とTrait_2には同じmethodという名前のメソッドがあってコンフリクトしているが、FinalClassではTrait_1のmethodを使用するように指定している。さらに、使われなくなったTrait_2のmethodは別名aliase_methodとして利用できるようにしてある。
──insteadofとasでなんとかコンフリクトは回避できるが、コードの見通しが悪くなるので、こういう使い方はしない方が良い。
traitのネスト
traitは他のtrait内からも使用できる。だが、これをやり始めるとクラスが毛細血管のように拡散していってしまい、クラス構造が把握しづらくなる。ソースの見通しも非常に悪くなってしまう可能性があるのでやめた方が良い。
私としては、traitは極力インスタンス化するファイナルクラスでuseするようにしている。
他にも注意点はあるのだが、あまりトリッキーな使い方をすると、収集がつかなくなって自分の首を絞めることになるので、実用的な路線で利用していくのが最善かと思う。
まとめ
WordPressの推奨PHP環境が5.6となり、さすがに世の中のPHP普及バージョン環境も5.4ぐらいにはなって来たかと思って調べてみたら、54%もPHP 5.3以下の環境があってまだまだ主流のようだ…。
Usage statistics and market share of PHP version 5 for websites
http://w3techs.com/
う~ん、とても素敵なtraitの機能をまだ体験してない人も多いってことだな。
──というか、このままPHP5.4以上必須な条件でプラグインのバージョン2をリリースしてしまうと、使えなくなってしまうユーザーさんも結構いるってことがちょいとショックだ…。何か対応策を考えないとな…。