/vendor/rockettheme/toolbox/Blueprints/src/Blueprints.php

https://gitlab.com/asun89/socianovation-web · PHP · 400 lines · 208 code · 43 blank · 149 comment · 47 complexity · ee3c93ba0abdac2b2cdf30e6ba8b0179 MD5 · raw file

  1. <?php
  2. namespace RocketTheme\Toolbox\Blueprints;
  3. /**
  4. * Blueprints can be used to define a data structure.
  5. *
  6. * @package RocketTheme\Toolbox\Blueprints
  7. * @author RocketTheme
  8. * @license MIT
  9. */
  10. class Blueprints
  11. {
  12. /**
  13. * @var array
  14. */
  15. protected $items = [];
  16. /**
  17. * @var array
  18. */
  19. protected $rules = [];
  20. /**
  21. * @var array
  22. */
  23. protected $nested = [];
  24. /**
  25. * @var array
  26. */
  27. protected $dynamic = [];
  28. /**
  29. * @var array
  30. */
  31. protected $filter = ['validation' => true];
  32. /**
  33. * Constructor.
  34. *
  35. * @param array $serialized Serialized content if available.
  36. */
  37. public function __construct(array $serialized = null)
  38. {
  39. if ($serialized) {
  40. $this->items = (array) $serialized['items'];
  41. $this->rules = (array) $serialized['rules'];
  42. $this->nested = (array) $serialized['nested'];
  43. $this->dynamic = (array) $serialized['dynamic'];
  44. $this->filter = (array) $serialized['filter'];
  45. }
  46. }
  47. /**
  48. * Initialize blueprints with its dynamic fields.
  49. *
  50. * @return $this
  51. */
  52. public function init()
  53. {
  54. foreach ($this->dynamic as $key => $data) {
  55. $field = &$this->items[$key];
  56. foreach ($data as $property => $call) {
  57. $func = $call['function'];
  58. $value = $call['params'];
  59. list($o, $f) = preg_split('/::/', $func);
  60. if (!$f && function_exists($o)) {
  61. $data = call_user_func_array($o, $value);
  62. } elseif ($f && method_exists($o, $f)) {
  63. $data = call_user_func_array(array($o, $f), $value);
  64. }
  65. // If function returns a value,
  66. if (isset($data)) {
  67. if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
  68. // Combine field and @data-field together.
  69. $field[$property] += $data;
  70. } else {
  71. // Or create/replace field with @data-field.
  72. $field[$property] = $data;
  73. }
  74. }
  75. }
  76. }
  77. return $this;
  78. }
  79. /**
  80. * Set filter for inherited properties.
  81. *
  82. * @param array $filter List of field names to be inherited.
  83. */
  84. public function setFilter(array $filter)
  85. {
  86. $this->filter = array_flip($filter);
  87. }
  88. /**
  89. * Get value by using dot notation for nested arrays/objects.
  90. *
  91. * @example $value = $data->get('this.is.my.nested.variable');
  92. *
  93. * @param string $name Dot separated path to the requested value.
  94. * @param mixed $default Default value (or null).
  95. * @param string $separator Separator, defaults to '.'
  96. *
  97. * @return mixed Value.
  98. */
  99. public function get($name, $default = null, $separator = '.')
  100. {
  101. $name = $separator != '.' ? strtr($name, $separator, '.') : $name;
  102. return isset($this->items[$name]) ? $this->items[$name] : $default;
  103. }
  104. /**
  105. * Set value by using dot notation for nested arrays/objects.
  106. *
  107. * @example $value = $data->set('this.is.my.nested.variable', $newField);
  108. *
  109. * @param string $name Dot separated path to the requested value.
  110. * @param mixed $value New value.
  111. * @param string $separator Separator, defaults to '.'
  112. */
  113. public function set($name, $value, $separator = '.')
  114. {
  115. $name = $separator != '.' ? strtr($name, $separator, '.') : $name;
  116. $this->items[$name] = $value;
  117. $this->addProperty($name);
  118. }
  119. /**
  120. * Define value by using dot notation for nested arrays/objects.
  121. *
  122. * @example $value = $data->set('this.is.my.nested.variable', true);
  123. *
  124. * @param string $name Dot separated path to the requested value.
  125. * @param mixed $value New value.
  126. * @param string $separator Separator, defaults to '.'
  127. */
  128. public function def($name, $value, $separator = '.')
  129. {
  130. $this->set($name, $this->get($name, $value, $separator), $separator);
  131. }
  132. /**
  133. * Convert object into an array.
  134. *
  135. * @return array
  136. */
  137. public function toArray()
  138. {
  139. return ['items' => $this->items, 'rules' => $this->rules, 'nested' => $this->nested, 'dynamic' => $this->dynamic, 'filter' => $this->filter];
  140. }
  141. /**
  142. * Get nested structure containing default values defined in the blueprints.
  143. *
  144. * Fields without default value are ignored in the list.
  145. *
  146. * @return array
  147. */
  148. public function getDefaults()
  149. {
  150. return $this->buildDefaults($this->nested);
  151. }
  152. /**
  153. * Embed an array to the blueprint.
  154. *
  155. * @param $name
  156. * @param array $value
  157. * @param string $separator
  158. * @return $this
  159. */
  160. public function embed($name, array $value, $separator = '.')
  161. {
  162. if (isset($value['rules'])) {
  163. $this->rules = array_merge($this->rules, $value['rules']);
  164. }
  165. if (!isset($value['form']['fields']) || !is_array($value['form']['fields'])) {
  166. return $this;
  167. }
  168. $prefix = $name ? ($separator != '.' ? strtr($name, $separator, '.') : $name) . '.' : '';
  169. $params = array_intersect_key($this->filter, $value);
  170. $this->parseFormFields($value['form']['fields'], $params, $prefix);
  171. return $this;
  172. }
  173. /**
  174. * Merge two arrays by using blueprints.
  175. *
  176. * @param array $data1
  177. * @param array $data2
  178. * @param string $name Optional
  179. * @param string $separator Optional
  180. * @return array
  181. */
  182. public function mergeData(array $data1, array $data2, $name = null, $separator = '.')
  183. {
  184. $nested = $this->getProperty($name, $separator);
  185. return $this->mergeArrays($data1, $data2, $nested);
  186. }
  187. /**
  188. * @param array $nested
  189. * @return array
  190. */
  191. protected function buildDefaults(array &$nested)
  192. {
  193. $defaults = [];
  194. foreach ($nested as $key => $value) {
  195. if ($key === '*') {
  196. // TODO: Add support for adding defaults to collections.
  197. continue;
  198. }
  199. if (is_array($value)) {
  200. // Recursively fetch the items.
  201. $list = $this->buildDefaults($value);
  202. // Only return defaults if there are any.
  203. if (!empty($list)) {
  204. $defaults[$key] = $list;
  205. }
  206. } else {
  207. // We hit a field; get default from it if it exists.
  208. $item = $this->get($value);
  209. // Only return default value if it exists.
  210. if (isset($item['default'])) {
  211. $defaults[$key] = $item['default'];
  212. }
  213. }
  214. }
  215. return $defaults;
  216. }
  217. /**
  218. * @param array $data1
  219. * @param array $data2
  220. * @param array $rules
  221. * @return array
  222. * @internal
  223. */
  224. protected function mergeArrays(array $data1, array $data2, array $rules)
  225. {
  226. foreach ($data2 as $key => $field) {
  227. $val = isset($rules[$key]) ? $rules[$key] : null;
  228. $rule = is_string($val) ? $this->items[$val] : null;
  229. if ($rule && $rule['type'] === '_parent' || (array_key_exists($key, $data1) && is_array($data1[$key]) && is_array($field) && is_array($val) && !isset($val['*']))) {
  230. // Array has been defined in blueprints and is not a collection of items.
  231. $data1[$key] = $this->mergeArrays($data1[$key], $field, $val);
  232. } else {
  233. // Otherwise just take value from the data2.
  234. $data1[$key] = $field;
  235. }
  236. }
  237. return $data1;
  238. }
  239. /**
  240. * Gets all field definitions from the blueprints.
  241. *
  242. * @param array $fields
  243. * @param array $params
  244. * @param string $prefix
  245. * @param string $parent
  246. * @internal
  247. */
  248. protected function parseFormFields(array &$fields, array $params, $prefix = '', $parent = '')
  249. {
  250. // Go though all the fields in current level.
  251. foreach ($fields as $key => &$field) {
  252. // Set name from the array key.
  253. if ($key && $key[0] == '.') {
  254. $key = ($parent ?: rtrim($prefix, '.')) . $key;
  255. } else {
  256. $key = $prefix . $key;
  257. }
  258. $field['name'] = $key;
  259. $field += $params;
  260. if (isset($field['fields'])) {
  261. $isArray = !empty($field['array']);
  262. // Recursively get all the nested fields.
  263. $newParams = array_intersect_key($this->filter, $field);
  264. $this->parseFormFields($field['fields'], $newParams, $prefix, $key . ($isArray ? '.*': ''));
  265. } else {
  266. // Add rule.
  267. $path = explode('.', $key);
  268. array_pop($path);
  269. $parent = '';
  270. foreach ($path as $part) {
  271. $parent .= ($parent ? '.' : '') . $part;
  272. if (!isset($this->items[$parent])) {
  273. $this->items[$parent] = ['type' => '_parent', 'name' => $parent];
  274. }
  275. }
  276. $this->items[$key] = &$field;
  277. $this->addProperty($key);
  278. if (!empty($field['data'])) {
  279. $this->dynamic[$key] = $field['data'];
  280. }
  281. foreach ($field as $name => $value) {
  282. if (substr($name, 0, 6) == '@data-') {
  283. $property = substr($name, 6);
  284. if (is_array($value)) {
  285. $func = array_shift($value);
  286. } else {
  287. $func = $value;
  288. $value = array();
  289. }
  290. $this->dynamic[$key][$property] = ['function' => $func, 'params' => $value];
  291. }
  292. }
  293. // Initialize predefined validation rule.
  294. if (isset($field['validate']['rule'])) {
  295. $field['validate'] += $this->getRule($field['validate']['rule']);
  296. }
  297. }
  298. }
  299. }
  300. /**
  301. * Get property from the definition.
  302. *
  303. * @param string $path Comma separated path to the property.
  304. * @param string $separator
  305. * @return array
  306. * @internal
  307. */
  308. public function getProperty($path = null, $separator = '.')
  309. {
  310. if (!$path) {
  311. return $this->nested;
  312. }
  313. $parts = explode($separator, $path);
  314. $item = array_pop($parts);
  315. $nested = $this->nested;
  316. foreach ($parts as $part) {
  317. if (!isset($nested[$part])) {
  318. return [];
  319. }
  320. $nested = $nested[$part];
  321. }
  322. return isset($nested[$item]) ? $nested[$item] : [];
  323. }
  324. /**
  325. * Add property to the definition.
  326. *
  327. * @param string $path Comma separated path to the property.
  328. * @internal
  329. */
  330. protected function addProperty($path)
  331. {
  332. $parts = explode('.', $path);
  333. $item = array_pop($parts);
  334. $nested = &$this->nested;
  335. foreach ($parts as $part) {
  336. if (!isset($nested[$part])) {
  337. $nested[$part] = array();
  338. }
  339. $nested = &$nested[$part];
  340. }
  341. if (!isset($nested[$item])) {
  342. $nested[$item] = $path;
  343. }
  344. }
  345. /**
  346. * @param $rule
  347. * @return array
  348. * @internal
  349. */
  350. protected function getRule($rule)
  351. {
  352. if (isset($this->rules[$rule]) && is_array($this->rules[$rule])) {
  353. return $this->rules[$rule];
  354. }
  355. return array();
  356. }
  357. }