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

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

PHP5.4の新機能(その3)

1回目2回目だけではまだまだ書ききれないPHP5.4の新機能を紹介いたします。

1.配列のシンタックスシュガー

すっかり紹介を忘れてました。
他の言語のように配列の簡単な初期化がサポートされます。
(これを名前空間とか、無名関数の前にやってほしかったなぁ、、、)

$a = ['AKB', 'SKE', 'MNB'];
$b = ['チームA' => '倉持明日香', 'チームK' => '秋元才加', 'チームB' => '柏木由紀'];

そう、arrayとか書かなくて良いのですよ!  ”[]“だけで良いのですよ!!!

2.staticメソッドの可変呼び出し

波括弧を使いstaticメソッドの呼び出しを可変に出来ます。

class StaticClass {
public static $name = '峯岸みなみ';
public static function getName() {
return '峯岸みなみ';
}
}

$property = 'name';
echo StaticClass::${$property},PHP_EOL; //「峯岸みなみ」と表示される。(5.3でもOK)

$method = 'getName';
echo StaticClass::{$method}() ,PHP_EOL; //「峯岸みなみ」と表示される。(5.4からOK)

プロパティの呼び出しは5.3から{}が使えていたのですが、5.4からメソッドでも使える用になりました。

3.callableが型ヒントに使える

β1からタイプヒントにcallable型が使える様になりました。

//callable タイプヒント
function hi(callable $f) {
echo $f(), PHP_EOL;
}

class Human
{
public function hello() {
return "上からマリコ";
}
}

hi([new Human(), 'hello']); //「上からマリコ」が表示される。

$songName = function() {
return "Everyday カチューシャ";
};

hi($songName); //「Everyday カチューシャ」が表示される。

現在の実装として無名関数の親クラスはClojureでそれを型ヒントに使えるのですが、
将来変更される可能性があるのでより便利な型が追加されたようです。

4.コンストラクタからメソッドチェーンが実行可能

β2から追加された新機能です。

class Human
{
function __construct($name) {
$this->name = $name;
}

public function hello() {
return "Hi " . $this->name;
}
}

// 古い方法
$human = new Human("まゆゆ");
echo $human->hello();

// 新しい方法
echo (new Human("まゆゆ"))->hello();

phpは変数のスコープが他の言語と比べても広いので、
コンストラクタからメソッドチェーンや、配列をメソッドチェーンの様に利用
は変数の宣言を抑えて、名前の衝突を避けることができるので地味に嬉しいですね。

php5.4の新機能(Trait)

前回、Traitsについて書くと書きましたが、
それについて自分がしっかり理解できていなかったのですね、、、、

Traitsのサンプルをみると

trait A {
  public function smallTalk() {
    echo 'a';
  }

  public function bigTalk() {
    echo 'A';
  }

}

trait B {
  public function smallTalk() {
    echo 'b';
  }

  public function bigTalk() {
    echo 'B';
 }

}

 class Talker {
   use A, B {
     B::smallTalk instead A;
     A::bigTalk instead B;
   }
 }

$talker = new Talker();
$talker->smallTalk(); //bと表示
$talker->bigTalk(); //Aと表示

みたいな例が出てきますが、これって

class A {
  public function smallTalk() {
    echo 'a';
  }
  public function bigTalk() {
    echo 'A';
  }
}

class B {
  public function smallTalk() {
    echo 'b';
  }
  public function bigTalk() {
    echo 'B';
  }
}

class Talker {

  private $a;
  private $b;

  public function __construct() {
    $this->a = new A();
    $this->b = new B();
  }

  public function smallTalk() {
    $this->b->smallTalk();
  }

  public function bigTalk() {
    $this->a->bigTalk();
  }

}

$taker = new Talker();
$talker->smallTalk(); //bと表示
$talker->bigTalk(); //Aと表示

と変わらないんじゃねーの? と思ってたのです。
しかし、ここのサンプルをみて、Traitの意味が分かってきたのです!
https://wiki.php.net/rfc/traits

class Base {
  public function sayHello() {
    echo 'Hello ';
  }

}

trait SayWorld {
  public function sayHello() {
    parent::sayHello(); //親クラスが無いのにpearent?
    echo 'World!';
  }
}

class MyHelloWorld extends Base {
  use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello(); // Hello World! と表示される

なるほど、これは他のクラスへのデリゲーションでは実現できない。

  • 継承はコードの再利用ができるけど、親クラスは一つのみ(ダイアモンド継承を避けるため)
  • デリーゲーションは、制約なく色々組み合わせる事が出けど、継承関係の再組み合わせは出来ない。

この問題を解決するための新しい単位が「Trait」か!

上記の例は、正直使いどころが直ぐに思い浮かばないけど、
下の例は結構便利そうですね。

trait Hello {
  public function sayHelloWorld() {
    echo 'Hello'.$this->getWorld();
  }

  abstract public function getWorld(); //実装を呼び出し側に任せる
}

class MyHelloWorld {
  private $world;
  use Hello;

  public function getWorld() {
    return $this->world;
  }

  public function setWorld($val) {
    $this->world = $val;
  }
}

上の例も、デリゲーションでもなんとかなるけど、abstractという形で実装の強制はできないし、
なによりデリゲーションよりずっとコード量がへるので便利ですね!

php5.4の新機能を2週にわたってレビューしましたが、いかがでしょうか?
他にこんな新機能も注目だよ!っていうのがあれば教えて下さい。

php5.4の新機能

php5.4αが7/5にリリースされましたが、
そのなかで気になった機能を紹介したいとおもいます。

1. 配列をメソッドチェーンのように参照可能

php5からおなじみのメソッドチェーンですが、

class A
{
  public function foo() {
    return new B();
  }
}

class B
{
  public function bar() {
    return "あっちゃん";
  }
}

$a = new A();
echo $a->foo()->bar(); //「あっちゃん」と表示される。

戻り値を配列にした時は1度変数に入れないと、インデックスにアクセスすることは出来ませんでした。

class A
{
  public function foo() {
    return array('name' => 'あっちゃん');
  }
}

$a = new A();
$foo = $a->foo();
echo $foo['name'];

php5.4からはメソッドチェーンのように配列のインデックスにアクセス可能です。

$a = new A();
echo $a->foo()['name']; //「あっちゃん」と表示される。

2. callable配列をそのまま実行可能

callable配列( array(‘class’, ‘method’) )をそのまま実行出来るようになりました。

class Foo
{
  public function bar($name) {
    echo "こんにちは, $name";
  }
}

$f = array('Foo','bar');
echo $f('まりこさま'); // 「こんにちは, まりこさま」と表示される。

詳しくはこちらを御覧ください。

3. クロージャの中で$thisが使える

クロージャでの$thisの呼び出しがサポートされました。

class A
{
  private $value = 'ともちん';
  function single_getter($name) {
    return function() use ($name) {
      return $this->$name;
    };
  }
}
class B
{
  private $value = 2;
  function test() {
    $a = new A;
    $private_getter = $a->single_getter('value');
    print $private_getter();
  }
}

$b = new B;
$b->test();

php5.3までは、 「Fatal error: Using $this when not in object context」でしたが
php5.4では、「ともちん」と表示されます。

4.Traitsのサポート

でました、Traits。
この説明だけで一つ記事が出来ると思うので、次回書かせていただきます。

4.Traitsのサポート

でました、Traits。
この説明だけで一つ記事が出来ると思うので、次回書かせていただきます。