Data.php 8.0 KB

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