国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

742 lines
16KB

  1. <?php
  2. if (!defined('DEDEINC')) exit('dedebiz');
  3. require_once(dirname(__FILE__)."/Exceptions/ConditionNotAllowedException.php");
  4. require_once(dirname(__FILE__)."/Exceptions/FileNotFoundException.php");
  5. require_once(dirname(__FILE__)."/Exceptions/InvalidJsonException.php");
  6. require_once(dirname(__FILE__)."/Exceptions/InvalidNodeException.php");
  7. require_once(dirname(__FILE__)."/Exceptions/NullValueException.php");
  8. require_once(dirname(__FILE__)."/JsonQueriable.php");
  9. class Jsonq
  10. {
  11. use JsonQueriable;
  12. /**
  13. * this constructor set main json file path
  14. * otherwise create it and read file contents
  15. * and decode as an array and store it in $this->_data
  16. *
  17. * @param null $jsonFile
  18. * @throws Exceptions\FileNotFoundException
  19. * @throws InvalidJsonException
  20. */
  21. public function __construct($jsonFile = null)
  22. {
  23. if (!is_null($jsonFile)) {
  24. $this->import($jsonFile);
  25. }
  26. }
  27. /**
  28. * Deep copy current instance
  29. *
  30. * @return Jsonq
  31. */
  32. public function copy()
  33. {
  34. return clone $this;
  35. }
  36. /**
  37. * Set node path, where JsonQ start to prepare
  38. *
  39. * @param null $node
  40. * @return $this
  41. * @throws NullValueException
  42. */
  43. public function from($node = null)
  44. {
  45. $this->_isProcessed = false;
  46. if (is_null($node) || $node == '') {
  47. throw new NullValueException("Null node exception");
  48. }
  49. $this->_node = $node;
  50. return $this;
  51. }
  52. /**
  53. * Alias of from() method
  54. *
  55. * @param null $node
  56. * @return $this
  57. * @throws NullValueException
  58. */
  59. public function at($node = null)
  60. {
  61. return $this->from($node);
  62. }
  63. /**
  64. * select desired column
  65. *
  66. * @param ... scalar
  67. * @return $this
  68. */
  69. public function select()
  70. {
  71. $args = func_get_args();
  72. if (count($args) > 0 ){
  73. $this->_select = $args;
  74. }
  75. return $this;
  76. }
  77. /**
  78. * select desired column for except
  79. *
  80. * @param ... scalar
  81. * @return $this
  82. */
  83. public function except()
  84. {
  85. $args = func_get_args();
  86. if (count($args) > 0 ){
  87. $this->_except = $args;
  88. }
  89. return $this;
  90. }
  91. /**
  92. * getting prepared data
  93. *
  94. * @param bool $object
  95. * @return array|object
  96. * @throws ConditionNotAllowedException
  97. */
  98. public function get($object = false)
  99. {
  100. $this->prepare();
  101. return $this->prepareResult($this->_map, $object);
  102. }
  103. /**
  104. * alias of get method
  105. *
  106. * @param bool $object
  107. * @return array|object
  108. * @throws ConditionNotAllowedException
  109. */
  110. public function fetch($object = true)
  111. {
  112. return $this->get($object);
  113. }
  114. /**
  115. * check data exists in system
  116. *
  117. * @return bool
  118. * @throws ConditionNotAllowedException
  119. */
  120. public function exists()
  121. {
  122. $this->prepare();
  123. return (!empty($this->_map) && !is_null($this->_map));
  124. }
  125. /**
  126. * reset given data to the $_map
  127. *
  128. * @param mixed $data
  129. * @param bool $instance
  130. * @return jsonq
  131. */
  132. public function reset($data = null, $instance = false)
  133. {
  134. if (!is_null($data)) {
  135. $this->_baseContents = $data;
  136. }
  137. if ($instance) {
  138. $self = new self();
  139. $self->collect($this->_baseContents);
  140. return $self;
  141. }
  142. $this->_map = $this->_baseContents;
  143. $this->reProcess();
  144. return $this;
  145. }
  146. /**
  147. * getting group data from specific column
  148. *
  149. * @param string $column
  150. * @return $this
  151. * @throws ConditionNotAllowedException
  152. */
  153. public function groupBy($column)
  154. {
  155. $this->prepare();
  156. $data = [];
  157. foreach ($this->_map as $map) {
  158. $value = $this->getFromNested($map, $column);
  159. if ($value) {
  160. $data[$value][] = $map;
  161. }
  162. }
  163. $this->_map = $data;
  164. return $this;
  165. }
  166. public function countGroupBy($column)
  167. {
  168. $this->prepare();
  169. $data = [];
  170. foreach ($this->_map as $map) {
  171. $value = $this->getFromNested($map, $column);
  172. if (!$value) {
  173. continue;
  174. }
  175. if (isset($data[$value])) {
  176. $data[$value] ++;
  177. } else {
  178. $data[$value] = 1;
  179. }
  180. }
  181. $this->_map = $data;
  182. return $this;
  183. }
  184. /**
  185. * count prepared data
  186. *
  187. * @return int
  188. * @throws ConditionNotAllowedException
  189. */
  190. public function count()
  191. {
  192. $this->prepare();
  193. return count($this->_map);
  194. }
  195. /**
  196. * size is an alias of count
  197. *
  198. * @return int
  199. * @throws ConditionNotAllowedException
  200. */
  201. public function size()
  202. {
  203. return $this->count();
  204. }
  205. /**
  206. * sum prepared data
  207. * @param int $column
  208. * @return int
  209. * @throws ConditionNotAllowedException
  210. */
  211. public function sum($column = null)
  212. {
  213. $this->prepare();
  214. $sum = 0;
  215. if (is_null($column)) {
  216. $sum = array_sum($this->_map);
  217. } else {
  218. foreach ($this->_map as $key => $val) {
  219. $value = $this->getFromNested($val, $column);
  220. if (is_scalar($value)) {
  221. $sum += $value;
  222. }
  223. }
  224. }
  225. return $sum;
  226. }
  227. /**
  228. * getting max value from prepared data
  229. *
  230. * @param int $column
  231. * @return int
  232. * @throws ConditionNotAllowedException
  233. */
  234. public function max($column = null)
  235. {
  236. $this->prepare();
  237. if (is_null($column)) {
  238. $max = max($this->_map);
  239. } else {
  240. $max = max(array_column($this->_map, $column));
  241. }
  242. return $max;
  243. }
  244. /**
  245. * getting min value from prepared data
  246. *
  247. * @param int $column
  248. * @return string
  249. * @throws ConditionNotAllowedException
  250. */
  251. public function min($column = null)
  252. {
  253. $this->prepare();
  254. if (is_null($column)) {
  255. $min = min($this->_map);
  256. } else {
  257. $min = min(array_column($this->_map, $column));
  258. }
  259. return $min;
  260. }
  261. /**
  262. * getting average value from prepared data
  263. *
  264. * @param int $column
  265. * @return string
  266. * @throws ConditionNotAllowedException
  267. */
  268. public function avg($column = null)
  269. {
  270. $this->prepare();
  271. $count = $this->count();
  272. $total = $this->sum($column);
  273. return ($total/$count);
  274. }
  275. /**
  276. * getting first element of prepared data
  277. *
  278. * @param bool $object
  279. * @return object|array|null
  280. * @throws ConditionNotAllowedException
  281. */
  282. public function first($object = false)
  283. {
  284. $this->prepare();
  285. $data = $this->_map;
  286. if (count($data) > 0) {
  287. return $this->prepareResult(reset($data), $object);
  288. }
  289. return null;
  290. }
  291. /**
  292. * getting last element of prepared data
  293. *
  294. * @param bool $object
  295. * @return object|array|null
  296. * @throws ConditionNotAllowedException
  297. */
  298. public function last($object = false)
  299. {
  300. $this->prepare();
  301. $data = $this->_map;
  302. if (count($data) > 0) {
  303. return $this->prepareResult(end($data), $object);
  304. }
  305. return null;
  306. }
  307. /**
  308. * getting nth number of element of prepared data
  309. *
  310. * @param int $index
  311. * @param bool $object
  312. * @return object|array|null
  313. * @throws ConditionNotAllowedException
  314. */
  315. public function nth($index, $object = false)
  316. {
  317. $this->prepare();
  318. $data = $this->_map;
  319. $total_elm = count($data);
  320. $idx = abs($index);
  321. if (!is_integer($index) || $total_elm < $idx || $index == 0 || !is_array($this->_map)) {
  322. return null;
  323. }
  324. if ($index > 0) {
  325. $result = $data[$index - 1];
  326. } else {
  327. $result = $data[$this->count() + $index];
  328. }
  329. return $this->prepareResult($result, $object);
  330. }
  331. /**
  332. * sorting from prepared data
  333. *
  334. * @param string $column
  335. * @param string $order
  336. * @return object|array|null
  337. * @throws ConditionNotAllowedException
  338. */
  339. public function sortBy($column, $order = 'asc')
  340. {
  341. $this->prepare();
  342. if (!is_array($this->_map)) {
  343. return $this;
  344. }
  345. usort($this->_map, function ($a, $b) use ($column, $order) {
  346. $val1 = $this->getFromNested($a, $column);
  347. $val2 = $this->getFromNested($b, $column);
  348. if (is_string($val1)) {
  349. $val1 = strtolower($val1);
  350. }
  351. if (is_string($val2)) {
  352. $val2 = strtolower($val2);
  353. }
  354. if ($val1 == $val2) {
  355. return 0;
  356. }
  357. $order = strtolower(trim($order));
  358. if ($order == 'desc') {
  359. return ($val1 > $val2) ? -1 : 1;
  360. } else {
  361. return ($val1 < $val2) ? -1 : 1;
  362. }
  363. });
  364. return $this;
  365. }
  366. /**
  367. * Sort prepared data using a custom sort function.
  368. *
  369. * @param callable $sortFunc
  370. *
  371. * @return object|array|null
  372. * @throws ConditionNotAllowedException
  373. */
  374. public function sortByCallable(callable $sortFunc)
  375. {
  376. $this->prepare();
  377. if (!is_array($this->_map)) {
  378. return $this;
  379. }
  380. usort($this->_map, $sortFunc);
  381. return $this;
  382. }
  383. /**
  384. * Sort an array value
  385. *
  386. * @param string $order
  387. * @return Jsonq
  388. */
  389. public function sort($order = 'asc')
  390. {
  391. if ($order == 'desc') {
  392. rsort($this->_map);
  393. }else{
  394. sort($this->_map);
  395. }
  396. return $this;
  397. }
  398. /**
  399. * getting data from desire path
  400. *
  401. * @param string $path
  402. * @param bool $object
  403. * @return mixed
  404. * @throws NullValueException
  405. * @throws ConditionNotAllowedException
  406. */
  407. public function find($path, $object = false)
  408. {
  409. return $this->from($path)->prepare()->get($object);
  410. }
  411. /**
  412. * take action of each element of prepared data
  413. *
  414. * @param callable $fn
  415. * @throws ConditionNotAllowedException
  416. */
  417. public function each(callable $fn)
  418. {
  419. $this->prepare();
  420. foreach ($this->_map as $key => $val) {
  421. $fn($key, $val);
  422. }
  423. }
  424. /**
  425. * transform prepared data by using callable function
  426. *
  427. * @param callable $fn
  428. * @return object|array
  429. * @throws ConditionNotAllowedException
  430. */
  431. public function transform(callable $fn)
  432. {
  433. $this->prepare();
  434. $new_data = [];
  435. foreach ($this->_map as $key => $val) {
  436. $new_data[$key] = $fn($val);
  437. }
  438. return $this->prepareResult($new_data, false);
  439. }
  440. /**
  441. * pipe send output in next pipe
  442. *
  443. * @param callable $fn
  444. * @param string|null $class
  445. * @return object|array
  446. * @throws ConditionNotAllowedException
  447. */
  448. public function pipe(callable $fn, $class = null)
  449. {
  450. $this->prepare();
  451. if (is_string($fn) && !is_null($class)) {
  452. $instance = new $class;
  453. $this->_map = call_user_func_array([$instance, $fn], [$this]);
  454. return $this;
  455. }
  456. $this->_map = $fn($this);
  457. return $this;
  458. }
  459. /**
  460. * filtered each element of prepared data
  461. *
  462. * @param callable $fn
  463. * @param bool $key
  464. * @return mixed|array
  465. * @throws ConditionNotAllowedException
  466. */
  467. public function filter(callable $fn, $key = false)
  468. {
  469. $this->prepare();
  470. $data = [];
  471. foreach ($this->_map as $k => $val) {
  472. if ($fn($val)) {
  473. if ($key) {
  474. $data[$k] = $val;
  475. } else {
  476. $data[] = $val;
  477. }
  478. }
  479. }
  480. return $this->prepareResult($data, false);
  481. }
  482. /**
  483. * then method set position of working data
  484. *
  485. * @param string $node
  486. * @return jsonq
  487. * @throws NullValueException
  488. * @throws ConditionNotAllowedException
  489. */
  490. public function then($node)
  491. {
  492. $this->_map = $this->prepare()->first(false);
  493. $this->from($node);
  494. return $this;
  495. }
  496. /**
  497. * import raw JSON data for process
  498. *
  499. * @param string $data
  500. * @return jsonq
  501. */
  502. public function json($data)
  503. {
  504. $json = $this->isJson($data, true);
  505. if ($json) {
  506. return $this->collect($json);
  507. }
  508. return $this;
  509. }
  510. /**
  511. * import parsed data from raw json
  512. *
  513. * @param array|object $data
  514. * @return jsonq
  515. */
  516. public function collect($data)
  517. {
  518. $this->_map = $this->objectToArray($data);
  519. $this->_baseContents = &$this->_map;
  520. return $this;
  521. }
  522. /**
  523. * implode resulting data from desire key and delimeter
  524. *
  525. * @param string|array $key
  526. * @param string $delimiter
  527. * @return string|array
  528. * @throws ConditionNotAllowedException
  529. */
  530. public function implode($key, $delimiter = ',')
  531. {
  532. $this->prepare();
  533. $implode = [];
  534. if (is_string($key)) {
  535. return $this->makeImplode($key, $delimiter);
  536. }
  537. if (is_array($key)) {
  538. foreach ($key as $k) {
  539. $imp = $this->makeImplode($k, $delimiter);
  540. $implode[$k] = $imp;
  541. }
  542. return $implode;
  543. }
  544. return '';
  545. }
  546. /**
  547. * process implode from resulting data
  548. *
  549. * @param string $key
  550. * @param string $delimiter
  551. * @return string|null
  552. */
  553. protected function makeImplode($key, $delimiter)
  554. {
  555. $data = array_column($this->_map, $key);
  556. if (is_array($data)) {
  557. return implode($delimiter, $data);
  558. }
  559. return null;
  560. }
  561. /**
  562. * getting specific key's value from prepared data
  563. *
  564. * @param string $column
  565. * @return object|array
  566. * @throws ConditionNotAllowedException
  567. */
  568. public function column($column)
  569. {
  570. $this->prepare();
  571. return array_column($this->_map, $column);
  572. }
  573. /**
  574. * getting raw JSON from prepared data
  575. *
  576. * @return string
  577. * @throws ConditionNotAllowedException
  578. */
  579. public function toJson()
  580. {
  581. $this->prepare();
  582. return json_encode($this->_map);
  583. }
  584. /**
  585. * getting all keys from prepared data
  586. *
  587. * @return object|array
  588. * @throws ConditionNotAllowedException
  589. */
  590. public function keys()
  591. {
  592. $this->prepare();
  593. return array_keys($this->_map);
  594. }
  595. /**
  596. * getting all values from prepared data
  597. *
  598. * @return object|array
  599. * @throws ConditionNotAllowedException
  600. */
  601. public function values()
  602. {
  603. $this->prepare();
  604. return array_values($this->_map);
  605. }
  606. /**
  607. * getting chunk values from prepared data
  608. *
  609. * @param int $amount
  610. * @param $fn
  611. * @return object|array|bool
  612. * @throws ConditionNotAllowedException
  613. */
  614. public function chunk($amount, callable $fn = null)
  615. {
  616. $this->prepare();
  617. $chunk_value = array_chunk($this->_map, $amount);
  618. $chunks = [];
  619. if (!is_null($fn) && is_callable($fn)) {
  620. foreach ($chunk_value as $chunk) {
  621. $return = $fn($chunk);
  622. if (!is_null($return)) {
  623. $chunks[] = $return;
  624. }
  625. }
  626. return count($chunks) > 0 ? $chunks : null;
  627. }
  628. return $chunk_value;
  629. }
  630. }