Php data collection class

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

Не так давно при разработке своего проекта возникла идея реализовать полноценные коллекции для хранения объектов одинакового типа, по удобству напоминающие List в 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. * @param string $type Тип коллекции
  11. * @return mixed
  12. */
  13. public static function create($type)
  14. $ class = $type . ‘Collection’ ;
  15. self::__create_class($ class );
  16. $obj = new $ class ($type);
  17. return $obj;
  18. >
  19. /**
  20. * Создаёт класс с именем $class
  21. *
  22. * @param string $class Имя класса
  23. * @return void
  24. */
  25. private static function __create_class($ class )
  26. if ( ! class_exists($ class ))
  27. eval( ‘class ‘ . $ class . ‘ extends Collection < >‘ );
  28. >
  29. >
  30. >

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

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

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

  1. class BookStore
  2. function addBooks(BookCollection $books)
  3. // реализация
  4. >
  5. function addMagazines(MagazineCollection $magazines)
  6. // реализация
  7. >
  8. function addGoods(Collection $goods)
  9. // Если тип коллекции не важен,
  10. // можно указать базовый тип Collection
  11. >
  12. >
  13. class Book
  14. var $id;
  15. function Book($id)
  16. $ this ->id = $id;
  17. >
  18. >
  19. class Magazine
  20. var $id;
  21. function Magazine($id)
  22. $ this ->id = $id;
  23. >
  24. >
  25. // Создаём коллекцию
  26. $books = CollectionFactory::create( ‘Book’ );
  27. echo get_class($books); // BookCollection
  28. // Добавим объектов в коллекцию:
  29. $books->add( new Book(1), new Book(2));
  30. $books->add( new Book(3))->add( new Book(2));
  31. $books[] = new Book(5);
  32. echo count($books); // 5
  33. .
  34. foreach ($books as $book)
  35. echo $book->id;
  36. > // 12345
  37. .
  38. $books->add( new Magazine(1)); // Ошибка (неверный тип)
  39. .
  40. $magazines = CollectionFactory::create( ‘Magazine’ );
  41. $magazines->add( new Magazine(1));
  42. .
  43. $bookStore = new BookStore();
  44. $bookStore->addBooks($books); // Всё в порядке
  45. $bookStore->addBooks($magazines); // Ошибка (неверный тип)
  46. $bookStore->addMagazines($magazines); // Всё в порядке
  47. $bookStore->addGoods($books); // Всё в порядке
  48. $bookStore->addGoods($magazines); // Всё в порядке
  49. ?>

Источник

Collections in PHP

For many applications, you want to create and manage groups of related objects. Unfortunately, generics will never become part of the PHP programming language. For this reason I thought again about how at least a strictly typed list with specific data types or objects can be implemented, even without generics.

I already wrote a blog post about this topic a few years ago, but this time I want to present an even simpler approach.

As goal, I have defined the following criteria.

  • No dependencies (no library must be used)
  • Write as less code as possible
  • No fancy language features should be used
  • It must be type safe
  • The collection must be iterable using foreach
  • It must be compatible with the PHPStorm code completion feature
  • It must comply PHPStan level 9

Implementing a String Collection

For the sake of simplicity, I want to start with string collection.

The idea is to create a class that is able to add new items to an internal array of strings. For this purpose, I add a method called add .

 namespace Example; class StringCollection  /** @var string[] */ private array $list = []; public function add(string $string): void  $this->list[] = $string; > > 

The next challenge is to read the list of strings using a foreach loop.

To achieve this, we need to implement a method that returns something that can be iterated. Luckily, PHP itself provides the IteratorAggregate interface for such an use case.

The implementation is quite simple, add the IteratorAggregate to your collection class and implement the getIterator method.

 namespace Example; use ArrayIterator; use IteratorAggregate; use Traversable; class StringCollection implements IteratorAggregate  /** @var string[] */ private array $list = []; public function add(string $string): void  $this->list[] = $string; > public function getIterator(): Traversable  return new ArrayIterator($this->list); > > 

This ArrayIterator allows us iterating over arrays and objects. In this case we are iterating over an array of strings.

The current implementation would already work, but PHPStan would now complain with the following error message:

Class StringCollection implements generic interface IteratorAggregate but does not specify its types: TKey, TValue You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your phpstan.neon. 

To fix this, we need to tell PHPStan which type of collection we are dealing with by adding a DocBlock to that class.

/** * @implements IteratorAggregate */ 

This means that our collection returns a list of strings as value and an integer as key. The complete collection class, with a namespace, then looks like this:

 namespace Example; use ArrayIterator; use IteratorAggregate; use Traversable; /** * @implements IteratorAggregate */ class StringCollection implements IteratorAggregate  /** @var string[] */ private array $list = []; public function add(string $string): void  $this->list[] = $string; > public function getIterator(): Traversable  return new ArrayIterator($this->list); > > 

That’s it, only 26 lines of code for the implementation of a strictly typed and iterable collection class.

The usage of the collection class is quite simple:

 $strings = new StringCollection(); $strings->add('foo'); $strings->add('bar'); foreach ($strings as $value)  echo $value . "\n"; > 

Implementing an Object Collection

That approach works with objects too.

Let’s say you want to implement a collection for a class called User :

 namespace Example; class User  public string $username; public string $email; > 

The Collection class can be implemented as follows:

 namespace Example; use ArrayIterator; use IteratorAggregate; use Traversable; /** * @implements IteratorAggregate */ class UserCollection implements IteratorAggregate  /** @var User[] */ private array $list = []; public function add(User $user): void  $this->list[] = $user; > public function getIterator(): Traversable  return new ArrayIterator($this->list); > > 
 $users = new UserCollection(); $users->add(new User()); $users->add(new User()); foreach ($users as $user)  // . > 

Note that this is just an pseudo example. You need to fill the User objects with real data before you add it to the collection. How you pass the data into your objects depends on your specific implementation.

Conclusion

With this simple but effective approach it is possible to fulfill the criteria mentioned above. I hope that this could inspire some people to use collection classes instead of arrays in the future.

Read more

Источник

Читайте также:  Java integer parseint source
Оцените статью