Pull to refresh

Полноценные коллекции в PHP

Reading time7 min
Views9.4K
Не так давно при разработке своего проекта возникла идея реализовать полноценные коллекции для хранения объектов одинакового типа, по удобству напоминающие List<Type> в C#.
Идея состоит в том, чтобы коллекции, содержащие объекты различных типов сами по себе различались, а не имели, скажем, один унифицированный тип Collection. Другими словами, коллекция объектов типа User это не то же что коллекция объектов Book. Естественно, первой мыслью было создание различных классов для коллекций (UserCollection, BookCollection, …). Но данных подход не обеспечивает нужной гибкости, плюс ко всему, нужно тратить время на объявление каждого подобного класса.
Немного поразмыслив, реализовал динамическое создание классов коллекций. Выглядит это так: пользователь создаёт коллекцию объектов типа Book, а нужный тип BookCollection создаётся (т.е. объявляется) автоматически.

Что я получил в итоге:

— Полноценный TypeHinting создаваемых типов коллекций.
— Строгая типизация коллекций.
— Возможнось обращатся к коллекции как к массиву как в C# (путём реализации интерфейса ArrayAccess)
— Полноценная итерация коллекции (возможность использования в любых циклах).

Реализация


Фабрика коллекций


  1. /**
  2. * Фабрика коллекций
  3. *
  4. * @author    [x26]VOLAND
  5. */
  6. abstract class CollectionFactory {
  7.  
  8.   /**
  9.    * Создаёт коллекцию заданного типа.
  10.    *
  11.    * @param string $type  Тип коллекции
  12.    * @return mixed
  13.    */
  14.   public static function create($type)
  15.   {
  16.     $class = $type . 'Collection';
  17.     self::__create_class($class);
  18.     $obj = new $class($type);
  19.     return $obj;
  20.   }
  21.  
  22.   /**
  23.    * Создаёт класс с именем $class
  24.    *
  25.    * @param string $class  Имя класса
  26.    * @return void
  27.    */
  28.   private static function __create_class($class) {
  29.     if ( ! class_exists($class)) {
  30.       eval('class ' . $class . ' extends Collection { }');
  31.     }
  32.   }
  33. }
* This source code was highlighted with Source Code Highlighter.


Класс коллекции (описывает поведение)


  1. /**
  2. * Класс коллекции
  3. * Базовый универсальный тип, на основе которого будут создаваться коллекции.
  4. *
  5. * @author    [x26]VOLAND
  6. */
  7. abstract class Collection implements IteratorAggregate, ArrayAccess, Countable {
  8.  
  9.   /**
  10.    * Тип элементов, хранящихся в данной коллекции.
  11.    * @var string
  12.    */
  13.   private $__type;
  14.  
  15.   /**
  16.    * Хранилище объектов
  17.    * @var array
  18.    */
  19.   private $__collection = array();
  20.  
  21.   // --------------------------------------------------------------------
  22.  
  23.   /**
  24.    * Констурктор.
  25.    * Задаёт тип элементо, которые будут хранитья в данной коллекции.
  26.    *
  27.    * @param string $type  Тип элементов
  28.    * @return void
  29.    */
  30.   public function __construct($type) {
  31.     $this->__type = $type;
  32.   }
  33.  
  34.   // --------------------------------------------------------------------
  35.  
  36.   /**
  37.    * Проверяет тип объекта.
  38.    * Препятствует добавлению в коллекцию объектов `чужого` типа.
  39.    *
  40.    * @param object $object  Объект для проверки
  41.    * @return void
  42.    * @throws Exception
  43.    */
  44.   private function __check_type(&$object) {
  45.     if (get_class($object) != $this->__type) {
  46.       throw new Exception('Объект типа `' . get_class($object)
  47.         . '` не может быть добавлен в коллекцию объектов типа `' . $this->__type . '`');
  48.     }
  49.   }
  50.  
  51.   // --------------------------------------------------------------------
  52.  
  53.   /**
  54.    * Добавляет в коллекцию объекты, переданные в аргументах.
  55.    *
  56.    * @param object(s)  Объекты
  57.    * @return mixed Collection
  58.    */
  59.   public function add()
  60.   {
  61.     $args = func_get_args();
  62.     foreach ($args as $object) {
  63.       $this->__check_type($object);
  64.       $this->__collection[] = $object;
  65.     }
  66.     return $this;
  67.   }
  68.  
  69.   // --------------------------------------------------------------------
  70.  
  71.   /**
  72.    * Удаляет из коллекции объекты, переданные в аргументах.
  73.    *
  74.    * @param object(s)  Объекты
  75.    * @return mixed Collection
  76.    */
  77.   public function remove()
  78.   {
  79.     $args = func_get_args();
  80.     foreach ($args as $object) {
  81.       unset($this->__collection[array_search($object, $this->__collection)]);
  82.     }
  83.     return $this;
  84.   }
  85.  
  86.   // --------------------------------------------------------------------
  87.  
  88.   /**
  89.    * Очищает коллекцию.
  90.    *
  91.    * @return mixed Collection
  92.    */
  93.   public function clear() {
  94.     $this->__collection = array();
  95.     return $this;
  96.   }
  97.  
  98.   // --------------------------------------------------------------------
  99.  
  100.   /**
  101.    * Выясняет, пуста ли коллекция.
  102.    *
  103.    * @return bool
  104.    */
  105.   public function isEmpty() {
  106.     return empty($this->__collection);
  107.   }
  108.  
  109.   // --------------------------------------------------------------------
  110.  
  111.   /**
  112.    * Реализация интерфейса IteratorAggregate
  113.    */
  114.  
  115.   /**
  116.    * Возвращает объект итератора.
  117.    *
  118.    * @return CollectionIterator
  119.    */
  120.   public function getIterator() {
  121.     return new CollectionIterator($this->__collection);
  122.   }
  123.  
  124.   // --------------------------------------------------------------------
  125.  
  126.  
  127.   /**
  128.    * Реализация интерфейса ArrayAccess.
  129.    */
  130.  
  131.   /**
  132.    * Sets an element of collection at the offset
  133.    *
  134.    * @param ineter $offset  Offset
  135.    * @param mixed $offset Object
  136.    * @return void
  137.    */
  138.   public function offsetSet($offset, $object) {
  139.     $this->__check_type($object);
  140.     if ($offset === NULL) {
  141.       $offset = max(array_keys($this->__collection)) + 1;
  142.     }
  143.     $this->__collection[$offset] = $object;
  144.   }
  145.  
  146.   // --------------------------------------------------------------------
  147.  
  148.   /**
  149.    * Выясняет существует ли элемент с данным ключом.
  150.    *
  151.    * @param integer $offset  Ключ
  152.    * @return bool
  153.    */
  154.   public function offsetExists($offset) {
  155.     return isset($this->__collection[$offset]);
  156.   }
  157.  
  158.   // --------------------------------------------------------------------
  159.  
  160.   /**
  161.    * Удаляет элемент, на который ссылается ключ $offset.
  162.    *
  163.    * @param integer $offset  Ключ
  164.    * @return void
  165.    */
  166.   public function offsetUnset($offset) {
  167.     unset($this->__collection[$offset]);
  168.   }
  169.  
  170.   // --------------------------------------------------------------------
  171.  
  172.   /**
  173.    * Возвращает элемент по ключу.
  174.    *
  175.    * @param integer $offset  Ключ
  176.    * @return mixed
  177.    */
  178.   public function offsetGet($offset) {
  179.     if(isset($this->__collection[$offset]) === FALSE) {
  180.       return NULL;
  181.     }
  182.     return $this->__collection[$offset];
  183.   }
  184.  
  185.   // --------------------------------------------------------------------
  186.  
  187.  
  188.   /**
  189.    * Реализация интерфейса Countable
  190.    */
  191.  
  192.   /**
  193.    * Возвращает кол-во элементов в коллекции.
  194.    *
  195.    * @return integer
  196.    */
  197.   public function count() {
  198.     return sizeof($this->__collection);
  199.   }
  200. }
* This source code was highlighted with Source Code Highlighter.


Примеры использования



  1. <?php
  2.  
  3. class BookStore {
  4.   function addBooks(BookCollection $books) {
  5.     // реализация
  6.   }
  7.  
  8.   function addMagazines(MagazineCollection $magazines) {
  9.     // реализация
  10.   }
  11.  
  12.   function addGoods(Collection $goods) {
  13.     // Если тип коллекции не важен,
  14.     // можно указать базовый тип Collection
  15.   }
  16. }
  17. class Book {
  18.   var $id;
  19.   function Book($id) {
  20.     $this->id = $id;
  21.   }
  22. }
  23.  
  24.  
  25. class Magazine {
  26.   var $id;
  27.   function Magazine($id) {
  28.     $this->id = $id;
  29.   }
  30. }
  31. // Создаём коллекцию
  32. $books = CollectionFactory::create('Book');
  33.   
  34. echo get_class($books); // BookCollection
  35.   
  36. // Добавим объектов в коллекцию:
  37. $books->add(new Book(1), new Book(2));
  38. $books->add(new Book(3))->add(new Book(2));
  39. $books[] = new Book(5);
  40.   
  41. echo count($books); // 5
  42.   
  43. ...
  44.   
  45. foreach ($books as $book) {
  46.  echo $book->id;
  47. } // 12345
  48.   
  49. ...
  50.   
  51. $books->add(new Magazine(1)); // Ошибка (неверный тип)
  52.   
  53. ...
  54.   
  55. $magazines = CollectionFactory::create('Magazine');
  56. $magazines->add(new Magazine(1));
  57.   
  58. ...
  59.   
  60. $bookStore = new BookStore();
  61.   
  62. $bookStore->addBooks($books); // Всё в порядке
  63.   
  64. $bookStore->addBooks($magazines); // Ошибка (неверный тип)
  65. $bookStore->addMagazines($magazines); // Всё в порядке
  66.   
  67. $bookStore->addGoods($books); // Всё в порядке
  68. $bookStore->addGoods($magazines); // Всё в порядке
  69.   
  70. ?>
* This source code was highlighted with Source Code Highlighter.

Скачать исходник
Tags:
Hubs:
Total votes 73: ↑45 and ↓28+17
Comments74

Articles