DataObject.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework;
  7. /**
  8. * Universal data container with array access implementation
  9. *
  10. * @api
  11. * @SuppressWarnings(PHPMD.NumberOfChildren)
  12. * @since 100.0.2
  13. */
  14. class DataObject implements \ArrayAccess
  15. {
  16. /**
  17. * Object attributes
  18. *
  19. * @var array
  20. */
  21. protected $_data = [];
  22. /**
  23. * Setter/Getter underscore transformation cache
  24. *
  25. * @var array
  26. */
  27. protected static $_underscoreCache = [];
  28. /**
  29. * Constructor
  30. *
  31. * By default is looking for first argument as array and assigns it as object attributes
  32. * This behavior may change in child classes
  33. *
  34. * @param array $data
  35. */
  36. public function __construct(array $data = [])
  37. {
  38. $this->_data = $data;
  39. }
  40. /**
  41. * Add data to the object.
  42. *
  43. * Retains previous data in the object.
  44. *
  45. * @param array $arr
  46. * @return $this
  47. */
  48. public function addData(array $arr)
  49. {
  50. foreach ($arr as $index => $value) {
  51. $this->setData($index, $value);
  52. }
  53. return $this;
  54. }
  55. /**
  56. * Overwrite data in the object.
  57. *
  58. * The $key parameter can be string or array.
  59. * If $key is string, the attribute value will be overwritten by $value
  60. *
  61. * If $key is an array, it will overwrite all the data in the object.
  62. *
  63. * @param string|array $key
  64. * @param mixed $value
  65. * @return $this
  66. */
  67. public function setData($key, $value = null)
  68. {
  69. if ($key === (array)$key) {
  70. $this->_data = $key;
  71. } else {
  72. $this->_data[$key] = $value;
  73. }
  74. return $this;
  75. }
  76. /**
  77. * Unset data from the object.
  78. *
  79. * @param null|string|array $key
  80. * @return $this
  81. */
  82. public function unsetData($key = null)
  83. {
  84. if ($key === null) {
  85. $this->setData([]);
  86. } elseif (is_string($key)) {
  87. if (isset($this->_data[$key]) || array_key_exists($key, $this->_data)) {
  88. unset($this->_data[$key]);
  89. }
  90. } elseif ($key === (array)$key) {
  91. foreach ($key as $element) {
  92. $this->unsetData($element);
  93. }
  94. }
  95. return $this;
  96. }
  97. /**
  98. * Object data getter
  99. *
  100. * If $key is not defined will return all the data as an array.
  101. * Otherwise it will return value of the element specified by $key.
  102. * It is possible to use keys like a/b/c for access nested array data
  103. *
  104. * If $index is specified it will assume that attribute data is an array
  105. * and retrieve corresponding member. If data is the string - it will be explode
  106. * by new line character and converted to array.
  107. *
  108. * @param string $key
  109. * @param string|int $index
  110. * @return mixed
  111. */
  112. public function getData($key = '', $index = null)
  113. {
  114. if ('' === $key) {
  115. return $this->_data;
  116. }
  117. /* process a/b/c key as ['a']['b']['c'] */
  118. if (strpos($key, '/') !== false) {
  119. $data = $this->getDataByPath($key);
  120. } else {
  121. $data = $this->_getData($key);
  122. }
  123. if ($index !== null) {
  124. if ($data === (array)$data) {
  125. $data = isset($data[$index]) ? $data[$index] : null;
  126. } elseif (is_string($data)) {
  127. $data = explode(PHP_EOL, $data);
  128. $data = isset($data[$index]) ? $data[$index] : null;
  129. } elseif ($data instanceof \Magento\Framework\DataObject) {
  130. $data = $data->getData($index);
  131. } else {
  132. $data = null;
  133. }
  134. }
  135. return $data;
  136. }
  137. /**
  138. * Get object data by path
  139. *
  140. * Method consider the path as chain of keys: a/b/c => ['a']['b']['c']
  141. *
  142. * @param string $path
  143. * @return mixed
  144. */
  145. public function getDataByPath($path)
  146. {
  147. $keys = explode('/', $path);
  148. $data = $this->_data;
  149. foreach ($keys as $key) {
  150. if ((array)$data === $data && isset($data[$key])) {
  151. $data = $data[$key];
  152. } elseif ($data instanceof \Magento\Framework\DataObject) {
  153. $data = $data->getDataByKey($key);
  154. } else {
  155. return null;
  156. }
  157. }
  158. return $data;
  159. }
  160. /**
  161. * Get object data by particular key
  162. *
  163. * @param string $key
  164. * @return mixed
  165. */
  166. public function getDataByKey($key)
  167. {
  168. return $this->_getData($key);
  169. }
  170. /**
  171. * Get value from _data array without parse key
  172. *
  173. * @param string $key
  174. * @return mixed
  175. */
  176. protected function _getData($key)
  177. {
  178. if (isset($this->_data[$key])) {
  179. return $this->_data[$key];
  180. }
  181. return null;
  182. }
  183. /**
  184. * Set object data with calling setter method
  185. *
  186. * @param string $key
  187. * @param mixed $args
  188. * @return $this
  189. */
  190. public function setDataUsingMethod($key, $args = [])
  191. {
  192. $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
  193. $this->{$method}($args);
  194. return $this;
  195. }
  196. /**
  197. * Get object data by key with calling getter method
  198. *
  199. * @param string $key
  200. * @param mixed $args
  201. * @return mixed
  202. */
  203. public function getDataUsingMethod($key, $args = null)
  204. {
  205. $method = 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
  206. return $this->{$method}($args);
  207. }
  208. /**
  209. * If $key is empty, checks whether there's any data in the object
  210. * Otherwise checks if the specified attribute is set.
  211. *
  212. * @param string $key
  213. * @return bool
  214. */
  215. public function hasData($key = '')
  216. {
  217. if (empty($key) || !is_string($key)) {
  218. return !empty($this->_data);
  219. }
  220. return array_key_exists($key, $this->_data);
  221. }
  222. /**
  223. * Convert array of object data with to array with keys requested in $keys array
  224. *
  225. * @param array $keys array of required keys
  226. * @return array
  227. */
  228. public function toArray(array $keys = [])
  229. {
  230. if (empty($keys)) {
  231. return $this->_data;
  232. }
  233. $result = [];
  234. foreach ($keys as $key) {
  235. if (isset($this->_data[$key])) {
  236. $result[$key] = $this->_data[$key];
  237. } else {
  238. $result[$key] = null;
  239. }
  240. }
  241. return $result;
  242. }
  243. /**
  244. * The "__" style wrapper for toArray method
  245. *
  246. * @param array $keys
  247. * @return array
  248. */
  249. public function convertToArray(array $keys = [])
  250. {
  251. return $this->toArray($keys);
  252. }
  253. /**
  254. * Convert object data into XML string
  255. *
  256. * @param array $keys array of keys that must be represented
  257. * @param string $rootName root node name
  258. * @param bool $addOpenTag flag that allow to add initial xml node
  259. * @param bool $addCdata flag that require wrap all values in CDATA
  260. * @return string
  261. */
  262. public function toXml(array $keys = [], $rootName = 'item', $addOpenTag = false, $addCdata = true)
  263. {
  264. $xml = '';
  265. $data = $this->toArray($keys);
  266. foreach ($data as $fieldName => $fieldValue) {
  267. if ($addCdata === true) {
  268. $fieldValue = "<![CDATA[{$fieldValue}]]>";
  269. } else {
  270. $fieldValue = str_replace(
  271. ['&', '"', "'", '<', '>'],
  272. ['&amp;', '&quot;', '&apos;', '&lt;', '&gt;'],
  273. $fieldValue
  274. );
  275. }
  276. $xml .= "<{$fieldName}>{$fieldValue}</{$fieldName}>\n";
  277. }
  278. if ($rootName) {
  279. $xml = "<{$rootName}>\n{$xml}</{$rootName}>\n";
  280. }
  281. if ($addOpenTag) {
  282. $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $xml;
  283. }
  284. return $xml;
  285. }
  286. /**
  287. * The "__" style wrapper for toXml method
  288. *
  289. * @param array $arrAttributes array of keys that must be represented
  290. * @param string $rootName root node name
  291. * @param bool $addOpenTag flag that allow to add initial xml node
  292. * @param bool $addCdata flag that require wrap all values in CDATA
  293. * @return string
  294. */
  295. public function convertToXml(
  296. array $arrAttributes = [],
  297. $rootName = 'item',
  298. $addOpenTag = false,
  299. $addCdata = true
  300. ) {
  301. return $this->toXml($arrAttributes, $rootName, $addOpenTag, $addCdata);
  302. }
  303. /**
  304. * Convert object data to JSON
  305. *
  306. * @param array $keys array of required keys
  307. * @return bool|string
  308. * @throws \InvalidArgumentException
  309. */
  310. public function toJson(array $keys = [])
  311. {
  312. $data = $this->toArray($keys);
  313. return \Magento\Framework\Serialize\JsonConverter::convert($data);
  314. }
  315. /**
  316. * The "__" style wrapper for toJson
  317. *
  318. * @param array $keys
  319. * @return bool|string
  320. * @throws \InvalidArgumentException
  321. */
  322. public function convertToJson(array $keys = [])
  323. {
  324. return $this->toJson($keys);
  325. }
  326. /**
  327. * Convert object data into string with predefined format
  328. *
  329. * Will use $format as an template and substitute {{key}} for attributes
  330. *
  331. * @param string $format
  332. * @return string
  333. */
  334. public function toString($format = '')
  335. {
  336. if (empty($format)) {
  337. $result = implode(', ', $this->getData());
  338. } else {
  339. preg_match_all('/\{\{([a-z0-9_]+)\}\}/is', $format, $matches);
  340. foreach ($matches[1] as $var) {
  341. $format = str_replace('{{' . $var . '}}', $this->getData($var), $format);
  342. }
  343. $result = $format;
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Set/Get attribute wrapper
  349. *
  350. * @param string $method
  351. * @param array $args
  352. * @return mixed
  353. * @throws \Magento\Framework\Exception\LocalizedException
  354. */
  355. public function __call($method, $args)
  356. {
  357. switch (substr($method, 0, 3)) {
  358. case 'get':
  359. $key = $this->_underscore(substr($method, 3));
  360. $index = isset($args[0]) ? $args[0] : null;
  361. return $this->getData($key, $index);
  362. case 'set':
  363. $key = $this->_underscore(substr($method, 3));
  364. $value = isset($args[0]) ? $args[0] : null;
  365. return $this->setData($key, $value);
  366. case 'uns':
  367. $key = $this->_underscore(substr($method, 3));
  368. return $this->unsetData($key);
  369. case 'has':
  370. $key = $this->_underscore(substr($method, 3));
  371. return isset($this->_data[$key]);
  372. }
  373. throw new \Magento\Framework\Exception\LocalizedException(
  374. new \Magento\Framework\Phrase('Invalid method %1::%2', [get_class($this), $method])
  375. );
  376. }
  377. /**
  378. * Checks whether the object is empty
  379. *
  380. * @return bool
  381. */
  382. public function isEmpty()
  383. {
  384. if (empty($this->_data)) {
  385. return true;
  386. }
  387. return false;
  388. }
  389. /**
  390. * Converts field names for setters and getters
  391. *
  392. * $this->setMyField($value) === $this->setData('my_field', $value)
  393. * Uses cache to eliminate unnecessary preg_replace
  394. *
  395. * @param string $name
  396. * @return string
  397. */
  398. protected function _underscore($name)
  399. {
  400. if (isset(self::$_underscoreCache[$name])) {
  401. return self::$_underscoreCache[$name];
  402. }
  403. $result = strtolower(trim(preg_replace('/([A-Z]|[0-9]+)/', "_$1", $name), '_'));
  404. self::$_underscoreCache[$name] = $result;
  405. return $result;
  406. }
  407. /**
  408. * Convert object data into string with defined keys and values.
  409. *
  410. * Example: key1="value1" key2="value2" ...
  411. *
  412. * @param array $keys array of accepted keys
  413. * @param string $valueSeparator separator between key and value
  414. * @param string $fieldSeparator separator between key/value pairs
  415. * @param string $quote quoting sign
  416. * @return string
  417. */
  418. public function serialize($keys = [], $valueSeparator = '=', $fieldSeparator = ' ', $quote = '"')
  419. {
  420. $data = [];
  421. if (empty($keys)) {
  422. $keys = array_keys($this->_data);
  423. }
  424. foreach ($this->_data as $key => $value) {
  425. if (in_array($key, $keys)) {
  426. $data[] = $key . $valueSeparator . $quote . $value . $quote;
  427. }
  428. }
  429. $res = implode($fieldSeparator, $data);
  430. return $res;
  431. }
  432. /**
  433. * Present object data as string in debug mode
  434. *
  435. * @param mixed $data
  436. * @param array &$objects
  437. * @return array
  438. */
  439. public function debug($data = null, &$objects = [])
  440. {
  441. if ($data === null) {
  442. $hash = spl_object_hash($this);
  443. if (!empty($objects[$hash])) {
  444. return '*** RECURSION ***';
  445. }
  446. $objects[$hash] = true;
  447. $data = $this->getData();
  448. }
  449. $debug = [];
  450. foreach ($data as $key => $value) {
  451. if (is_scalar($value)) {
  452. $debug[$key] = $value;
  453. } elseif (is_array($value)) {
  454. $debug[$key] = $this->debug($value, $objects);
  455. } elseif ($value instanceof \Magento\Framework\DataObject) {
  456. $debug[$key . ' (' . get_class($value) . ')'] = $value->debug(null, $objects);
  457. }
  458. }
  459. return $debug;
  460. }
  461. /**
  462. * Implementation of \ArrayAccess::offsetSet()
  463. *
  464. * @param string $offset
  465. * @param mixed $value
  466. * @return void
  467. * @link http://www.php.net/manual/en/arrayaccess.offsetset.php
  468. */
  469. public function offsetSet($offset, $value)
  470. {
  471. $this->_data[$offset] = $value;
  472. }
  473. /**
  474. * Implementation of \ArrayAccess::offsetExists()
  475. *
  476. * @param string $offset
  477. * @return bool
  478. * @link http://www.php.net/manual/en/arrayaccess.offsetexists.php
  479. */
  480. public function offsetExists($offset)
  481. {
  482. return isset($this->_data[$offset]) || array_key_exists($offset, $this->_data);
  483. }
  484. /**
  485. * Implementation of \ArrayAccess::offsetUnset()
  486. *
  487. * @param string $offset
  488. * @return void
  489. * @link http://www.php.net/manual/en/arrayaccess.offsetunset.php
  490. */
  491. public function offsetUnset($offset)
  492. {
  493. unset($this->_data[$offset]);
  494. }
  495. /**
  496. * Implementation of \ArrayAccess::offsetGet()
  497. *
  498. * @param string $offset
  499. * @return mixed
  500. * @link http://www.php.net/manual/en/arrayaccess.offsetget.php
  501. */
  502. public function offsetGet($offset)
  503. {
  504. if (isset($this->_data[$offset])) {
  505. return $this->_data[$offset];
  506. }
  507. return null;
  508. }
  509. }