GenericArrayObject.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. /**
  3. * Extends ArrayObject and does two things:
  4. *
  5. * Allows for deriving classes to easily intercept additions
  6. * and deletions for purposes such as additional indexing.
  7. *
  8. * Enforces the objects to be of a certain type, so this
  9. * can be replied upon, much like if this had true support
  10. * for generics, which sadly enough is not possible in PHP.
  11. *
  12. * This program is free software; you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation; either version 2 of the License, or
  15. * (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License along
  23. * with this program; if not, write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  25. * http://www.gnu.org/copyleft/gpl.html
  26. *
  27. * @since 1.20
  28. *
  29. * @file
  30. *
  31. * @license GPL-2.0-or-later
  32. * @author Jeroen De Dauw < jeroendedauw@gmail.com >
  33. */
  34. abstract class GenericArrayObject extends ArrayObject {
  35. /**
  36. * Returns the name of an interface/class that the element should implement/extend.
  37. *
  38. * @since 1.20
  39. *
  40. * @return string
  41. */
  42. abstract public function getObjectType();
  43. /**
  44. * @see SiteList::getNewOffset()
  45. * @since 1.20
  46. * @var int
  47. */
  48. protected $indexOffset = 0;
  49. /**
  50. * Finds a new offset for when appending an element.
  51. * The base class does this, so it would be better to integrate,
  52. * but there does not appear to be any way to do this...
  53. *
  54. * @since 1.20
  55. *
  56. * @return int
  57. */
  58. protected function getNewOffset() {
  59. while ( $this->offsetExists( $this->indexOffset ) ) {
  60. $this->indexOffset++;
  61. }
  62. return $this->indexOffset;
  63. }
  64. /**
  65. * @see ArrayObject::__construct
  66. *
  67. * @since 1.20
  68. *
  69. * @param null|array $input
  70. * @param int $flags
  71. * @param string $iterator_class
  72. */
  73. public function __construct( $input = null, $flags = 0, $iterator_class = 'ArrayIterator' ) {
  74. parent::__construct( [], $flags, $iterator_class );
  75. if ( !is_null( $input ) ) {
  76. foreach ( $input as $offset => $value ) {
  77. $this->offsetSet( $offset, $value );
  78. }
  79. }
  80. }
  81. /**
  82. * @see ArrayObject::append
  83. *
  84. * @since 1.20
  85. *
  86. * @param mixed $value
  87. */
  88. public function append( $value ) {
  89. $this->setElement( null, $value );
  90. }
  91. /**
  92. * @see ArrayObject::offsetSet()
  93. *
  94. * @since 1.20
  95. *
  96. * @param mixed $index
  97. * @param mixed $value
  98. */
  99. public function offsetSet( $index, $value ) {
  100. $this->setElement( $index, $value );
  101. }
  102. /**
  103. * Returns if the provided value has the same type as the elements
  104. * that can be added to this ArrayObject.
  105. *
  106. * @since 1.20
  107. *
  108. * @param mixed $value
  109. *
  110. * @return bool
  111. */
  112. protected function hasValidType( $value ) {
  113. $class = $this->getObjectType();
  114. return $value instanceof $class;
  115. }
  116. /**
  117. * Method that actually sets the element and holds
  118. * all common code needed for set operations, including
  119. * type checking and offset resolving.
  120. *
  121. * If you want to do additional indexing or have code that
  122. * otherwise needs to be executed whenever an element is added,
  123. * you can overload @see preSetElement.
  124. *
  125. * @since 1.20
  126. *
  127. * @param mixed $index
  128. * @param mixed $value
  129. *
  130. * @throws InvalidArgumentException
  131. */
  132. protected function setElement( $index, $value ) {
  133. if ( !$this->hasValidType( $value ) ) {
  134. throw new InvalidArgumentException(
  135. 'Can only add ' . $this->getObjectType() . ' implementing objects to '
  136. . static::class . '.'
  137. );
  138. }
  139. if ( is_null( $index ) ) {
  140. $index = $this->getNewOffset();
  141. }
  142. if ( $this->preSetElement( $index, $value ) ) {
  143. parent::offsetSet( $index, $value );
  144. }
  145. }
  146. /**
  147. * Gets called before a new element is added to the ArrayObject.
  148. *
  149. * At this point the index is always set (ie not null) and the
  150. * value is always of the type returned by @see getObjectType.
  151. *
  152. * Should return a boolean. When false is returned the element
  153. * does not get added to the ArrayObject.
  154. *
  155. * @since 1.20
  156. *
  157. * @param int|string $index
  158. * @param mixed $value
  159. *
  160. * @return bool
  161. */
  162. protected function preSetElement( $index, $value ) {
  163. return true;
  164. }
  165. /**
  166. * @see Serializable::serialize
  167. *
  168. * @since 1.20
  169. *
  170. * @return string
  171. */
  172. public function serialize() {
  173. return serialize( $this->getSerializationData() );
  174. }
  175. /**
  176. * Returns an array holding all the data that should go into serialization calls.
  177. * This is intended to allow overloading without having to reimplement the
  178. * behavior of this base class.
  179. *
  180. * @since 1.20
  181. *
  182. * @return array
  183. */
  184. protected function getSerializationData() {
  185. return [
  186. 'data' => $this->getArrayCopy(),
  187. 'index' => $this->indexOffset,
  188. ];
  189. }
  190. /**
  191. * @see Serializable::unserialize
  192. *
  193. * @since 1.20
  194. *
  195. * @param string $serialization
  196. *
  197. * @return array
  198. * @suppress PhanParamSignatureMismatchInternal The stub appears to be wrong
  199. */
  200. public function unserialize( $serialization ) {
  201. $serializationData = unserialize( $serialization );
  202. foreach ( $serializationData['data'] as $offset => $value ) {
  203. // Just set the element, bypassing checks and offset resolving,
  204. // as these elements have already gone through this.
  205. parent::offsetSet( $offset, $value );
  206. }
  207. $this->indexOffset = $serializationData['index'];
  208. return $serializationData;
  209. }
  210. /**
  211. * Returns if the ArrayObject has no elements.
  212. *
  213. * @since 1.20
  214. *
  215. * @return bool
  216. */
  217. public function isEmpty() {
  218. return $this->count() === 0;
  219. }
  220. }