29
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPの配列要素の型を縛る

Last updated at Posted at 2014-02-21

前書き

@Hiraku さんの SplFixedArrayで"純粋な配列"を作る に感化されて衝動的に作ってしまった。

個人的により強く縛りたいと感じるのは、配列のサイズというよりは格納される 要素の型 なので。それならPHPなんか使うなって言われそうだけどまぁうんそうだねはい(適当)

Collection

ArrayObject を継承して作ってみました。使い方はほとんど ArrayObject と同じです。

クラス定義

class Collection extends ArrayObject {
    
    const TYPE_BOOL        = 1;
    const TYPE_INT         = 2;
    const TYPE_FLOAT       = 4;
    const TYPE_STRING      = 8;
    const TYPE_ARRAY       = 16;
    const TYPE_OBJECT      = 32;
    const TYPE_NULL        = 64;
    const TYPE_RESOURCE    = 128;
    const TYPE_CALLABLE    = 256;
    const TYPE_STRINGABLE  = 512;
    const TYPE_NUMBER      = 7;
    const TYPE_TRAVERSABLE = 48;
    const TYPE_ALL         = 1023;
    
    private $type;
    private $object;
    private $resource;
    
    public function __construct($type = self::TYPE_ALL, $object = null, $resource = null) {
        if (!is_int($type) or !($type & self::TYPE_ALL)) {
            throw new InvalidArgumentException('Unknown type definition.');
        }
        $this->type = $type;
        if ($object !== null) {
            switch (true) {
                case is_object($object):
                    $this->object = get_class($object);
                    break;
                case is_string($object):
                    $this->object = $object;
                    break;
                default:
                    throw new InvalidArgumentException('Illegal object type definition.');
            }
        }
        if ($resource !== null) {
            switch (true) {
                case is_resource($resource):
                    $this->resource = get_resource_type($resource);
                    break;
                case is_string($resource):
                    $this->resource = $resource;
                    break;
                default:
                    throw new InvalidArgumentException('Illegal resource type definition.');
            }
        }
        parent::__construct();
    }
    
    public function offsetSet($offset, $value) {
        if (is_array($offset)) {
            throw new DomainException('Illegal offset type.');
        }
        switch (true) {
            case $this->type & self::TYPE_BOOL       and is_bool($value):
            case $this->type & self::TYPE_INT        and is_int($value):
            case $this->type & self::TYPE_FLOAT      and is_float($value):
            case $this->type & self::TYPE_STRING     and is_string($value):
            case $this->type & self::TYPE_ARRAY      and is_array($value):
            case $this->type & self::TYPE_NULL       and is_null($value):
            case $this->type & self::TYPE_CALLABLE   and is_callable($value):
            case $this->type & self::TYPE_OBJECT     and is_object($value):
            case $this->type & self::TYPE_STRINGABLE and self::isStringable($value):
                break;
            default:
                throw new DomainException('Illegal value type.');
        }
        switch (true) {
            case
                $this->object !== null and
                is_object($value) and
                !($value instanceof $this->object):
                    throw new DomainException('Illegal object type.');
            case
                $this->resource !== null and
                is_resource($value) and
                get_resource_type($value) !== $this->resource:
                    throw new DomainException('Illegal resource type.');
        }
        parent::offsetSet($offset, $value);
    }
    
    private static function isStringable($value) {
        switch (true) {
            case is_array($value):
            case is_object($value) and !method_exists($value, '__toString'):
                return false;
            default:
                return true;
        }
    }
    
}

基本的な使い方

型が適合しなかったときには DomainException がスローされます。

整数

$array = new Collection(Collection::TYPE_INT);
$array[] = 1;

数値(整数と浮動小数点数と論理値)

$array = new Collection(Collection::TYPE_NUMBER);
$array[] = 1;
$array[] = 1.1;
$array[] = true;

コーラブル

$array = new Collection(Collection::TYPE_CALLABLE);
$array[] = 'strlen';
$array[] = function () { echo 'こんにちは世界'; };
$array[] = array(new SimpleXMLElement('<xml></xml>'), 'addChild');

オブジェクト

$array = new Collection(Collection::TYPE_OBJECT);
$array[] = new SimpleXMLElement('<xml></xml>');
$array[] = new stdClass;

特定のオブジェクト

$array = new Collection(Collection::TYPE_OBJECT, 'stdClass');
$array[] = new stdClass;

特定のリソース

$array = new Collection(Collection::TYPE_RESOURCE, null, 'stream');
$array[] = fopen('test.txt', 'wb');

文字列にキャスト可能なもの

$array = new Collection(Collection::TYPE_STRINGABLE);
$array[] = 1;
$array[] = 1.1;
$array[] = true;
$array[] = null;
$array[] = fopen('test.txt', 'wb');
$array[] = new SimpleXMLElement('<xml></xml>');

再帰的な定義

Collection を再帰的に代入していけば、Javaの ジェネリクス みたいなことがPHPでも出来るように!

$root = new Collection(Collection::TYPE_OBJECT, 'Collection');
$root['int'] = new Collection(Collection::TYPE_INT);
$root['bool'] = new Collection(Collection::TYPE_BOOL);
$root['int'][] = 1;
$root['bool'][] = true;

…よく考えたら new でインスタンス生成するタイミングで全ての再帰的な定義が終わってないとだめだよね……うーん………所詮はPHPか…………

29
23
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?