/libraries/dabl/database/query/Condition.php

https://github.com/coleHafner/coleandheather_dabl · PHP · 585 lines · 309 code · 60 blank · 216 comment · 45 complexity · 3de1d6cbf842a365162bb1a5aec9b221 MD5 · raw file

  1. <?php
  2. /**
  3. * Represents/contains "AND" or "OR" statements
  4. *
  5. * $q = new Query("table");
  6. * $q->setAction("SELECT");
  7. *
  8. * Example:
  9. *
  10. * $c = new Condition;
  11. * $c->addAnd('Column',$value); - $c statement = "Column=$value"
  12. * $c->addOr('Column2',$value2,"<"); - $c statement = "Column=$value OR Column2<$value2"
  13. *
  14. * ..could also be written like this:
  15. * $c->addAnd('Column',$value)->addOr('Column2',$value2,"<");
  16. *
  17. * $c2 = new Condition;
  18. * $c2->addAnd('Column3',$value3); - $c2 statement = "Column3=$value3"
  19. * $c2->addAnd('Column4',$value4); - $c2 statement = "Column3=$value3 AND Column4=$value4"
  20. *
  21. * $c->addOr($c2); - $c statement = "Column=$value OR Column2<$value2 OR (Column3=$value3 AND Column4=$value4)"
  22. *
  23. * $q->addAnd($c); - $q string = "SELECT * FROM table WHERE Column=$value OR Column2<$value2 OR (Column3=$value3 AND Column4=$value4)"
  24. */
  25. class Condition {
  26. /**
  27. * escape only the first parameter
  28. */
  29. const QUOTE_LEFT = 1;
  30. /**
  31. * escape only the second param
  32. */
  33. const QUOTE_RIGHT = 2;
  34. /**
  35. * escape both params
  36. */
  37. const QUOTE_BOTH = 3;
  38. /**
  39. * escape no params
  40. */
  41. const QUOTE_NONE = 4;
  42. private $conds = array();
  43. function __construct($left = null, $right = null, $operator = Query::EQUAL, $quote = null) {
  44. if (func_num_args() > 0) {
  45. $this->add($left, $right, $operator, $quote);
  46. }
  47. }
  48. /**
  49. * Returns new instance of self by passing arguments directly to constructor.
  50. * @param $left mixed
  51. * @param $right mixed[optional]
  52. * @param $operator string[optional]
  53. * @param $quote int[optional]
  54. * @return Condition
  55. */
  56. static function create($left = null, $right = null, $operator=Query::EQUAL, $quote = null) {
  57. return new self($left, $right, $operator, $quote);
  58. }
  59. /**
  60. * @return string
  61. */
  62. private static function processCondition($left = null, $right = null, $operator = Query::EQUAL, $quote = null) {
  63. if ($left instanceof QueryStatement && 1 === func_num_args()) {
  64. return $left;
  65. }
  66. $statement = new QueryStatement;
  67. // Left can be a Condition
  68. if ($left instanceof self) {
  69. $clause_statement = $left->getQueryStatement();
  70. if (null === $clause_statement) {
  71. return null;
  72. }
  73. $clause_statement->string = '(' . $clause_statement->string . ')';
  74. return $clause_statement;
  75. }
  76. if (null === $quote) {
  77. // You can skip $operator and specify $quote with parameter 3
  78. if (is_int($operator)) {
  79. $quote = $operator;
  80. $operator = Query::EQUAL;
  81. } else {
  82. $quote = self::QUOTE_RIGHT;
  83. }
  84. }
  85. if (Query::BEGINS_WITH === $operator) {
  86. $right .= '%';
  87. $operator = Query::LIKE;
  88. } elseif (Query::ENDS_WITH === $operator) {
  89. $right = '%' . $right;
  90. $operator = Query::LIKE;
  91. } elseif (Query::CONTAINS === $operator) {
  92. $right = '%' . $right . '%';
  93. $operator = Query::LIKE;
  94. }
  95. // Escape $left
  96. if ($quote === self::QUOTE_LEFT || $quote === self::QUOTE_BOTH) {
  97. $statement->addParam($left);
  98. $left = QueryStatement::PARAM;
  99. } else {
  100. $statement->addIdentifier($left);
  101. $left = QueryStatement::IDENTIFIER;
  102. }
  103. $is_query = $right instanceof Query;
  104. $is_array = false === $is_query && is_array($right);
  105. if ($is_array || $is_query) {
  106. if (false === $is_query || 1 !== $right->getLimit()) {
  107. // Convert any sort of equality operator to something suitable for arrays
  108. switch ($operator) {
  109. // Various forms of equal
  110. case Query::IN:
  111. break;
  112. case Query::EQUAL:
  113. $operator = Query::IN;
  114. break;
  115. case Query::BETWEEN:
  116. break;
  117. // Various forms of not equal
  118. case Query::NOT_IN:
  119. break;
  120. case Query::NOT_EQUAL:
  121. case Query::ALT_NOT_EQUAL:
  122. $operator = Query::NOT_IN;
  123. break;
  124. default:
  125. throw new Exception($operator . ' unknown for comparing an array.');
  126. }
  127. }
  128. // Right can be a Query, if you're trying to nest queries, like "WHERE MyColumn = (SELECT OtherColumn From MyTable LIMIT 1)"
  129. if ($is_query) {
  130. if (!$right->getTable()) {
  131. throw new Exception('right does not have a table, so it cannot be nested.');
  132. }
  133. $clause_statement = $right->getQuery();
  134. if (null === $clause_statement) {
  135. return null;
  136. }
  137. $right = '(' . $clause_statement->string . ')';
  138. $statement->addParams($clause_statement->params);
  139. $statement->addIdentifiers($clause_statement->identifiers);
  140. if ($quote !== self::QUOTE_LEFT) {
  141. $quote = self::QUOTE_NONE;
  142. }
  143. } elseif ($is_array) {
  144. $array_len = count($right);
  145. // BETWEEN
  146. if (2 === $array_len && $operator === Query::BETWEEN) {
  147. $statement->string = $left . ' ' . $operator . ' ' . QueryStatement::PARAM . ' AND ' . QueryStatement::PARAM;
  148. $statement->addParams($right);
  149. return $statement;
  150. } elseif (0 === $array_len) {
  151. // Handle empty arrays
  152. if ($operator === Query::IN) {
  153. $statement->string = '(0 = 1)';
  154. return $statement;
  155. } elseif ($operator === Query::NOT_IN) {
  156. return null;
  157. }
  158. } elseif ($quote === self::QUOTE_RIGHT || $quote === self::QUOTE_BOTH) {
  159. $statement->addParams($right);
  160. $r_string = '(';
  161. for ($x = 0; $x < $array_len; ++$x) {
  162. if (0 < $x) {
  163. $r_string .= ',';
  164. }
  165. $r_string .= QueryStatement::PARAM;
  166. }
  167. $right = $r_string . ')';
  168. }
  169. }
  170. } else {
  171. if (null === $right) {
  172. if ($operator === Query::NOT_EQUAL || $operator === Query::ALT_NOT_EQUAL) {
  173. // IS NOT NULL
  174. $operator = Query::IS_NOT_NULL;
  175. } elseif ($operator === Query::EQUAL) {
  176. // IS NULL
  177. $operator = Query::IS_NULL;
  178. }
  179. }
  180. if ($operator === Query::IS_NULL || $operator === Query::IS_NOT_NULL) {
  181. $right = null;
  182. } elseif ($quote === self::QUOTE_RIGHT || $quote == self::QUOTE_BOTH) {
  183. $statement->addParam($right);
  184. $right = QueryStatement::PARAM;
  185. }
  186. }
  187. $statement->string = $left . ' ' . $operator . ' ' . $right;
  188. return $statement;
  189. }
  190. /**
  191. * Alias of addAnd
  192. * @return Condition
  193. */
  194. function add($left, $right = null, $operator = Query::EQUAL, $quote = null) {
  195. if (func_num_args() === 1) {
  196. return $this->addAnd($left);
  197. }
  198. return $this->addAnd($left, $right, $operator, $quote);
  199. }
  200. /**
  201. * Adds an "AND" condition to the array of conditions.
  202. * @param $left mixed
  203. * @param $right mixed[optional]
  204. * @param $operator string[optional]
  205. * @param $quote int[optional]
  206. * @return Condition
  207. */
  208. function addAnd($left, $right = null, $operator = Query::EQUAL, $quote = null) {
  209. if (null === $left) {
  210. return $this;
  211. }
  212. if (is_array($left)) {
  213. foreach ($left as $key => &$value) {
  214. $this->addAnd($key, $value);
  215. }
  216. return $this;
  217. }
  218. $this->conds[] = array('AND', func_get_args());
  219. return $this;
  220. }
  221. /**
  222. * @return QueryStatement[]
  223. */
  224. function getAnds() {
  225. throw new Exception("self::getAnds() can't do what you want anymore...");
  226. // $ors = array();
  227. // foreach ($this->conds as $cond) {
  228. // if ('AND' === $cond[0]) {
  229. // $ors[] = call_user_func_array(array('self', 'processCondition'), $cond[1]);
  230. // }
  231. // }
  232. // return $ors;
  233. }
  234. /**
  235. * Adds an "OR" condition to the array of conditions
  236. * @param $left mixed
  237. * @param $right mixed[optional]
  238. * @param $operator string[optional]
  239. * @param $quote int[optional]
  240. * @return Condition
  241. */
  242. function addOr($left, $right = null, $operator = Query::EQUAL, $quote = null) {
  243. if (null === $left) {
  244. return $this;
  245. }
  246. if (is_array($left)) {
  247. foreach ($left as $key => &$value) {
  248. $this->addOr($key, $value);
  249. }
  250. return $this;
  251. }
  252. $this->conds[] = array('OR', func_get_args());
  253. return $this;
  254. }
  255. /**
  256. * @return QueryStatement[]
  257. */
  258. function getOrs() {
  259. throw new Exception("self::getOrs() can't do what you want anymore...");
  260. // $ands = array();
  261. // foreach ($this->conds as $cond) {
  262. // if ('AND' === $cond[0]) {
  263. // $ands[] = call_user_func_array(array('self', 'processCondition'), $cond[1]);
  264. // }
  265. // }
  266. // return $ands;
  267. }
  268. /**
  269. * @param mixed $column
  270. * @param mixed $value
  271. * @return Condition
  272. */
  273. function andNot($column, $value) {
  274. return $this->addAnd($column, $value, Query::NOT_EQUAL);
  275. }
  276. /**
  277. * @param mixed $column
  278. * @param mixed $value
  279. * @return Condition
  280. */
  281. function andLike($column, $value) {
  282. return $this->addAnd($column, $value, Query::LIKE);
  283. }
  284. /**
  285. * @param mixed $column
  286. * @param mixed $value
  287. * @return Condition
  288. */
  289. function andNotLike($column, $value) {
  290. return $this->addAnd($column, $value, Query::NOT_LIKE);
  291. }
  292. /**
  293. * @param mixed $column
  294. * @param mixed $value
  295. * @return Condition
  296. */
  297. function andGreater($column, $value) {
  298. return $this->addAnd($column, $value, Query::GREATER_THAN);
  299. }
  300. /**
  301. * @param mixed $column
  302. * @param mixed $value
  303. * @return Condition
  304. */
  305. function andGreaterEqual($column, $value) {
  306. return $this->addAnd($column, $value, Query::GREATER_EQUAL);
  307. }
  308. /**
  309. * @param mixed $column
  310. * @param mixed $value
  311. * @return Condition
  312. */
  313. function andLess($column, $value) {
  314. return $this->addAnd($column, $value, Query::LESS_THAN);
  315. }
  316. /**
  317. * @param mixed $column
  318. * @param mixed $value
  319. * @return Condition
  320. */
  321. function andLessEqual($column, $value) {
  322. return $this->addAnd($column, $value, Query::LESS_EQUAL);
  323. }
  324. /**
  325. * @param mixed $column
  326. * @return Condition
  327. */
  328. function andNull($column) {
  329. return $this->addAnd($column, null);
  330. }
  331. /**
  332. * @param mixed $column
  333. * @return Condition
  334. */
  335. function andNotNull($column) {
  336. return $this->addAnd($column, null, Query::NOT_EQUAL);
  337. }
  338. /**
  339. * @param mixed $column
  340. * @param mixed $from
  341. * @param mixed $to
  342. * @return Condition
  343. */
  344. function andBetween($column, $from, $to) {
  345. return $this->addAnd($column, array($from, $to), Query::BETWEEN);
  346. }
  347. /**
  348. * @param mixed $column
  349. * @param mixed $value
  350. * @return Condition
  351. */
  352. function andBeginsWith($column, $value) {
  353. return $this->addAnd($column, $value, Query::BEGINS_WITH);
  354. }
  355. /**
  356. * @param mixed $column
  357. * @param mixed $value
  358. * @return Condition
  359. */
  360. function andEndsWith($column, $value) {
  361. return $this->addAnd($column, $value, Query::ENDS_WITH);
  362. }
  363. /**
  364. * @param mixed $column
  365. * @param mixed $value
  366. * @return Condition
  367. */
  368. function andContains($column, $value) {
  369. return $this->addAnd($column, $value, Query::CONTAINS);
  370. }
  371. /**
  372. * @param mixed $column
  373. * @param mixed $value
  374. * @return Condition
  375. */
  376. function orNot($column, $value) {
  377. return $this->addOr($column, $value, Query::NOT_EQUAL);
  378. }
  379. /**
  380. * @param mixed $column
  381. * @param mixed $value
  382. * @return Condition
  383. */
  384. function orLike($column, $value) {
  385. return $this->addOr($column, $value, Query::LIKE);
  386. }
  387. /**
  388. * @param mixed $column
  389. * @param mixed $value
  390. * @return Condition
  391. */
  392. function orNotLike($column, $value) {
  393. return $this->addOr($column, $value, Query::NOT_LIKE);
  394. }
  395. /**
  396. * @param mixed $column
  397. * @param mixed $value
  398. * @return Condition
  399. */
  400. function orGreater($column, $value) {
  401. return $this->addOr($column, $value, Query::GREATER_THAN);
  402. }
  403. /**
  404. * @param mixed $column
  405. * @param mixed $value
  406. * @return Condition
  407. */
  408. function orGreaterEqual($column, $value) {
  409. return $this->addOr($column, $value, Query::GREATER_EQUAL);
  410. }
  411. /**
  412. * @param mixed $column
  413. * @param mixed $value
  414. * @return Condition
  415. */
  416. function orLess($column, $value) {
  417. return $this->addOr($column, $value, Query::LESS_THAN);
  418. }
  419. /**
  420. * @param mixed $column
  421. * @param mixed $value
  422. * @return Condition
  423. */
  424. function orLessEqual($column, $value) {
  425. return $this->addOr($column, $value, Query::LESS_EQUAL);
  426. }
  427. /**
  428. * @param mixed $column
  429. * @return Condition '
  430. */
  431. function orNull($column) {
  432. return $this->addOr($column, null);
  433. }
  434. /**
  435. * @param mixed $column
  436. * @return Condition
  437. */
  438. function orNotNull($column) {
  439. return $this->addOr($column, null, Query::NOT_EQUAL);
  440. }
  441. /**
  442. * @param mixed $column
  443. * @param mixed $from
  444. * @param mixed $to
  445. * @return Condition
  446. */
  447. function orBetween($column, $from, $to) {
  448. return $this->addOr($column, array($from, $to), Query::BETWEEN);
  449. }
  450. /**
  451. * @param mixed $column
  452. * @param mixed $value
  453. * @return Condition
  454. */
  455. function orBeginsWith($column, $value) {
  456. return $this->addOr($column, $value, Query::BEGINS_WITH);
  457. }
  458. /**
  459. * @param mixed $column
  460. * @param mixed $value
  461. * @return Condition
  462. */
  463. function orEndsWith($column, $value) {
  464. return $this->addOr($column, $value, Query::ENDS_WITH);
  465. }
  466. /**
  467. * @param mixed $column
  468. * @param mixed $value
  469. * @return Condition
  470. */
  471. function orContains($column, $value) {
  472. return $this->addOr($column, $value, Query::CONTAINS);
  473. }
  474. /**
  475. * Builds and returns a string representation of $this Condition
  476. * @return QueryStatement
  477. */
  478. function getQueryStatement() {
  479. if (0 === count($this->conds)) {
  480. return null;
  481. }
  482. $stmnt = new QueryStatement;
  483. foreach ($this->conds as $num => &$cond) {
  484. if (0 === $num) {
  485. $sep = '';
  486. } else {
  487. $sep = ((1 === $num && 'OR' === $this->conds[0][0]) ? 'OR' : $cond[0]) . ' ';
  488. }
  489. $cond_stmnt = null;
  490. // avoid call_user_func_array for better stack traces
  491. switch (count($cond[1])) {
  492. case 1:
  493. $cond_stmnt = self::processCondition($cond[1][0]);
  494. break;
  495. case 2:
  496. $cond_stmnt = self::processCondition($cond[1][0], $cond[1][1]);
  497. break;
  498. case 3:
  499. $cond_stmnt = self::processCondition($cond[1][0], $cond[1][1], $cond[1][2]);
  500. break;
  501. case 4:
  502. $cond_stmnt = self::processCondition($cond[1][0], $cond[1][1], $cond[1][2], $cond[1][3]);
  503. break;
  504. }
  505. if (null === $cond_stmnt) {
  506. continue;
  507. }
  508. $stmnt->string .= "\n\t$sep" . $cond_stmnt->string;
  509. $stmnt->addParams($cond_stmnt->params);
  510. $stmnt->addIdentifiers($cond_stmnt->identifiers);
  511. }
  512. return $stmnt;
  513. }
  514. /**
  515. * Builds and returns a string representation of $this Condition
  516. * @return string
  517. */
  518. function __toString() {
  519. return (string) $this->getQueryStatement();
  520. }
  521. }