PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/runtime/lib/query/Criterion.php

https://github.com/1989gaurav/Propel
PHP | 543 lines | 274 code | 63 blank | 206 comment | 41 complexity | f0ec940fb1e73b06682edb7873b224a8 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. *
  7. * @license MIT License
  8. */
  9. /**
  10. * This is an "inner" class that describes an object in the criteria.
  11. *
  12. * In Torque this is an inner class of the Criteria class.
  13. *
  14. * @author Hans Lellelid <hans@xmpl.org> (Propel)
  15. * @version $Revision$
  16. * @package propel.runtime.query
  17. */
  18. class Criterion
  19. {
  20. const UND = " AND ";
  21. const ODER = " OR ";
  22. /** Value of the CO. */
  23. protected $value;
  24. /** Comparison value.
  25. * @var SqlEnum
  26. */
  27. protected $comparison;
  28. /** Table name. */
  29. protected $table;
  30. /** Real table name */
  31. protected $realtable;
  32. /** Column name. */
  33. protected $column;
  34. /** flag to ignore case in comparison */
  35. protected $ignoreStringCase = false;
  36. /**
  37. * The DBAdaptor which might be used to get db specific
  38. * variations of sql.
  39. */
  40. protected $db;
  41. /**
  42. * other connected criteria and their conjunctions.
  43. */
  44. protected $clauses = array();
  45. protected $conjunctions = array();
  46. /** "Parent" Criteria class */
  47. protected $parent;
  48. /**
  49. * Create a new instance.
  50. *
  51. * @param Criteria $parent The outer class (this is an "inner" class).
  52. * @param string $column TABLE.COLUMN format.
  53. * @param mixed $value
  54. * @param string $comparison
  55. */
  56. public function __construct(Criteria $outer, $column, $value, $comparison = null)
  57. {
  58. $this->value = $value;
  59. $dotPos = strrpos($column, '.');
  60. if ($dotPos === false) {
  61. // no dot => aliased column
  62. $this->table = null;
  63. $this->column = $column;
  64. } else {
  65. $this->table = substr($column, 0, $dotPos);
  66. $this->column = substr($column, $dotPos + 1);
  67. }
  68. $this->comparison = ($comparison === null) ? Criteria::EQUAL : $comparison;
  69. $this->init($outer);
  70. }
  71. /**
  72. * Init some properties with the help of outer class
  73. * @param Criteria $criteria The outer class
  74. */
  75. public function init(Criteria $criteria)
  76. {
  77. // init $this->db
  78. try {
  79. $db = Propel::getDB($criteria->getDbName());
  80. $this->setDB($db);
  81. } catch (Exception $e) {
  82. // we are only doing this to allow easier debugging, so
  83. // no need to throw up the exception, just make note of it.
  84. Propel::log("Could not get a DBAdapter, sql may be wrong", Propel::LOG_ERR);
  85. }
  86. // init $this->realtable
  87. $realtable = $criteria->getTableForAlias($this->table);
  88. $this->realtable = $realtable ? $realtable : $this->table;
  89. }
  90. /**
  91. * Get the column name.
  92. *
  93. * @return string A String with the column name.
  94. */
  95. public function getColumn()
  96. {
  97. return $this->column;
  98. }
  99. /**
  100. * Set the table name.
  101. *
  102. * @param name A String with the table name.
  103. * @return void
  104. */
  105. public function setTable($name)
  106. {
  107. $this->table = $name;
  108. }
  109. /**
  110. * Get the table name.
  111. *
  112. * @return string A String with the table name.
  113. */
  114. public function getTable()
  115. {
  116. return $this->table;
  117. }
  118. /**
  119. * Get the comparison.
  120. *
  121. * @return string A String with the comparison.
  122. */
  123. public function getComparison()
  124. {
  125. return $this->comparison;
  126. }
  127. /**
  128. * Get the value.
  129. *
  130. * @return mixed An Object with the value.
  131. */
  132. public function getValue()
  133. {
  134. return $this->value;
  135. }
  136. /**
  137. * Get the value of db.
  138. * The DBAdapter which might be used to get db specific
  139. * variations of sql.
  140. * @return DBAdapter value of db.
  141. */
  142. public function getDB()
  143. {
  144. return $this->db;
  145. }
  146. /**
  147. * Set the value of db.
  148. * The DBAdapter might be used to get db specific variations of sql.
  149. * @param DBAdapter $v Value to assign to db.
  150. * @return void
  151. */
  152. public function setDB(DBAdapter $v)
  153. {
  154. $this->db = $v;
  155. foreach ( $this->clauses as $clause ) {
  156. $clause->setDB($v);
  157. }
  158. }
  159. /**
  160. * Sets ignore case.
  161. *
  162. * @param boolean $b True if case should be ignored.
  163. * @return Criterion A modified Criterion object.
  164. */
  165. public function setIgnoreCase($b)
  166. {
  167. $this->ignoreStringCase = (boolean) $b;
  168. return $this;
  169. }
  170. /**
  171. * Is ignore case on or off?
  172. *
  173. * @return boolean True if case is ignored.
  174. */
  175. public function isIgnoreCase()
  176. {
  177. return $this->ignoreStringCase;
  178. }
  179. /**
  180. * Get the list of clauses in this Criterion.
  181. * @return array
  182. */
  183. private function getClauses()
  184. {
  185. return $this->clauses;
  186. }
  187. /**
  188. * Get the list of conjunctions in this Criterion
  189. * @return array
  190. */
  191. public function getConjunctions()
  192. {
  193. return $this->conjunctions;
  194. }
  195. /**
  196. * Append an AND Criterion onto this Criterion's list.
  197. */
  198. public function addAnd(Criterion $criterion)
  199. {
  200. $this->clauses[] = $criterion;
  201. $this->conjunctions[] = self::UND;
  202. return $this;
  203. }
  204. /**
  205. * Append an OR Criterion onto this Criterion's list.
  206. * @return Criterion
  207. */
  208. public function addOr(Criterion $criterion)
  209. {
  210. $this->clauses[] = $criterion;
  211. $this->conjunctions[] = self::ODER;
  212. return $this;
  213. }
  214. /**
  215. * Appends a Prepared Statement representation of the Criterion
  216. * onto the buffer.
  217. *
  218. * @param string &$sb The string that will receive the Prepared Statement
  219. * @param array $params A list to which Prepared Statement parameters will be appended
  220. * @return void
  221. * @throws PropelException - if the expression builder cannot figure out how to turn a specified
  222. * expression into proper SQL.
  223. */
  224. public function appendPsTo(&$sb, array &$params)
  225. {
  226. $sb .= str_repeat ( '(', count($this->clauses) );
  227. $this->dispatchPsHandling($sb, $params);
  228. foreach ($this->clauses as $key => $clause) {
  229. $sb .= $this->conjunctions[$key];
  230. $clause->appendPsTo($sb, $params);
  231. $sb .= ')';
  232. }
  233. }
  234. /**
  235. * Figure out which Criterion method to use
  236. * to build the prepared statement and parameters using to the Criterion comparison
  237. * and call it to append the prepared statement and the parameters of the current clause
  238. *
  239. * @param string &$sb The string that will receive the Prepared Statement
  240. * @param array $params A list to which Prepared Statement parameters will be appended
  241. */
  242. protected function dispatchPsHandling(&$sb, array &$params)
  243. {
  244. switch ($this->comparison) {
  245. case Criteria::CUSTOM:
  246. // custom expression with no parameter binding
  247. $this->appendCustomToPs($sb, $params);
  248. break;
  249. case Criteria::IN:
  250. case Criteria::NOT_IN:
  251. // table.column IN (?, ?) or table.column NOT IN (?, ?)
  252. $this->appendInToPs($sb, $params);
  253. break;
  254. case Criteria::LIKE:
  255. case Criteria::NOT_LIKE:
  256. case Criteria::ILIKE:
  257. case Criteria::NOT_ILIKE:
  258. // table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres)
  259. $this->appendLikeToPs($sb, $params);
  260. break;
  261. default:
  262. // table.column = ? or table.column >= ? etc. (traditional expressions, the default)
  263. $this->appendBasicToPs($sb, $params);
  264. }
  265. }
  266. /**
  267. * Appends a Prepared Statement representation of the Criterion onto the buffer
  268. * For custom expressions with no binding, e.g. 'NOW() = 1'
  269. *
  270. * @param string &$sb The string that will receive the Prepared Statement
  271. * @param array $params A list to which Prepared Statement parameters will be appended
  272. */
  273. protected function appendCustomToPs(&$sb, array &$params)
  274. {
  275. if ($this->value !== "") {
  276. $sb .= (string) $this->value;
  277. }
  278. }
  279. /**
  280. * Appends a Prepared Statement representation of the Criterion onto the buffer
  281. * For IN expressions, e.g. table.column IN (?, ?) or table.column NOT IN (?, ?)
  282. *
  283. * @param string &$sb The string that will receive the Prepared Statement
  284. * @param array $params A list to which Prepared Statement parameters will be appended
  285. */
  286. protected function appendInToPs(&$sb, array &$params)
  287. {
  288. if ($this->value !== "") {
  289. $bindParams = array();
  290. $index = count($params); // to avoid counting the number of parameters for each element in the array
  291. foreach ((array) $this->value as $value) {
  292. $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $value);
  293. $index++; // increment this first to correct for wanting bind params to start with :p1
  294. $bindParams[] = ':p' . $index;
  295. }
  296. if (count($bindParams)) {
  297. $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
  298. $sb .= $field . $this->comparison . '(' . implode(',', $bindParams) . ')';
  299. } else {
  300. $sb .= ($this->comparison === Criteria::IN) ? "1<>1" : "1=1";
  301. }
  302. }
  303. }
  304. /**
  305. * Appends a Prepared Statement representation of the Criterion onto the buffer
  306. * For LIKE expressions, e.g. table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres)
  307. *
  308. * @param string &$sb The string that will receive the Prepared Statement
  309. * @param array $params A list to which Prepared Statement parameters will be appended
  310. */
  311. protected function appendLikeToPs(&$sb, array &$params)
  312. {
  313. $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
  314. $db = $this->getDb();
  315. // If selection is case insensitive use ILIKE for PostgreSQL or SQL
  316. // UPPER() function on column name for other databases.
  317. if ($this->ignoreStringCase) {
  318. if ($db instanceof DBPostgres) {
  319. if ($this->comparison === Criteria::LIKE) {
  320. $this->comparison = Criteria::ILIKE;
  321. } elseif ($this->comparison === Criteria::NOT_LIKE) {
  322. $this->comparison = Criteria::NOT_ILIKE;
  323. }
  324. } else {
  325. $field = $db->ignoreCase($field);
  326. }
  327. }
  328. $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $this->value);
  329. $sb .= $field . $this->comparison;
  330. // If selection is case insensitive use SQL UPPER() function
  331. // on criteria or, if Postgres we are using ILIKE, so not necessary.
  332. if ($this->ignoreStringCase && !($db instanceof DBPostgres)) {
  333. $sb .= $db->ignoreCase(':p'.count($params));
  334. } else {
  335. $sb .= ':p'.count($params);
  336. }
  337. }
  338. /**
  339. * Appends a Prepared Statement representation of the Criterion onto the buffer
  340. * For traditional expressions, e.g. table.column = ? or table.column >= ? etc.
  341. *
  342. * @param string &$sb The string that will receive the Prepared Statement
  343. * @param array $params A list to which Prepared Statement parameters will be appended
  344. */
  345. protected function appendBasicToPs(&$sb, array &$params)
  346. {
  347. $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
  348. // NULL VALUES need special treatment because the SQL syntax is different
  349. // i.e. table.column IS NULL rather than table.column = null
  350. if ($this->value !== null) {
  351. // ANSI SQL functions get inserted right into SQL (not escaped, etc.)
  352. if ($this->value === Criteria::CURRENT_DATE || $this->value === Criteria::CURRENT_TIME || $this->value === Criteria::CURRENT_TIMESTAMP) {
  353. $sb .= $field . $this->comparison . $this->value;
  354. } else {
  355. $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $this->value);
  356. // default case, it is a normal col = value expression; value
  357. // will be replaced w/ '?' and will be inserted later using PDO bindValue()
  358. if ($this->ignoreStringCase) {
  359. $sb .= $this->getDb()->ignoreCase($field) . $this->comparison . $this->getDb()->ignoreCase(':p'.count($params));
  360. } else {
  361. $sb .= $field . $this->comparison . ':p'.count($params);
  362. }
  363. }
  364. } else {
  365. // value is null, which means it was either not specified or specifically
  366. // set to null.
  367. if ($this->comparison === Criteria::EQUAL || $this->comparison === Criteria::ISNULL) {
  368. $sb .= $field . Criteria::ISNULL;
  369. } elseif ($this->comparison === Criteria::NOT_EQUAL || $this->comparison === Criteria::ISNOTNULL) {
  370. $sb .= $field . Criteria::ISNOTNULL;
  371. } else {
  372. // for now throw an exception, because not sure how to interpret this
  373. throw new PropelException("Could not build SQL for expression: $field " . $this->comparison . " NULL");
  374. }
  375. }
  376. }
  377. /**
  378. * This method checks another Criteria to see if they contain
  379. * the same attributes and hashtable entries.
  380. * @return boolean
  381. */
  382. public function equals($obj)
  383. {
  384. // TODO: optimize me with early outs
  385. if ($this === $obj) {
  386. return true;
  387. }
  388. if (($obj === null) || !($obj instanceof Criterion)) {
  389. return false;
  390. }
  391. $crit = $obj;
  392. $isEquiv = ( ( ($this->table === null && $crit->getTable() === null)
  393. || ( $this->table !== null && $this->table === $crit->getTable() )
  394. )
  395. && $this->column === $crit->getColumn()
  396. && $this->comparison === $crit->getComparison());
  397. // check chained criterion
  398. $clausesLength = count($this->clauses);
  399. $isEquiv &= (count($crit->getClauses()) == $clausesLength);
  400. $critConjunctions = $crit->getConjunctions();
  401. $critClauses = $crit->getClauses();
  402. for ($i=0; $i < $clausesLength && $isEquiv; $i++) {
  403. $isEquiv &= ($this->conjunctions[$i] === $critConjunctions[$i]);
  404. $isEquiv &= ($this->clauses[$i] === $critClauses[$i]);
  405. }
  406. if ($isEquiv) {
  407. $isEquiv &= $this->value === $crit->getValue();
  408. }
  409. return $isEquiv;
  410. }
  411. /**
  412. * Returns a hash code value for the object.
  413. */
  414. public function hashCode()
  415. {
  416. $h = crc32(serialize($this->value)) ^ crc32($this->comparison);
  417. if ($this->table !== null) {
  418. $h ^= crc32($this->table);
  419. }
  420. if ($this->column !== null) {
  421. $h ^= crc32($this->column);
  422. }
  423. foreach ( $this->clauses as $clause ) {
  424. // TODO: i KNOW there is a php incompatibility with the following line
  425. // but i dont remember what it is, someone care to look it up and
  426. // replace it if it doesnt bother us?
  427. // $clause->appendPsTo($sb='',$params=array());
  428. $sb = '';
  429. $params = array();
  430. $clause->appendPsTo($sb,$params);
  431. $h ^= crc32(serialize(array($sb,$params)));
  432. unset ( $sb, $params );
  433. }
  434. return $h;
  435. }
  436. /**
  437. * Get all tables from nested criterion objects
  438. * @return array
  439. */
  440. public function getAllTables()
  441. {
  442. $tables = array();
  443. $this->addCriterionTable($this, $tables);
  444. return $tables;
  445. }
  446. /**
  447. * method supporting recursion through all criterions to give
  448. * us a string array of tables from each criterion
  449. * @return void
  450. */
  451. private function addCriterionTable(Criterion $c, array &$s)
  452. {
  453. $s[] = $c->getTable();
  454. foreach ( $c->getClauses() as $clause ) {
  455. $this->addCriterionTable($clause, $s);
  456. }
  457. }
  458. /**
  459. * get an array of all criterion attached to this
  460. * recursing through all sub criterion
  461. * @return array Criterion[]
  462. */
  463. public function getAttachedCriterion()
  464. {
  465. $criterions = array($this);
  466. foreach ($this->getClauses() as $criterion) {
  467. $criterions = array_merge($criterions, $criterion->getAttachedCriterion());
  468. }
  469. return $criterions;
  470. }
  471. /**
  472. * Ensures deep cloning of attached objects
  473. */
  474. public function __clone()
  475. {
  476. foreach ($this->clauses as $key => $criterion) {
  477. $this->clauses[$key] = clone $criterion;
  478. }
  479. }
  480. }