国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

588 行
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_SSL_VERIFYPEER, false);
  275. curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false);
  276. curl_setopt($curl_handle, CURLOPT_TIMEOUT, 5);
  277. curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 10 );
  278. 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');
  279. $data = curl_exec($curl_handle);
  280. curl_close($curl_handle);
  281. $json = $this->isJson($data, true);
  282. if (!$json) {
  283. throw new InvalidJsonException();
  284. }
  285. return $json;
  286. }
  287. /**
  288. * Get data from nested array
  289. *
  290. * @param $map array
  291. * @param $node string
  292. * @return bool|array|mixed
  293. */
  294. protected function getFromNested($map, $node)
  295. {
  296. if (empty($node) || $node == '.') {
  297. return $map;
  298. }
  299. if ($node) {
  300. $terminate = false;
  301. $path = explode('.', $node);
  302. foreach ($path as $val) {
  303. if (!is_array($map)) return $map;
  304. if (!array_key_exists($val, $map)) {
  305. $terminate = true;
  306. break;
  307. }
  308. $map = &$map[$val];
  309. }
  310. if ($terminate) {
  311. return new ValueNotFound();
  312. }
  313. return $map;
  314. }
  315. return new ValueNotFound();
  316. }
  317. /**
  318. * get data from node path
  319. *
  320. * @return mixed
  321. */
  322. protected function getData()
  323. {
  324. return $this->getFromNested($this->_map, $this->_node);
  325. }
  326. /**
  327. * process AND and OR conditions
  328. *
  329. * @return array|string|object
  330. * @throws ConditionNotAllowedException
  331. */
  332. protected function processConditions()
  333. {
  334. $data = $this->getData();
  335. $conditions = $this->_conditions;
  336. $result = array_filter($data, function ($val) use ($conditions) {
  337. $res = false;
  338. foreach ($conditions as $cond) {
  339. $tmp = true;
  340. foreach ($cond as $rule) {
  341. $function = self::$_rulesMap[$rule['condition']];
  342. if (!is_callable($function)) {
  343. if (!method_exists(Condition::class, $function)) {
  344. throw new ConditionNotAllowedException("Exception: $function condition not allowed");
  345. }
  346. $function = [Condition::class, $function];
  347. }
  348. $value = $this->getFromNested($val, $rule['key']);
  349. $return = $value instanceof ValueNotFound ? false : call_user_func_array($function, [$value, $rule['value']]);
  350. $tmp &= $return;
  351. }
  352. $res |= $tmp;
  353. }
  354. return $res;
  355. });
  356. return $result;
  357. }
  358. /**
  359. * make WHERE clause
  360. *
  361. * @param string $key
  362. * @param string $condition
  363. * @param mixed $value
  364. * @return $this
  365. */
  366. public function where($key, $condition = null, $value = null)
  367. {
  368. if (!is_null($condition) && is_null($value)) {
  369. $value = $condition;
  370. $condition = '=';
  371. }
  372. if (count($this->_conditions) < 1) {
  373. array_push($this->_conditions, []);
  374. }
  375. return $this->makeWhere($key, $condition, $value);
  376. }
  377. /**
  378. * make WHERE clause with OR
  379. *
  380. * @param string $key
  381. * @param string $condition
  382. * @param mixed $value
  383. * @return $this
  384. */
  385. public function orWhere($key = null, $condition = null, $value = null)
  386. {
  387. if (!is_null($condition) && is_null($value)) {
  388. $value = $condition;
  389. $condition = '=';
  390. }
  391. array_push($this->_conditions, []);
  392. return $this->makeWhere($key, $condition, $value);
  393. }
  394. /**
  395. * generator for AND and OR where
  396. *
  397. * @param string $key
  398. * @param string $condition
  399. * @param mixed $value
  400. * @return $this
  401. */
  402. protected function makeWhere($key, $condition = null, $value = null)
  403. {
  404. $current = end($this->_conditions);
  405. $index = key($this->_conditions);
  406. if (is_callable($key)) {
  407. $key($this);
  408. return $this;
  409. }
  410. array_push($current, [
  411. 'key' => $key,
  412. 'condition' => $condition,
  413. 'value' => $value,
  414. ]);
  415. $this->_conditions[$index] = $current;
  416. return $this;
  417. }
  418. /**
  419. * make WHERE IN clause
  420. *
  421. * @param string $key
  422. * @param array $value
  423. * @return $this
  424. */
  425. public function whereIn($key = null, $value = [])
  426. {
  427. $this->where($key, 'in', $value);
  428. return $this;
  429. }
  430. /**
  431. * make WHERE NOT IN clause
  432. *
  433. * @param string $key
  434. * @param mixed $value
  435. * @return $this
  436. */
  437. public function whereNotIn($key = null, $value = [])
  438. {
  439. $this->where($key, 'notin', $value);
  440. return $this;
  441. }
  442. /**
  443. * make WHERE NULL clause
  444. *
  445. * @param string $key
  446. * @return $this
  447. */
  448. public function whereNull($key = null)
  449. {
  450. $this->where($key, 'null', 'null');
  451. return $this;
  452. }
  453. /**
  454. * make WHERE Boolean clause
  455. *
  456. * @param string $key
  457. * @return $this
  458. */
  459. public function whereBool($key, $value)
  460. {
  461. if (is_bool($value)) {
  462. $this->where($key, '==', $value);
  463. }
  464. return $this;
  465. }
  466. /**
  467. * make WHERE NOT NULL clause
  468. *
  469. * @param string $key
  470. * @return $this
  471. */
  472. public function whereNotNull($key = null)
  473. {
  474. $this->where($key, 'notnull', 'null');
  475. return $this;
  476. }
  477. /**
  478. * make WHERE START WITH clause
  479. *
  480. * @param string $key
  481. * @param string $value
  482. * @return $this
  483. */
  484. public function whereStartsWith($key, $value)
  485. {
  486. $this->where($key, 'startswith', $value);
  487. return $this;
  488. }
  489. /**
  490. * make WHERE ENDS WITH clause
  491. *
  492. * @param string $key
  493. * @param string $value
  494. * @return $this
  495. */
  496. public function whereEndsWith($key, $value)
  497. {
  498. $this->where($key, 'endswith', $value);
  499. return $this;
  500. }
  501. /**
  502. * make WHERE MATCH clause
  503. *
  504. * @param string $key
  505. * @param string $value
  506. * @return $this
  507. */
  508. public function whereMatch($key, $value)
  509. {
  510. $this->where($key, 'match', $value);
  511. return $this;
  512. }
  513. /**
  514. * make WHERE CONTAINS clause
  515. *
  516. * @param string $key
  517. * @param string $value
  518. * @return $this
  519. */
  520. public function whereContains($key, $value)
  521. {
  522. $this->where($key, 'contains', $value);
  523. return $this;
  524. }
  525. /**
  526. * make WHERE DATE clause
  527. *
  528. * @param string $key
  529. * @param string $value
  530. * @return $this
  531. */
  532. public function whereDate($key, $value)
  533. {
  534. $this->where($key, 'dates', $value);
  535. return $this;
  536. }
  537. /**
  538. * make WHERE month clause
  539. *
  540. * @param string $key
  541. * @param string $value
  542. * @return $this
  543. */
  544. public function whereMonth($key, $value)
  545. {
  546. $this->where($key, 'month', $value);
  547. return $this;
  548. }
  549. /**
  550. * make WHERE Year clause
  551. *
  552. * @param string $key
  553. * @param string $value
  554. * @return $this
  555. */
  556. public function whereYear($key, $value)
  557. {
  558. $this->where($key, 'year', $value);
  559. return $this;
  560. }
  561. /**
  562. * make macro for custom where clause
  563. *
  564. * @param string $name
  565. * @param callable $fn
  566. * @return bool
  567. */
  568. public static function macro($name, callable $fn)
  569. {
  570. if (!in_array($name, self::$_rulesMap)) {
  571. self::$_rulesMap[$name] = $fn;
  572. return true;
  573. }
  574. return false;
  575. }
  576. }
  577. ?>