PHPのコレクションオブジェクト



配列の問題
PHPでは、配列は非常に柔軟なデータ構造です.これはほとんどすべてを受け入れ、好きなように組み合わせることができます.これは実際に良いです:他の言語とは異なり、PHPでは、連想配列と数字の配列を組み合わせることができますので、自由を提供します.しかし、あなたがちょうどタイプ安全性を必要とする時があります.
それに対処する一つの方法はきっと、配列のすべての項目をチェックすることです.
$array = [1,2,3,4,5]

foreach ($array as $item) {
    if (!is_int($item)) {
        throw new \Exception('wrong type');
    }

    //do your stuff here
}
これは明らかに機能していますが、ちょっと奇妙です.
  • すべての発生を因数分解するために、あなたはグローバルにアクセス可能な機能のいくらかの種類を必要とします
  • 実際に、それをチェックする間違った場所です.理想的には、配列を作成するときに値の型をチェックしたいと思います.

  • 解決策:コレクション
    コレクションは、配列をあなたのために要約するクラスです.基本的なコレクションクラスは次のようになります.
    class IntCollection {
        private $values = [];
    
        public function addValue (int $val, $key = null): void
        {
            //the typehint ensures that you won't have any non-integer values
            if ($key === null) {
                $this->values[] = $val;
            } else {
                $this->values[$key] = $val;
            }
        }
    
        public function deleteValue($key): void
        {
            unset($this->values[$key]);
        }
    
        public function get($key): int
        {
            return $this->values[$key];
        }
    }
    
    それは既にかなりクールですが、十分ではありません.例えば、
  • リファクタリングの場合、このオブジェクトで配列を置き換えるために、複数のコード部分を変更する必要があります
  • あなたはそれの項目をカウントすることはできません
  • foreachのような標準PHPコントロール構造では使えません

  • より良い解決策
    幸いにも、PHPは我々にツールを提供します.このために、コア言語インターフェイスのいくつかを使用するために、コレクションクラスをリファクタにする必要があります.
    インターフェイスは、クラスのAPI定義です.インターフェイスを実装する場合は、クラスが特定の標準化された定義済みの方法で動作することを世界に伝えます.あなたが初めてそれを見るとき、それは「抽象クラスのように」あなたを攻撃するかもしれません、しかし、それは全く正しくありません
  • インターフェイスで宣言されたすべてのメソッドはpublicでなければなりません
  • 抽象クラスには変数と実装されたメソッドを含めることができます
  • クラスは他のクラスを拡張することができますが、多くのインターフェイスとして実装することができます
  • タイプセーフコレクションを構築するには、3つのPHPインターフェイスを見ていきます

  • Countable => クラスを' count () 'のような関数で使用できるようにします

  • Iterator => あなたのオブジェクトが

  • ArrayAccess => これを実装し、配列のようなコレクションを使用することができます(例えば$ collection [$ key ])
  • 完成したものは次のようになります.
    class IntCollection implements \Countable, \Iterator, \ArrayAccess
    {
    
        private $values = [];
        private $position = 0;
    
        /**
         * This constructor is there in order to be able to create a collection with
         * its values already added
         */
        public function __construct(array $values = [])
        {
            foreach ($values as $value) {
                $this->offsetSet('', $value);
            }
        }
    
        /**
         * Implementation of method declared in \Countable.
         * Provides support for count()
         */
        public function count()
        {
            return count($this->values);
        }
    
        /**
         * Implementation of method declared in \Iterator
         * Resets the internal cursor to the beginning of the array
         */
        public function rewind()
        {
            $this->position = 0;
        }
    
        /**
         * Implementation of method declared in \Iterator
         * Used to get the current key (as for instance in a foreach()-structure
         */
        public function key()
        {
            return $this->position;
        }
    
        /**
         * Implementation of method declared in \Iterator
         * Used to get the value at the current cursor position
         */
        public function current()
        {
            return $this->values[$this->position];
        }
    
        /**
         * Implementation of method declared in \Iterator
         * Used to move the cursor to the next position
         */
        public function next()
        {
            $this->position++;
        }
    
        /**
         * Implementation of method declared in \Iterator
         * Checks if the current cursor position is valid
         */
        public function valid()
        {
            return isset($this->values[$this->position]);
        }
    
        /**
         * Implementation of method declared in \ArrayAccess
         * Used to be able to use functions like isset()
         */
        public function offsetExists($offset)
        {
            return isset($this->values[$offset]);
        }
    
        /**
         * Implementation of method declared in \ArrayAccess
         * Used for direct access array-like ($collection[$offset]);
         */
        public function offsetGet($offset)
        {
            return $this->values[$offset];
        }
    
        /**
         * Implementation of method declared in \ArrayAccess
         * Used for direct setting of values
         */
        public function offsetSet($offset, $value)
        {
            if (!is_int($value)) {
                throw new \InvalidArgumentException("Must be an int");
            }
    
            if (empty($offset)) { //this happens when you do $collection[] = 1;
                $this->values[] = $value;
            } else {
                $this->values[$offset] = $value;
            }
        }
    
        /**
         * Implementation of method declared in \ArrayAccess
         * Used for unset()
         */
        public function offsetUnset($offset)
        {
            unset($this->values[$offset]);
        }
    }
    

    より良い解決策:SPLの使用
    The SPL あなたが見ているならば、あなたが多くの非常に役に立つものを見つけるPHPからの中心的なライブラリです.残念ながら、それが有用であるように、それはほとんど広く知られていません.ここでの問題については、ArrayObjectというクラスが含まれています.これは、以前に手で行ったもの全てをシリアル化しています.オブジェクトを拡張することで、私たちの最終コレクションクラスは文字通り時間通りに書かれます.
    class IntCollection extends \ArrayObject
    {
        public function offsetSet($index, $newval)
        {
            if (!is_int($newval)) {
                throw new \InvalidArgumentException("Must be int");
            }
    
            parent::offsetSet($index, $newval);
        }
    }