AbstractOptions.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. declare(strict_types=1);
  3. namespace Laminas\Stdlib;
  4. use Traversable;
  5. use function array_shift;
  6. use function get_object_vars;
  7. use function is_array;
  8. use function is_callable;
  9. use function method_exists;
  10. use function preg_replace_callback;
  11. use function sprintf;
  12. use function str_replace;
  13. use function strtolower;
  14. use function ucwords;
  15. /**
  16. * @template TValue
  17. * @implements ParameterObjectInterface<string, TValue>
  18. */
  19. abstract class AbstractOptions implements ParameterObjectInterface
  20. {
  21. // phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore,WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCapsProperty
  22. /**
  23. * We use the __ prefix to avoid collisions with properties in
  24. * user-implementations.
  25. *
  26. * @var bool
  27. */
  28. protected $__strictMode__ = true;
  29. // phpcs:enable
  30. /**
  31. * Constructor
  32. *
  33. * @param iterable<string, TValue>|AbstractOptions<TValue>|null $options
  34. */
  35. public function __construct($options = null)
  36. {
  37. if (null !== $options) {
  38. $this->setFromArray($options);
  39. }
  40. }
  41. /**
  42. * Set one or more configuration properties
  43. *
  44. * @param iterable<string, TValue>|AbstractOptions<TValue> $options
  45. * @throws Exception\InvalidArgumentException
  46. * @return AbstractOptions Provides fluent interface
  47. */
  48. public function setFromArray($options)
  49. {
  50. if ($options instanceof self) {
  51. $options = $options->toArray();
  52. }
  53. if (! is_array($options) && ! $options instanceof Traversable) {
  54. throw new Exception\InvalidArgumentException(
  55. sprintf(
  56. 'Parameter provided to %s must be an %s, %s or %s',
  57. __METHOD__,
  58. 'array',
  59. 'Traversable',
  60. self::class
  61. )
  62. );
  63. }
  64. foreach ($options as $key => $value) {
  65. $this->__set($key, $value);
  66. }
  67. return $this;
  68. }
  69. /**
  70. * Cast to array
  71. *
  72. * @return array<string, TValue>
  73. */
  74. public function toArray()
  75. {
  76. $array = [];
  77. $transform = static function (array $letters): string {
  78. /** @var list<string> $letters */
  79. $letter = array_shift($letters);
  80. return '_' . strtolower($letter);
  81. };
  82. /** @psalm-var TValue $value */
  83. foreach (get_object_vars($this) as $key => $value) {
  84. if ($key === '__strictMode__') {
  85. continue;
  86. }
  87. $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key);
  88. $array[$normalizedKey] = $value;
  89. }
  90. return $array;
  91. }
  92. /**
  93. * Set a configuration property
  94. *
  95. * @see ParameterObject::__set()
  96. *
  97. * @param string $key
  98. * @param TValue|null $value
  99. * @throws Exception\BadMethodCallException
  100. * @return void
  101. */
  102. public function __set($key, $value)
  103. {
  104. $setter = 'set' . str_replace('_', '', $key);
  105. if (is_callable([$this, $setter])) {
  106. $this->{$setter}($value);
  107. return;
  108. }
  109. if ($this->__strictMode__) {
  110. throw new Exception\BadMethodCallException(sprintf(
  111. 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined',
  112. $key,
  113. 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))),
  114. $setter
  115. ));
  116. }
  117. }
  118. /**
  119. * Get a configuration property
  120. *
  121. * @see ParameterObject::__get()
  122. *
  123. * @param string $key
  124. * @throws Exception\BadMethodCallException
  125. * @return TValue
  126. */
  127. public function __get($key)
  128. {
  129. $getter = 'get' . str_replace('_', '', $key);
  130. if (is_callable([$this, $getter])) {
  131. return $this->{$getter}();
  132. }
  133. throw new Exception\BadMethodCallException(sprintf(
  134. 'The option "%s" does not have a callable "%s" getter method which must be defined',
  135. $key,
  136. 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))
  137. ));
  138. }
  139. /**
  140. * Test if a configuration property is null
  141. *
  142. * @see ParameterObject::__isset()
  143. *
  144. * @param string $key
  145. * @return bool
  146. */
  147. public function __isset($key)
  148. {
  149. $getter = 'get' . str_replace('_', '', $key);
  150. return method_exists($this, $getter) && null !== $this->__get($key);
  151. }
  152. /**
  153. * Set a configuration property to NULL
  154. *
  155. * @see ParameterObject::__unset()
  156. *
  157. * @param string $key
  158. * @throws Exception\InvalidArgumentException
  159. * @return void
  160. */
  161. public function __unset($key)
  162. {
  163. try {
  164. $this->__set($key, null);
  165. } catch (Exception\BadMethodCallException $e) {
  166. throw new Exception\InvalidArgumentException(
  167. 'The class property $' . $key . ' cannot be unset as'
  168. . ' NULL is an invalid value for it',
  169. 0,
  170. $e
  171. );
  172. }
  173. }
  174. }