PHPでEnum(列挙型)っぽい何かを実装

PHP 皆さん書いてますか!元気ですか!

Javaっぽい構文だがなんだか痒いところに手が届かない感を感じたりしませんか。僕は感じます。

Java書いてるときは enum クラスというのを多用するのですが PHP にはなく、1年ぐらい前に実装しました。

コードは github においてあるので、せっかちな人はコードだけ見ればいいよ
git://github.com/yamashiro/php_enum.git


まずは使い方。テストを見てみましょう

<?php
require __DIR__ . '/CarrierEnum.php';

class CarrierEnumTest extends PHPUnit_Framework_TestCase {

    /** @test */
    public function valueOfのテスト() {
        //DBやcache、ファイルからとってきた値を valueOf するイメージ
        $this->assertSame(CarrierEnum::$au,
                          CarrierEnum::valueOf('au'));
    }

    /** @test */
    public function getNameのテスト() {
        //DBやcache、ファイルに書き込むときは getName
        $this->assertSame('softbank',
                          CarrierEnum::$softbank->getName());
    }

    /** @test */
    public function toStringも定義してあるから文字連結したりすると自動で() {
        $this->assertSame('hogedocomo',
                          'hoge'.CarrierEnum::$docomo);
    }

    /** @test */
    public function values() {
        //enum だから列挙できるよ!
        $values = CarrierEnum::values();
        $this->assertSame(4, count($values));
        $this->assertSame(CarrierEnum::$docomo, $values[0]);
    }
}

コードの中では文字列は絶対に使わずに、Enumのオブジェクトを使います。これによりマジックナンバーがなくなります。DBなどから値をとってきたら、valueOfを使ってPHPのオブジェクトにします。逆に書き込むときは、getNameかtoStringを使います。

どのように定義するかを以下に示します。

<?php
require_once __DIR__ . '/EnumBase.php';
class CarrierEnum extends EnumBase {
    static $docomo;
    static $au;
    static $softbank;
    static $willcom;

    private $otherOption1;
	protected function __construct($displayName, $otherOption1) {
	    parent::__construct($displayName);
		$this->otherOption1 = $otherOption1;
	}

	public static function ___init() {
		self::$docomo = new CarrierEnum('DoCoMo', 100);
		self::$au = new CarrierEnum('au', 50);
		self::$softbank = new CarrierEnum('softbank', 30);
		self::$willcom = new CarrierEnum('Willcom', 30);
	}

	public function getOtherOption1() {
	    return $this->otherOption1;
	}
}

CarrierEnum::___init();

自分で new しないといけないのと、___init() 呼ばないといけないのが悲しみですが、PHPで無理やり Enum を実装するのですから我慢しましょう。

普通のクラスですので、例えばこの例だと携帯キャリアごとの実装などはこのクラスに生やしてしまって構いません。

それをやってくとこのクラスが肥大化して…となったら Enum からは Strategy を返すとかでもいいですね。

さて最後に、これを実現している EnumBase

<?php
abstract class EnumBase {
    private $displayName;

    protected function __construct($displayName = '') {
        $this->displayName = $displayName;
        self::$enumValues[] = $this;
    }

    private static $enumValues = array();

    public static function values() {
        $values = array();
        $clazz = get_called_class();
        foreach (self::$enumValues as $enumValue) {
            if ($clazz == get_class($enumValue)) {
                $values[] = $enumValue;
            }
        }
        return $values;
    }

    public function getDisplayName() {
        if( $this->displayName === ''){
            return $this->getName();
        }
        return $this->displayName;
    }

    public function getName() {
         $reflection = new ReflectionClass(get_class($this));
         $staticProperties = $reflection->getStaticProperties();
         foreach ($staticProperties as $name => $value) {
             if ($value === $this) {
                 return $name;
             }
         }
    }
    public static function valueOf($name) {
        $clazz = get_called_class();

        $reflection = new ReflectionClass($clazz);
        $staticProperties = $reflection->getStaticProperties();

        foreach ($staticProperties as $propName => $value) {
            if ($name === $propName) {
                return $value;
            }
        }
        throw new Exception('そのような名前の enum は存在しません. name['.$name.'] $clazz['.$clazz.']');

    }

    public function __toString() {
        return $this->getName();
    }

}

PHP5.3 以降で動きます。5.2でも get_called_class を debug_backtrace を解析して実装すれば動かないこともないですが…


|