ObjectPropertyTrait.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. /**
  3. * @package Grav\Framework\Object
  4. *
  5. * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Object\Property;
  9. use InvalidArgumentException;
  10. use function array_key_exists;
  11. use function get_object_vars;
  12. use function is_callable;
  13. /**
  14. * Object Property Trait
  15. *
  16. * Stores all properties as class member variables or object properties. All properties need to be defined as protected
  17. * properties. Undefined properties will throw an error.
  18. *
  19. * Additionally you may define following methods:
  20. * - `$this->offsetLoad($offset, $value)` called first time object property gets accessed
  21. * - `$this->offsetPrepare($offset, $value)` called on every object property set
  22. * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
  23. *
  24. * @package Grav\Framework\Object\Property
  25. */
  26. trait ObjectPropertyTrait
  27. {
  28. /** @var array */
  29. private $_definedProperties;
  30. /**
  31. * @param array $elements
  32. * @param string|null $key
  33. * @throws InvalidArgumentException
  34. */
  35. public function __construct(array $elements = [], $key = null)
  36. {
  37. $this->initObjectProperties();
  38. $this->setElements($elements);
  39. $this->setKey($key ?? '');
  40. }
  41. /**
  42. * @param string $property Object property name.
  43. * @return bool True if property has been loaded.
  44. */
  45. protected function isPropertyLoaded($property)
  46. {
  47. return !empty($this->_definedProperties[$property]);
  48. }
  49. /**
  50. * @param string $offset
  51. * @param mixed $value
  52. * @return mixed
  53. */
  54. protected function offsetLoad($offset, $value)
  55. {
  56. $methodName = "offsetLoad_{$offset}";
  57. return method_exists($this, $methodName)? $this->{$methodName}($value) : $value;
  58. }
  59. /**
  60. * @param string $offset
  61. * @param mixed $value
  62. * @return mixed
  63. */
  64. protected function offsetPrepare($offset, $value)
  65. {
  66. $methodName = "offsetPrepare_{$offset}";
  67. return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
  68. }
  69. /**
  70. * @param string $offset
  71. * @param mixed $value
  72. * @return mixed
  73. */
  74. protected function offsetSerialize($offset, $value)
  75. {
  76. $methodName = "offsetSerialize_{$offset}";
  77. return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
  78. }
  79. /**
  80. * @param string $property Object property name.
  81. * @return bool True if property has been defined (can be null).
  82. */
  83. protected function doHasProperty($property)
  84. {
  85. return array_key_exists($property, $this->_definedProperties);
  86. }
  87. /**
  88. * @param string $property Object property to be fetched.
  89. * @param mixed $default Default value if property has not been set.
  90. * @param callable|bool $doCreate Set true to create variable.
  91. * @return mixed Property value.
  92. */
  93. protected function &doGetProperty($property, $default = null, $doCreate = false)
  94. {
  95. if (!array_key_exists($property, $this->_definedProperties)) {
  96. throw new InvalidArgumentException("Property '{$property}' does not exist in the object!");
  97. }
  98. if (empty($this->_definedProperties[$property])) {
  99. if ($doCreate === true) {
  100. $this->_definedProperties[$property] = true;
  101. $this->{$property} = null;
  102. } elseif (is_callable($doCreate)) {
  103. $this->_definedProperties[$property] = true;
  104. $this->{$property} = $this->offsetLoad($property, $doCreate());
  105. } else {
  106. return $default;
  107. }
  108. }
  109. return $this->{$property};
  110. }
  111. /**
  112. * @param string $property Object property to be updated.
  113. * @param mixed $value New value.
  114. * @return void
  115. * @throws InvalidArgumentException
  116. */
  117. protected function doSetProperty($property, $value)
  118. {
  119. if (!array_key_exists($property, $this->_definedProperties)) {
  120. throw new InvalidArgumentException("Property '{$property}' does not exist in the object!");
  121. }
  122. $this->_definedProperties[$property] = true;
  123. $this->{$property} = $this->offsetPrepare($property, $value);
  124. }
  125. /**
  126. * @param string $property Object property to be unset.
  127. * @return void
  128. */
  129. protected function doUnsetProperty($property)
  130. {
  131. if (!array_key_exists($property, $this->_definedProperties)) {
  132. return;
  133. }
  134. $this->_definedProperties[$property] = false;
  135. $this->{$property} = null;
  136. }
  137. /**
  138. * @return void
  139. */
  140. protected function initObjectProperties()
  141. {
  142. $this->_definedProperties = [];
  143. foreach (get_object_vars($this) as $property => $value) {
  144. if ($property[0] !== '_') {
  145. $this->_definedProperties[$property] = ($value !== null);
  146. }
  147. }
  148. }
  149. /**
  150. * @param string $property
  151. * @param mixed|null $default
  152. * @return mixed|null
  153. */
  154. protected function getElement($property, $default = null)
  155. {
  156. if (empty($this->_definedProperties[$property])) {
  157. return $default;
  158. }
  159. return $this->offsetSerialize($property, $this->{$property});
  160. }
  161. /**
  162. * @return array
  163. */
  164. protected function getElements()
  165. {
  166. $properties = array_intersect_key(get_object_vars($this), array_filter($this->_definedProperties));
  167. $elements = [];
  168. foreach ($properties as $offset => $value) {
  169. $serialized = $this->offsetSerialize($offset, $value);
  170. if ($serialized !== null) {
  171. $elements[$offset] = $this->offsetSerialize($offset, $value);
  172. }
  173. }
  174. return $elements;
  175. }
  176. /**
  177. * @param array $elements
  178. * @return void
  179. */
  180. protected function setElements(array $elements)
  181. {
  182. foreach ($elements as $property => $value) {
  183. $this->setProperty($property, $value);
  184. }
  185. }
  186. }