Dot.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <?php
  2. /**
  3. * Dot - PHP dot notation access to arrays
  4. *
  5. * @author Riku Särkinen <riku@adbar.io>
  6. * @link https://github.com/adbario/php-dot-notation
  7. * @license https://github.com/adbario/php-dot-notation/blob/2.x/LICENSE.md (MIT License)
  8. */
  9. namespace Adbar;
  10. use Countable;
  11. use ArrayAccess;
  12. use ArrayIterator;
  13. use JsonSerializable;
  14. use IteratorAggregate;
  15. /**
  16. * Dot
  17. *
  18. * This class provides a dot notation access and helper functions for
  19. * working with arrays of data. Inspired by Laravel Collection.
  20. */
  21. class Dot implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
  22. {
  23. /**
  24. * The stored items
  25. *
  26. * @var array
  27. */
  28. protected $items = [];
  29. /**
  30. * Create a new Dot instance
  31. *
  32. * @param mixed $items
  33. */
  34. public function __construct($items = [])
  35. {
  36. $this->items = $this->getArrayItems($items);
  37. }
  38. /**
  39. * Set a given key / value pair or pairs
  40. * if the key doesn't exist already
  41. *
  42. * @param array|int|string $keys
  43. * @param mixed $value
  44. */
  45. public function add($keys, $value = null)
  46. {
  47. if (is_array($keys)) {
  48. foreach ($keys as $key => $value) {
  49. $this->add($key, $value);
  50. }
  51. } elseif (is_null($this->get($keys))) {
  52. $this->set($keys, $value);
  53. }
  54. }
  55. /**
  56. * Return all the stored items
  57. *
  58. * @return array
  59. */
  60. public function all()
  61. {
  62. return $this->items;
  63. }
  64. /**
  65. * Delete the contents of a given key or keys
  66. *
  67. * @param array|int|string|null $keys
  68. */
  69. public function clear($keys = null)
  70. {
  71. if (is_null($keys)) {
  72. $this->items = [];
  73. return;
  74. }
  75. $keys = (array) $keys;
  76. foreach ($keys as $key) {
  77. $this->set($key, []);
  78. }
  79. }
  80. /**
  81. * Delete the given key or keys
  82. *
  83. * @param array|int|string $keys
  84. */
  85. public function delete($keys)
  86. {
  87. $keys = (array) $keys;
  88. foreach ($keys as $key) {
  89. if ($this->exists($this->items, $key)) {
  90. unset($this->items[$key]);
  91. continue;
  92. }
  93. $items = &$this->items;
  94. $segments = explode('.', $key);
  95. $lastSegment = array_pop($segments);
  96. foreach ($segments as $segment) {
  97. if (!isset($items[$segment]) || !is_array($items[$segment])) {
  98. continue 2;
  99. }
  100. $items = &$items[$segment];
  101. }
  102. unset($items[$lastSegment]);
  103. }
  104. }
  105. /**
  106. * Checks if the given key exists in the provided array.
  107. *
  108. * @param array $array Array to validate
  109. * @param int|string $key The key to look for
  110. *
  111. * @return bool
  112. */
  113. protected function exists($array, $key)
  114. {
  115. return array_key_exists($key, $array);
  116. }
  117. /**
  118. * Flatten an array with the given character as a key delimiter
  119. *
  120. * @param string $delimiter
  121. * @param array|null $items
  122. * @param string $prepend
  123. * @return array
  124. */
  125. public function flatten($delimiter = '.', $items = null, $prepend = '')
  126. {
  127. $flatten = [];
  128. if (is_null($items)) {
  129. $items = $this->items;
  130. }
  131. foreach ($items as $key => $value) {
  132. if (is_array($value) && !empty($value)) {
  133. $flatten = array_merge(
  134. $flatten,
  135. $this->flatten($delimiter, $value, $prepend.$key.$delimiter)
  136. );
  137. } else {
  138. $flatten[$prepend.$key] = $value;
  139. }
  140. }
  141. return $flatten;
  142. }
  143. /**
  144. * Return the value of a given key
  145. *
  146. * @param int|string|null $key
  147. * @param mixed $default
  148. * @return mixed
  149. */
  150. public function get($key = null, $default = null)
  151. {
  152. if (is_null($key)) {
  153. return $this->items;
  154. }
  155. if ($this->exists($this->items, $key)) {
  156. return $this->items[$key];
  157. }
  158. if (strpos($key, '.') === false) {
  159. return $default;
  160. }
  161. $items = $this->items;
  162. foreach (explode('.', $key) as $segment) {
  163. if (!is_array($items) || !$this->exists($items, $segment)) {
  164. return $default;
  165. }
  166. $items = &$items[$segment];
  167. }
  168. return $items;
  169. }
  170. /**
  171. * Return the given items as an array
  172. *
  173. * @param mixed $items
  174. * @return array
  175. */
  176. protected function getArrayItems($items)
  177. {
  178. if (is_array($items)) {
  179. return $items;
  180. } elseif ($items instanceof self) {
  181. return $items->all();
  182. }
  183. return (array) $items;
  184. }
  185. /**
  186. * Check if a given key or keys exists
  187. *
  188. * @param array|int|string $keys
  189. * @return bool
  190. */
  191. public function has($keys)
  192. {
  193. $keys = (array) $keys;
  194. if (!$this->items || $keys === []) {
  195. return false;
  196. }
  197. foreach ($keys as $key) {
  198. $items = $this->items;
  199. if ($this->exists($items, $key)) {
  200. continue;
  201. }
  202. foreach (explode('.', $key) as $segment) {
  203. if (!is_array($items) || !$this->exists($items, $segment)) {
  204. return false;
  205. }
  206. $items = $items[$segment];
  207. }
  208. }
  209. return true;
  210. }
  211. /**
  212. * Check if a given key or keys are empty
  213. *
  214. * @param array|int|string|null $keys
  215. * @return bool
  216. */
  217. public function isEmpty($keys = null)
  218. {
  219. if (is_null($keys)) {
  220. return empty($this->items);
  221. }
  222. $keys = (array) $keys;
  223. foreach ($keys as $key) {
  224. if (!empty($this->get($key))) {
  225. return false;
  226. }
  227. }
  228. return true;
  229. }
  230. /**
  231. * Merge a given array or a Dot object with the given key
  232. * or with the whole Dot object
  233. *
  234. * @param array|string|self $key
  235. * @param array|self $value
  236. */
  237. public function merge($key, $value = [])
  238. {
  239. if (is_array($key)) {
  240. $this->items = array_merge($this->items, $key);
  241. } elseif (is_string($key)) {
  242. $items = (array) $this->get($key);
  243. $value = array_merge($items, $this->getArrayItems($value));
  244. $this->set($key, $value);
  245. } elseif ($key instanceof self) {
  246. $this->items = array_merge($this->items, $key->all());
  247. }
  248. }
  249. /**
  250. * Recursively merge a given array or a Dot object with the given key
  251. * or with the whole Dot object.
  252. *
  253. * Duplicate keys are converted to arrays.
  254. *
  255. * @param array|string|self $key
  256. * @param array|self $value
  257. */
  258. public function mergeRecursive($key, $value = [])
  259. {
  260. if (is_array($key)) {
  261. $this->items = array_merge_recursive($this->items, $key);
  262. } elseif (is_string($key)) {
  263. $items = (array) $this->get($key);
  264. $value = array_merge_recursive($items, $this->getArrayItems($value));
  265. $this->set($key, $value);
  266. } elseif ($key instanceof self) {
  267. $this->items = array_merge_recursive($this->items, $key->all());
  268. }
  269. }
  270. /**
  271. * Recursively merge a given array or a Dot object with the given key
  272. * or with the whole Dot object.
  273. *
  274. * Instead of converting duplicate keys to arrays, the value from
  275. * given array will replace the value in Dot object.
  276. *
  277. * @param array|string|self $key
  278. * @param array|self $value
  279. */
  280. public function mergeRecursiveDistinct($key, $value = [])
  281. {
  282. if (is_array($key)) {
  283. $this->items = $this->arrayMergeRecursiveDistinct($this->items, $key);
  284. } elseif (is_string($key)) {
  285. $items = (array) $this->get($key);
  286. $value = $this->arrayMergeRecursiveDistinct($items, $this->getArrayItems($value));
  287. $this->set($key, $value);
  288. } elseif ($key instanceof self) {
  289. $this->items = $this->arrayMergeRecursiveDistinct($this->items, $key->all());
  290. }
  291. }
  292. /**
  293. * Merges two arrays recursively. In contrast to array_merge_recursive,
  294. * duplicate keys are not converted to arrays but rather overwrite the
  295. * value in the first array with the duplicate value in the second array.
  296. *
  297. * @param array $array1 Initial array to merge
  298. * @param array $array2 Array to recursively merge
  299. * @return array
  300. */
  301. protected function arrayMergeRecursiveDistinct(array $array1, array $array2)
  302. {
  303. $merged = &$array1;
  304. foreach ($array2 as $key => $value) {
  305. if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
  306. $merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
  307. } else {
  308. $merged[$key] = $value;
  309. }
  310. }
  311. return $merged;
  312. }
  313. /**
  314. * Return the value of a given key and
  315. * delete the key
  316. *
  317. * @param int|string|null $key
  318. * @param mixed $default
  319. * @return mixed
  320. */
  321. public function pull($key = null, $default = null)
  322. {
  323. if (is_null($key)) {
  324. $value = $this->all();
  325. $this->clear();
  326. return $value;
  327. }
  328. $value = $this->get($key, $default);
  329. $this->delete($key);
  330. return $value;
  331. }
  332. /**
  333. * Push a given value to the end of the array
  334. * in a given key
  335. *
  336. * @param mixed $key
  337. * @param mixed $value
  338. */
  339. public function push($key, $value = null)
  340. {
  341. if (is_null($value)) {
  342. $this->items[] = $key;
  343. return;
  344. }
  345. $items = $this->get($key);
  346. if (is_array($items) || is_null($items)) {
  347. $items[] = $value;
  348. $this->set($key, $items);
  349. }
  350. }
  351. /**
  352. * Replace all values or values within the given key
  353. * with an array or Dot object
  354. *
  355. * @param array|string|self $key
  356. * @param array|self $value
  357. */
  358. public function replace($key, $value = [])
  359. {
  360. if (is_array($key)) {
  361. $this->items = array_replace($this->items, $key);
  362. } elseif (is_string($key)) {
  363. $items = (array) $this->get($key);
  364. $value = array_replace($items, $this->getArrayItems($value));
  365. $this->set($key, $value);
  366. } elseif ($key instanceof self) {
  367. $this->items = array_replace($this->items, $key->all());
  368. }
  369. }
  370. /**
  371. * Set a given key / value pair or pairs
  372. *
  373. * @param array|int|string $keys
  374. * @param mixed $value
  375. */
  376. public function set($keys, $value = null)
  377. {
  378. if (is_array($keys)) {
  379. foreach ($keys as $key => $value) {
  380. $this->set($key, $value);
  381. }
  382. return;
  383. }
  384. $items = &$this->items;
  385. foreach (explode('.', $keys) as $key) {
  386. if (!isset($items[$key]) || !is_array($items[$key])) {
  387. $items[$key] = [];
  388. }
  389. $items = &$items[$key];
  390. }
  391. $items = $value;
  392. }
  393. /**
  394. * Replace all items with a given array
  395. *
  396. * @param mixed $items
  397. */
  398. public function setArray($items)
  399. {
  400. $this->items = $this->getArrayItems($items);
  401. }
  402. /**
  403. * Replace all items with a given array as a reference
  404. *
  405. * @param array $items
  406. */
  407. public function setReference(array &$items)
  408. {
  409. $this->items = &$items;
  410. }
  411. /**
  412. * Return the value of a given key or all the values as JSON
  413. *
  414. * @param mixed $key
  415. * @param int $options
  416. * @return string
  417. */
  418. public function toJson($key = null, $options = 0)
  419. {
  420. if (is_string($key)) {
  421. return json_encode($this->get($key), $options);
  422. }
  423. $options = $key === null ? 0 : $key;
  424. return json_encode($this->items, $options);
  425. }
  426. /*
  427. * --------------------------------------------------------------
  428. * ArrayAccess interface
  429. * --------------------------------------------------------------
  430. */
  431. /**
  432. * Check if a given key exists
  433. *
  434. * @param int|string $key
  435. * @return bool
  436. */
  437. #[\ReturnTypeWillChange]
  438. public function offsetExists($key)
  439. {
  440. return $this->has($key);
  441. }
  442. /**
  443. * Return the value of a given key
  444. *
  445. * @param int|string $key
  446. * @return mixed
  447. */
  448. #[\ReturnTypeWillChange]
  449. public function offsetGet($key)
  450. {
  451. return $this->get($key);
  452. }
  453. /**
  454. * Set a given value to the given key
  455. *
  456. * @param int|string|null $key
  457. * @param mixed $value
  458. */
  459. #[\ReturnTypeWillChange]
  460. public function offsetSet($key, $value)
  461. {
  462. if (is_null($key)) {
  463. $this->items[] = $value;
  464. return;
  465. }
  466. $this->set($key, $value);
  467. }
  468. /**
  469. * Delete the given key
  470. *
  471. * @param int|string $key
  472. */
  473. #[\ReturnTypeWillChange]
  474. public function offsetUnset($key)
  475. {
  476. $this->delete($key);
  477. }
  478. /*
  479. * --------------------------------------------------------------
  480. * Countable interface
  481. * --------------------------------------------------------------
  482. */
  483. /**
  484. * Return the number of items in a given key
  485. *
  486. * @param int|string|null $key
  487. * @return int
  488. */
  489. #[\ReturnTypeWillChange]
  490. public function count($key = null)
  491. {
  492. return count($this->get($key));
  493. }
  494. /*
  495. * --------------------------------------------------------------
  496. * IteratorAggregate interface
  497. * --------------------------------------------------------------
  498. */
  499. /**
  500. * Get an iterator for the stored items
  501. *
  502. * @return \ArrayIterator
  503. */
  504. #[\ReturnTypeWillChange]
  505. public function getIterator()
  506. {
  507. return new ArrayIterator($this->items);
  508. }
  509. /*
  510. * --------------------------------------------------------------
  511. * JsonSerializable interface
  512. * --------------------------------------------------------------
  513. */
  514. /**
  515. * Return items for JSON serialization
  516. *
  517. * @return array
  518. */
  519. #[\ReturnTypeWillChange]
  520. public function jsonSerialize()
  521. {
  522. return $this->items;
  523. }
  524. }