国内流行的内容管理系统(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.

586 lines
15KB

  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__)."/Results/ValueNotFound.php");
  7. require_once(dirname(__FILE__)."/Condition.php");
  8. trait JsonQueriable
  9. {
  10. /**
  11. * store node path
  12. * @var string|array
  13. */
  14. protected $_node = '';
  15. /**
  16. * contain prepared data for process
  17. * @var mixed
  18. */
  19. protected $_map;
  20. /**
  21. * contains column names
  22. * @var array
  23. */
  24. protected $_select = [];
  25. /**
  26. * contains column names for except
  27. * @var array
  28. */
  29. protected $_except = [];
  30. /**
  31. * Stores base contents.
  32. *
  33. * @var array
  34. */
  35. protected $_baseContents = [];
  36. /**
  37. * Stores all conditions.
  38. *
  39. * @var array
  40. */
  41. protected $_conditions = [];
  42. /**
  43. * @var bool
  44. */
  45. protected $_isProcessed = false;
  46. /**
  47. * map all conditions with methods
  48. * @var array
  49. */
  50. protected static $_rulesMap = [
  51. '=' => 'equal',
  52. 'eq' => 'equal',
  53. '==' => 'strictEqual',
  54. 'seq' => 'strictEqual',
  55. '!=' => 'notEqual',
  56. 'neq' => 'notEqual',
  57. '!==' => 'strictNotEqual',
  58. 'sneq' => 'strictNotEqual',
  59. '>' => 'greaterThan',
  60. 'gt' => 'greaterThan',
  61. '<' => 'lessThan',
  62. 'lt' => 'lessThan',
  63. '>=' => 'greaterThanOrEqual',
  64. 'gte' => 'greaterThanOrEqual',
  65. '<=' => 'lessThanOrEqual',
  66. 'lte' => 'lessThanOrEqual',
  67. 'in' => 'in',
  68. 'notin' => 'notIn',
  69. 'null' => 'isNull',
  70. 'notnull' => 'isNotNull',
  71. 'startswith' => 'startWith',
  72. 'endswith' => 'endWith',
  73. 'match' => 'match',
  74. 'contains' => 'contains',
  75. 'dates' => 'dateEqual',
  76. 'month' => 'monthEqual',
  77. 'year' => 'yearEqual',
  78. ];
  79. /**
  80. * import data from file
  81. *
  82. * @param string|null $file
  83. * @return bool
  84. * @throws FileNotFoundException
  85. * @throws InvalidJsonException
  86. */
  87. public function import($file = null)
  88. {
  89. if (!is_null($file)) {
  90. if (is_string($file)) {
  91. if (preg_match("#^http#", $file)) {
  92. $this->_map = $this->getDataFromUrl($file);
  93. } else {
  94. $this->_map = $this->getDataFromFile($file);
  95. }
  96. $this->_baseContents = $this->_map;
  97. return true;
  98. }
  99. }
  100. throw new FileNotFoundException();
  101. }
  102. /**
  103. * Prepare data from desire conditions
  104. *
  105. * @return $this
  106. * @throws ConditionNotAllowedException
  107. */
  108. protected function prepare()
  109. {
  110. if ($this->_isProcessed) {
  111. return $this;
  112. }
  113. if (count($this->_conditions) > 0) {
  114. $calculatedData = $this->processConditions();
  115. $this->_map = $this->objectToArray($calculatedData);
  116. $this->_conditions = [];
  117. $this->_node = '';
  118. $this->_isProcessed = true;
  119. return $this;
  120. }
  121. $this->_isProcessed = true;
  122. $this->_map = $this->objectToArray($this->getData());
  123. return $this;
  124. }
  125. /**
  126. * Our system will cache processed data and prevend multiple time processing. If
  127. * you want to reprocess this method can help you
  128. *
  129. * @return $this
  130. */
  131. public function reProcess()
  132. {
  133. $this->_isProcessed = false;
  134. return $this;
  135. }
  136. /**
  137. * Parse object to array
  138. *
  139. * @param object $obj
  140. * @return array|mixed
  141. */
  142. protected function objectToArray($obj)
  143. {
  144. if (!is_array($obj) && !is_object($obj)) {
  145. return $obj;
  146. }
  147. if (is_array($obj)) {
  148. return $obj;
  149. }
  150. if (is_object($obj)) {
  151. $obj = get_object_vars($obj);
  152. }
  153. return array_map([$this, 'objectToArray'], $obj);
  154. }
  155. /**
  156. * Check given value is multidimensional array
  157. *
  158. * @param array $arr
  159. * @return bool
  160. */
  161. protected function isMultiArray($arr)
  162. {
  163. if (!is_array($arr)) {
  164. return false;
  165. }
  166. rsort($arr);
  167. return isset($arr[0]) && is_array($arr[0]);
  168. }
  169. /**
  170. * Check given value is valid JSON
  171. *
  172. * @param string $value
  173. * @param bool $isReturnMap
  174. *
  175. * @return bool|array
  176. */
  177. public function isJson($value, $isReturnMap = false)
  178. {
  179. if (is_array($value) || is_object($value)) {
  180. return false;
  181. }
  182. $data = json_decode($value, true);
  183. if (json_last_error() !== JSON_ERROR_NONE) {
  184. return false;
  185. }
  186. return $isReturnMap ? $data : true;
  187. }
  188. public function takeColumn($array)
  189. {
  190. return $this->selectColumn($this->exceptColumn($array));
  191. }
  192. /**
  193. * selecting specific column
  194. *
  195. * @param $array
  196. * @return array
  197. */
  198. protected function selectColumn($array)
  199. {
  200. $keys = $this->_select;
  201. if (count($keys) == 0) {
  202. return $array;
  203. }
  204. return array_intersect_key($array, array_flip((array) $keys));
  205. }
  206. /**
  207. * selecting specific column
  208. *
  209. * @param $array
  210. * @return array
  211. */
  212. protected function exceptColumn($array)
  213. {
  214. $keys = $this->_except;
  215. if (count($keys) == 0) {
  216. return $array;
  217. }
  218. return array_diff_key($array, array_flip((array) $keys));
  219. }
  220. /**
  221. * Prepare data for result
  222. *
  223. * @param mixed $data
  224. * @param bool $isObject
  225. * @return array|mixed
  226. */
  227. protected function prepareResult($data, $isObject)
  228. {
  229. $output = [];
  230. if (is_null($data) || is_scalar($data)) {
  231. return $data;
  232. }
  233. if ($this->isMultiArray($data)) {
  234. foreach ($data as $key => $val) {
  235. $val = $this->takeColumn($val);
  236. $output[$key] = $isObject ? (object) $val : $val;
  237. }
  238. } else {
  239. $output = json_decode(json_encode($this->takeColumn($data)), $isObject);
  240. }
  241. return $output;
  242. }
  243. /**
  244. * Read JSON data from file
  245. *
  246. * @param string $file
  247. * @return bool|string|array
  248. * @throws FileNotFoundException
  249. * @throws InvalidJsonException
  250. */
  251. protected function getDataFromFile($file)
  252. {
  253. $data = file_get_contents($file);
  254. $json = $this->isJson($data, true);
  255. if (!$json) {
  256. throw new InvalidJsonException();
  257. }
  258. return $json;
  259. }
  260. /**
  261. * Get JSON data from url
  262. *
  263. * @param string $url
  264. * @return bool|string|array
  265. * @throws FileNotFoundException
  266. * @throws InvalidJsonException
  267. */
  268. protected function getDataFromUrl($url)
  269. {
  270. $curl_handle=curl_init();
  271. curl_setopt($curl_handle, CURLOPT_URL, $url);
  272. curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 2);
  273. curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
  274. curl_setopt($curl_handle, CURLOPT_TIMEOUT, 5);
  275. curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 10 );
  276. curl_setopt($curl_handle, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36');
  277. $data = curl_exec($curl_handle);
  278. curl_close($curl_handle);
  279. $json = $this->isJson($data, true);
  280. if (!$json) {
  281. throw new InvalidJsonException();
  282. }
  283. return $json;
  284. }
  285. /**
  286. * Get data from nested array
  287. *
  288. * @param $map array
  289. * @param $node string
  290. * @return bool|array|mixed
  291. */
  292. protected function getFromNested($map, $node)
  293. {
  294. if (empty($node) || $node == '.') {
  295. return $map;
  296. }
  297. if ($node) {
  298. $terminate = false;
  299. $path = explode('.', $node);
  300. foreach ($path as $val) {
  301. if (!is_array($map)) return $map;
  302. if (!array_key_exists($val, $map)) {
  303. $terminate = true;
  304. break;
  305. }
  306. $map = &$map[$val];
  307. }
  308. if ($terminate) {
  309. return new ValueNotFound();
  310. }
  311. return $map;
  312. }
  313. return new ValueNotFound();
  314. }
  315. /**
  316. * get data from node path
  317. *
  318. * @return mixed
  319. */
  320. protected function getData()
  321. {
  322. return $this->getFromNested($this->_map, $this->_node);
  323. }
  324. /**
  325. * process AND and OR conditions
  326. *
  327. * @return array|string|object
  328. * @throws ConditionNotAllowedException
  329. */
  330. protected function processConditions()
  331. {
  332. $data = $this->getData();
  333. $conditions = $this->_conditions;
  334. $result = array_filter($data, function ($val) use ($conditions) {
  335. $res = false;
  336. foreach ($conditions as $cond) {
  337. $tmp = true;
  338. foreach ($cond as $rule) {
  339. $function = self::$_rulesMap[$rule['condition']];
  340. if (!is_callable($function)) {
  341. if (!method_exists(Condition::class, $function)) {
  342. throw new ConditionNotAllowedException("Exception: $function condition not allowed");
  343. }
  344. $function = [Condition::class, $function];
  345. }
  346. $value = $this->getFromNested($val, $rule['key']);
  347. $return = $value instanceof ValueNotFound ? false : call_user_func_array($function, [$value, $rule['value']]);
  348. $tmp &= $return;
  349. }
  350. $res |= $tmp;
  351. }
  352. return $res;
  353. });
  354. return $result;
  355. }
  356. /**
  357. * make WHERE clause
  358. *
  359. * @param string $key
  360. * @param string $condition
  361. * @param mixed $value
  362. * @return $this
  363. */
  364. public function where($key, $condition = null, $value = null)
  365. {
  366. if (!is_null($condition) && is_null($value)) {
  367. $value = $condition;
  368. $condition = '=';
  369. }
  370. if (count($this->_conditions) < 1) {
  371. array_push($this->_conditions, []);
  372. }
  373. return $this->makeWhere($key, $condition, $value);
  374. }
  375. /**
  376. * make WHERE clause with OR
  377. *
  378. * @param string $key
  379. * @param string $condition
  380. * @param mixed $value
  381. * @return $this
  382. */
  383. public function orWhere($key = null, $condition = null, $value = null)
  384. {
  385. if (!is_null($condition) && is_null($value)) {
  386. $value = $condition;
  387. $condition = '=';
  388. }
  389. array_push($this->_conditions, []);
  390. return $this->makeWhere($key, $condition, $value);
  391. }
  392. /**
  393. * generator for AND and OR where
  394. *
  395. * @param string $key
  396. * @param string $condition
  397. * @param mixed $value
  398. * @return $this
  399. */
  400. protected function makeWhere($key, $condition = null, $value = null)
  401. {
  402. $current = end($this->_conditions);
  403. $index = key($this->_conditions);
  404. if (is_callable($key)) {
  405. $key($this);
  406. return $this;
  407. }
  408. array_push($current, [
  409. 'key' => $key,
  410. 'condition' => $condition,
  411. 'value' => $value,
  412. ]);
  413. $this->_conditions[$index] = $current;
  414. return $this;
  415. }
  416. /**
  417. * make WHERE IN clause
  418. *
  419. * @param string $key
  420. * @param array $value
  421. * @return $this
  422. */
  423. public function whereIn($key = null, $value = [])
  424. {
  425. $this->where($key, 'in', $value);
  426. return $this;
  427. }
  428. /**
  429. * make WHERE NOT IN clause
  430. *
  431. * @param string $key
  432. * @param mixed $value
  433. * @return $this
  434. */
  435. public function whereNotIn($key = null, $value = [])
  436. {
  437. $this->where($key, 'notin', $value);
  438. return $this;
  439. }
  440. /**
  441. * make WHERE NULL clause
  442. *
  443. * @param string $key
  444. * @return $this
  445. */
  446. public function whereNull($key = null)
  447. {
  448. $this->where($key, 'null', 'null');
  449. return $this;
  450. }
  451. /**
  452. * make WHERE Boolean clause
  453. *
  454. * @param string $key
  455. * @return $this
  456. */
  457. public function whereBool($key, $value)
  458. {
  459. if (is_bool($value)) {
  460. $this->where($key, '==', $value);
  461. }
  462. return $this;
  463. }
  464. /**
  465. * make WHERE NOT NULL clause
  466. *
  467. * @param string $key
  468. * @return $this
  469. */
  470. public function whereNotNull($key = null)
  471. {
  472. $this->where($key, 'notnull', 'null');
  473. return $this;
  474. }
  475. /**
  476. * make WHERE START WITH clause
  477. *
  478. * @param string $key
  479. * @param string $value
  480. * @return $this
  481. */
  482. public function whereStartsWith($key, $value)
  483. {
  484. $this->where($key, 'startswith', $value);
  485. return $this;
  486. }
  487. /**
  488. * make WHERE ENDS WITH clause
  489. *
  490. * @param string $key
  491. * @param string $value
  492. * @return $this
  493. */
  494. public function whereEndsWith($key, $value)
  495. {
  496. $this->where($key, 'endswith', $value);
  497. return $this;
  498. }
  499. /**
  500. * make WHERE MATCH clause
  501. *
  502. * @param string $key
  503. * @param string $value
  504. * @return $this
  505. */
  506. public function whereMatch($key, $value)
  507. {
  508. $this->where($key, 'match', $value);
  509. return $this;
  510. }
  511. /**
  512. * make WHERE CONTAINS clause
  513. *
  514. * @param string $key
  515. * @param string $value
  516. * @return $this
  517. */
  518. public function whereContains($key, $value)
  519. {
  520. $this->where($key, 'contains', $value);
  521. return $this;
  522. }
  523. /**
  524. * make WHERE DATE clause
  525. *
  526. * @param string $key
  527. * @param string $value
  528. * @return $this
  529. */
  530. public function whereDate($key, $value)
  531. {
  532. $this->where($key, 'dates', $value);
  533. return $this;
  534. }
  535. /**
  536. * make WHERE month clause
  537. *
  538. * @param string $key
  539. * @param string $value
  540. * @return $this
  541. */
  542. public function whereMonth($key, $value)
  543. {
  544. $this->where($key, 'month', $value);
  545. return $this;
  546. }
  547. /**
  548. * make WHERE Year clause
  549. *
  550. * @param string $key
  551. * @param string $value
  552. * @return $this
  553. */
  554. public function whereYear($key, $value)
  555. {
  556. $this->where($key, 'year', $value);
  557. return $this;
  558. }
  559. /**
  560. * make macro for custom where clause
  561. *
  562. * @param string $name
  563. * @param callable $fn
  564. * @return bool
  565. */
  566. public static function macro($name, callable $fn)
  567. {
  568. if (!in_array($name, self::$_rulesMap)) {
  569. self::$_rulesMap[$name] = $fn;
  570. return true;
  571. }
  572. return false;
  573. }
  574. }
  575. ?>