PHPのTraitをやたらと使って感じたこと

以前、php5.4のプロジェクトに関わっていて、
そこのプロジェクトの方針でやたらとTraitを作っていたが、Traitの多用は「鬼門」だと正直思った。

class MyModel extends BaseModel 
{
    use User;
    use Photo;
    use Album;
    use Relation;
   //....
   //....
   
   public function getData($id) 
   {
        $tmp = $this->getFriendList($id);      //どのTraitに実装されているか
        return $this->getPhotoSet($tmp);       //調べるのが結構たいへん!
   }
}

NetBeansの場合はtraitのコード補完・関数ジャンプの機能があるからまだ楽だけど、
それでも実装があちこちに飛んでしまうのは階層の深い継承と同じストレスがあるし、
Traitの関数名を迂闊に変えるて、他のTraitの関数と名前が衝突してしてコンパイルエラーが起こるので、多重継承と同じストレスもある。

しかし、継承を使わずにプロパティにアクセス出来る実装を使いまわせるのは便利だった。

例えば、SPLにあるArrayAccessインターフェイスとかはTraitにしたほうが便利なのではないかと思う。

/**
 * ArrayAccessで典型的な処理をTraitで実装
 */
trait ArrayAccessHelper
{
    public function setProerty($offset, $value) 
     {
        if (!empty($offset)) {
            $this->$offset = $value;
        } 
    }
    public function proertyExists($offset) {
        return property_exists($this, $offset);
    }
    public function unsetProerty($offset) {
        unset($this->$offset);
    }
    public function getProerty($offset) {
        return property_exists($this, $offset) ? $this->$offset : null;
    }
}

/**
 * 上記をArrayAccessインターフェイスと合わせて使う
 */
class ArrayObj implements arrayaccess 
{
    use ArrayAccessHelper;

    public function offsetSet($offset, $value) 
    {
        return $this->setProerty($offset, $value);
    }
    public function offsetExists($offset) 
    {
        return this->proertyExists($offset]);
    }
    public function offsetUnset($offset) 
    {
        return $this->unsetProerty($offset);
    }
    public function offsetGet($offset) {
        return $this->getProerty($offset]);
    }
}

まぁこのやり方でも、インターフェイスの実装は必要なだけど、幾つものクラスにインタフェイスを実装するときはこの方法でタイプの量をかなり減らせる。
(※1)

あと、以前のプロジェクトではCodeIgniterのModelにTraitを使っていた。

  • コネクションを保持するのはCI_Model の$db
  • 特定のテーブルのアクセスはメソッドはTraitにまとめる
  • アプリ内で使用するModelは上記2つを合わせて構築する

上記のルールでテーブルへのアクセス処理をかなり共通化出来たのですが、
その代わり最初に書いた問題も露呈指摘たのでTraitの使い処は難しい。


(※1) ArrayAccessを実装したクラスをを継承したほうが手っ取り早い事に気づいた、、、

複数環境でPHPを動かすときに気をつけていること。

ほとんどのサーバー再度開発は Local→ステージング→本番の様に コーディングからリリースまでに複数の環境で動かすことが多いと思います。

この時に問題になるのが“Localでの環境設定がそのまま本番にUPされてしまった。”といった環境依存コードの管理ではないでしょうか?

今回自分が環境依存コードの管理で気をつけている事をまとめてみました。

1. 環境の設定をSCMに入れない。

subversionやgitにどの環境を判定・設定するコードを入れずに、サーバーの設定や、デプロイ時のコマンドで対応するようにします。

下記の様なコードをSCMに上げていると”Localの環境が本番に上がってた。” といった危険が増えます。

#本番では下をコメントアウトする。
define(DEBUG, true);

上記を防ぐために環境切り替えのためのコードはSCMで管理している場所以外に置くようにします。

よく使う場所としてはApacheのconfです。

<localhost *:80>
...
SetEnv APP_ENV development
...
</localhost>

上記で設定した環境変数はPHPではgetenv関数で取得する事が可能です。

$env = getenv('APP_ENV');

switch($env) {
    case: 'production'
        include('prod_conf.php');
        break;
    case: 'development'
        include('dev_conf.php');
        break;
    case: 'test'
        include('test_conf.php');
        break;
}

上記の様な工夫により環境の設定ミスを起こす可能性がかなり減らせます。

2. 非機能要件は抽象化する。

非機能要件→キャッシュ、ログ、セキュリティなどは必ず何らかのラッパーを入れて
環境により切り替えられるようにしないと、「テストのためだけにコメントアウトしたコードが本番に上がる」
といった事態が起こります。

//windows環境で動かせないので下をコメント(といった一時的なコードがそのまま本番に上がってしまう。)
// $settings = $memcache->get('setting');

SymfonyやSpringなど、DIコンテナが使えるのであればそれを使ってキャッシュやログ管理を抽象化するようにしましょう。
そういったものが使えない場合もライブラリを導入するか自作するかして、他のプログラマが、キャッシュ機構やログ機構を意識しないような設計にするのは重要です。

以上です。
こういうのはユニットテストなんかを書くと早めに気づけるので、テストコードを書きつつ設計を見直すことをおすすめします。