Data.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <?php
  2. /**
  3. * @package Grav\Common\Data
  4. *
  5. * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Data;
  9. use ArrayAccess;
  10. use Exception;
  11. use JsonSerializable;
  12. use RocketTheme\Toolbox\ArrayTraits\Countable;
  13. use RocketTheme\Toolbox\ArrayTraits\Export;
  14. use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
  15. use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
  16. use RocketTheme\Toolbox\File\FileInterface;
  17. use RuntimeException;
  18. use function func_get_args;
  19. use function is_array;
  20. use function is_callable;
  21. use function is_object;
  22. /**
  23. * Class Data
  24. * @package Grav\Common\Data
  25. */
  26. class Data implements DataInterface, ArrayAccess, \Countable, JsonSerializable, ExportInterface
  27. {
  28. use NestedArrayAccessWithGetters, Countable, Export;
  29. /** @var string */
  30. protected $gettersVariable = 'items';
  31. /** @var array */
  32. protected $items;
  33. /** @var Blueprint|callable|null */
  34. protected $blueprints;
  35. /** @var FileInterface|null */
  36. protected $storage;
  37. /** @var bool */
  38. private $missingValuesAsNull = false;
  39. /** @var bool */
  40. private $keepEmptyValues = true;
  41. /**
  42. * @param array $items
  43. * @param Blueprint|callable|null $blueprints
  44. */
  45. public function __construct(array $items = [], $blueprints = null)
  46. {
  47. $this->items = $items;
  48. if (null !== $blueprints) {
  49. $this->blueprints = $blueprints;
  50. }
  51. }
  52. /**
  53. * @param bool $value
  54. * @return $this
  55. */
  56. public function setKeepEmptyValues(bool $value)
  57. {
  58. $this->keepEmptyValues = $value;
  59. return $this;
  60. }
  61. /**
  62. * @param bool $value
  63. * @return $this
  64. */
  65. public function setMissingValuesAsNull(bool $value)
  66. {
  67. $this->missingValuesAsNull = $value;
  68. return $this;
  69. }
  70. /**
  71. * Get value by using dot notation for nested arrays/objects.
  72. *
  73. * @example $value = $data->value('this.is.my.nested.variable');
  74. *
  75. * @param string $name Dot separated path to the requested value.
  76. * @param mixed $default Default value (or null).
  77. * @param string $separator Separator, defaults to '.'
  78. * @return mixed Value.
  79. */
  80. public function value($name, $default = null, $separator = '.')
  81. {
  82. return $this->get($name, $default, $separator);
  83. }
  84. /**
  85. * Join nested values together by using blueprints.
  86. *
  87. * @param string $name Dot separated path to the requested value.
  88. * @param mixed $value Value to be joined.
  89. * @param string $separator Separator, defaults to '.'
  90. * @return $this
  91. * @throws RuntimeException
  92. */
  93. public function join($name, $value, $separator = '.')
  94. {
  95. $old = $this->get($name, null, $separator);
  96. if ($old !== null) {
  97. if (!is_array($old)) {
  98. throw new RuntimeException('Value ' . $old);
  99. }
  100. if (is_object($value)) {
  101. $value = (array) $value;
  102. } elseif (!is_array($value)) {
  103. throw new RuntimeException('Value ' . $value);
  104. }
  105. $value = $this->blueprints()->mergeData($old, $value, $name, $separator);
  106. }
  107. $this->set($name, $value, $separator);
  108. return $this;
  109. }
  110. /**
  111. * Get nested structure containing default values defined in the blueprints.
  112. *
  113. * Fields without default value are ignored in the list.
  114. * @return array
  115. */
  116. public function getDefaults()
  117. {
  118. return $this->blueprints()->getDefaults();
  119. }
  120. /**
  121. * Set default values by using blueprints.
  122. *
  123. * @param string $name Dot separated path to the requested value.
  124. * @param mixed $value Value to be joined.
  125. * @param string $separator Separator, defaults to '.'
  126. * @return $this
  127. */
  128. public function joinDefaults($name, $value, $separator = '.')
  129. {
  130. if (is_object($value)) {
  131. $value = (array) $value;
  132. }
  133. $old = $this->get($name, null, $separator);
  134. if ($old !== null) {
  135. $value = $this->blueprints()->mergeData($value, $old, $name, $separator);
  136. }
  137. $this->set($name, $value, $separator);
  138. return $this;
  139. }
  140. /**
  141. * Get value from the configuration and join it with given data.
  142. *
  143. * @param string $name Dot separated path to the requested value.
  144. * @param array|object $value Value to be joined.
  145. * @param string $separator Separator, defaults to '.'
  146. * @return array
  147. * @throws RuntimeException
  148. */
  149. public function getJoined($name, $value, $separator = '.')
  150. {
  151. if (is_object($value)) {
  152. $value = (array) $value;
  153. } elseif (!is_array($value)) {
  154. throw new RuntimeException('Value ' . $value);
  155. }
  156. $old = $this->get($name, null, $separator);
  157. if ($old === null) {
  158. // No value set; no need to join data.
  159. return $value;
  160. }
  161. if (!is_array($old)) {
  162. throw new RuntimeException('Value ' . $old);
  163. }
  164. // Return joined data.
  165. return $this->blueprints()->mergeData($old, $value, $name, $separator);
  166. }
  167. /**
  168. * Merge two configurations together.
  169. *
  170. * @param array $data
  171. * @return $this
  172. */
  173. public function merge(array $data)
  174. {
  175. $this->items = $this->blueprints()->mergeData($this->items, $data);
  176. return $this;
  177. }
  178. /**
  179. * Set default values to the configuration if variables were not set.
  180. *
  181. * @param array $data
  182. * @return $this
  183. */
  184. public function setDefaults(array $data)
  185. {
  186. $this->items = $this->blueprints()->mergeData($data, $this->items);
  187. return $this;
  188. }
  189. /**
  190. * Validate by blueprints.
  191. *
  192. * @return $this
  193. * @throws Exception
  194. */
  195. public function validate()
  196. {
  197. $this->blueprints()->validate($this->items);
  198. return $this;
  199. }
  200. /**
  201. * @return $this
  202. */
  203. public function filter()
  204. {
  205. $args = func_get_args();
  206. $missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
  207. $keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
  208. $this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
  209. return $this;
  210. }
  211. /**
  212. * Get extra items which haven't been defined in blueprints.
  213. *
  214. * @return array
  215. */
  216. public function extra()
  217. {
  218. return $this->blueprints()->extra($this->items);
  219. }
  220. /**
  221. * Return blueprints.
  222. *
  223. * @return Blueprint
  224. */
  225. public function blueprints()
  226. {
  227. if (null === $this->blueprints) {
  228. $this->blueprints = new Blueprint();
  229. } elseif (is_callable($this->blueprints)) {
  230. // Lazy load blueprints.
  231. $blueprints = $this->blueprints;
  232. $this->blueprints = $blueprints();
  233. }
  234. return $this->blueprints;
  235. }
  236. /**
  237. * Save data if storage has been defined.
  238. *
  239. * @return void
  240. * @throws RuntimeException
  241. */
  242. public function save()
  243. {
  244. $file = $this->file();
  245. if ($file) {
  246. $file->save($this->items);
  247. }
  248. }
  249. /**
  250. * Returns whether the data already exists in the storage.
  251. *
  252. * NOTE: This method does not check if the data is current.
  253. *
  254. * @return bool
  255. */
  256. public function exists()
  257. {
  258. $file = $this->file();
  259. return $file && $file->exists();
  260. }
  261. /**
  262. * Return unmodified data as raw string.
  263. *
  264. * NOTE: This function only returns data which has been saved to the storage.
  265. *
  266. * @return string
  267. */
  268. public function raw()
  269. {
  270. $file = $this->file();
  271. return $file ? $file->raw() : '';
  272. }
  273. /**
  274. * Set or get the data storage.
  275. *
  276. * @param FileInterface|null $storage Optionally enter a new storage.
  277. * @return FileInterface|null
  278. */
  279. public function file(FileInterface $storage = null)
  280. {
  281. if ($storage) {
  282. $this->storage = $storage;
  283. }
  284. return $this->storage;
  285. }
  286. /**
  287. * @return array
  288. */
  289. #[\ReturnTypeWillChange]
  290. public function jsonSerialize()
  291. {
  292. return $this->items;
  293. }
  294. }