123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- <?php
- /**
- * This file is part of the Propel package.
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- *
- * @license MIT License
- */
- /**
- * This is an "inner" class that describes an object in the criteria.
- *
- * In Torque this is an inner class of the Criteria class.
- *
- * @author Hans Lellelid <hans@xmpl.org> (Propel)
- * @version $Revision: 1740 $
- * @package propel.runtime.query
- */
- class Criterion
- {
- const UND = " AND ";
- const ODER = " OR ";
- /** Value of the CO. */
- protected $value;
- /** Comparison value.
- * @var SqlEnum
- */
- protected $comparison;
- /** Table name. */
- protected $table;
- /** Real table name */
- protected $realtable;
- /** Column name. */
- protected $column;
- /** flag to ignore case in comparison */
- protected $ignoreStringCase = false;
- /**
- * The DBAdaptor which might be used to get db specific
- * variations of sql.
- */
- protected $db;
- /**
- * other connected criteria and their conjunctions.
- */
- protected $clauses = array();
- protected $conjunctions = array();
- /** "Parent" Criteria class */
- protected $parent;
- /**
- * Create a new instance.
- *
- * @param Criteria $parent The outer class (this is an "inner" class).
- * @param string $column TABLE.COLUMN format.
- * @param mixed $value
- * @param string $comparison
- */
- public function __construct(Criteria $outer, $column, $value, $comparison = null)
- {
- $this->value = $value;
- $dotPos = strrpos($column, '.');
- if ($dotPos === false) {
- // no dot => aliased column
- $this->table = null;
- $this->column = $column;
- } else {
- $this->table = substr($column, 0, $dotPos);
- $this->column = substr($column, $dotPos + 1);
- }
- $this->comparison = ($comparison === null) ? Criteria::EQUAL : $comparison;
- $this->init($outer);
- }
- /**
- * Init some properties with the help of outer class
- * @param Criteria $criteria The outer class
- */
- public function init(Criteria $criteria)
- {
- // init $this->db
- try {
- $db = Propel::getDB($criteria->getDbName());
- $this->setDB($db);
- } catch (Exception $e) {
- // we are only doing this to allow easier debugging, so
- // no need to throw up the exception, just make note of it.
- Propel::log("Could not get a DBAdapter, sql may be wrong", Propel::LOG_ERR);
- }
- // init $this->realtable
- $realtable = $criteria->getTableForAlias($this->table);
- $this->realtable = $realtable ? $realtable : $this->table;
- }
- /**
- * Get the column name.
- *
- * @return string A String with the column name.
- */
- public function getColumn()
- {
- return $this->column;
- }
- /**
- * Set the table name.
- *
- * @param name A String with the table name.
- * @return void
- */
- public function setTable($name)
- {
- $this->table = $name;
- }
- /**
- * Get the table name.
- *
- * @return string A String with the table name.
- */
- public function getTable()
- {
- return $this->table;
- }
- /**
- * Get the comparison.
- *
- * @return string A String with the comparison.
- */
- public function getComparison()
- {
- return $this->comparison;
- }
- /**
- * Get the value.
- *
- * @return mixed An Object with the value.
- */
- public function getValue()
- {
- return $this->value;
- }
- /**
- * Get the value of db.
- * The DBAdapter which might be used to get db specific
- * variations of sql.
- * @return DBAdapter value of db.
- */
- public function getDB()
- {
- return $this->db;
- }
- /**
- * Set the value of db.
- * The DBAdapter might be used to get db specific variations of sql.
- * @param DBAdapter $v Value to assign to db.
- * @return void
- */
- public function setDB(DBAdapter $v)
- {
- $this->db = $v;
- foreach ( $this->clauses as $clause ) {
- $clause->setDB($v);
- }
- }
- /**
- * Sets ignore case.
- *
- * @param boolean $b True if case should be ignored.
- * @return Criterion A modified Criterion object.
- */
- public function setIgnoreCase($b)
- {
- $this->ignoreStringCase = (boolean) $b;
- return $this;
- }
- /**
- * Is ignore case on or off?
- *
- * @return boolean True if case is ignored.
- */
- public function isIgnoreCase()
- {
- return $this->ignoreStringCase;
- }
- /**
- * Get the list of clauses in this Criterion.
- * @return array
- */
- private function getClauses()
- {
- return $this->clauses;
- }
- /**
- * Get the list of conjunctions in this Criterion
- * @return array
- */
- public function getConjunctions()
- {
- return $this->conjunctions;
- }
- /**
- * Append an AND Criterion onto this Criterion's list.
- */
- public function addAnd(Criterion $criterion)
- {
- $this->clauses[] = $criterion;
- $this->conjunctions[] = self::UND;
- return $this;
- }
- /**
- * Append an OR Criterion onto this Criterion's list.
- * @return Criterion
- */
- public function addOr(Criterion $criterion)
- {
- $this->clauses[] = $criterion;
- $this->conjunctions[] = self::ODER;
- return $this;
- }
- /**
- * Appends a Prepared Statement representation of the Criterion
- * onto the buffer.
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- * @return void
- * @throws PropelException - if the expression builder cannot figure out how to turn a specified
- * expression into proper SQL.
- */
- public function appendPsTo(&$sb, array &$params)
- {
- $sb .= str_repeat ( '(', count($this->clauses) );
-
- $this->dispatchPsHandling($sb, $params);
- foreach ($this->clauses as $key => $clause) {
- $sb .= $this->conjunctions[$key];
- $clause->appendPsTo($sb, $params);
- $sb .= ')';
- }
- }
-
- /**
- * Figure out which Criterion method to use
- * to build the prepared statement and parameters using to the Criterion comparison
- * and call it to append the prepared statement and the parameters of the current clause
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- */
- protected function dispatchPsHandling(&$sb, array &$params)
- {
- switch ($this->comparison) {
- case Criteria::CUSTOM:
- // custom expression with no parameter binding
- $this->appendCustomToPs($sb, $params);
- break;
- case Criteria::IN:
- case Criteria::NOT_IN:
- // table.column IN (?, ?) or table.column NOT IN (?, ?)
- $this->appendInToPs($sb, $params);
- break;
- case Criteria::LIKE:
- case Criteria::NOT_LIKE:
- case Criteria::ILIKE:
- case Criteria::NOT_ILIKE:
- // table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres)
- $this->appendLikeToPs($sb, $params);
- break;
- default:
- // table.column = ? or table.column >= ? etc. (traditional expressions, the default)
- $this->appendBasicToPs($sb, $params);
- }
- }
-
- /**
- * Appends a Prepared Statement representation of the Criterion onto the buffer
- * For custom expressions with no binding, e.g. 'NOW() = 1'
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- */
- protected function appendCustomToPs(&$sb, array &$params)
- {
- if ($this->value !== "") {
- $sb .= (string) $this->value;
- }
- }
- /**
- * Appends a Prepared Statement representation of the Criterion onto the buffer
- * For IN expressions, e.g. table.column IN (?, ?) or table.column NOT IN (?, ?)
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- */
- protected function appendInToPs(&$sb, array &$params)
- {
- if ($this->value !== "") {
- $bindParams = array();
- $index = count($params); // to avoid counting the number of parameters for each element in the array
- foreach ((array) $this->value as $value) {
- $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $value);
- $index++; // increment this first to correct for wanting bind params to start with :p1
- $bindParams[] = ':p' . $index;
- }
- if (count($bindParams)) {
- $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
- $sb .= $field . $this->comparison . '(' . implode(',', $bindParams) . ')';
- } else {
- $sb .= ($this->comparison === Criteria::IN) ? "1<>1" : "1=1";
- }
- }
- }
- /**
- * Appends a Prepared Statement representation of the Criterion onto the buffer
- * For LIKE expressions, e.g. table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres)
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- */
- protected function appendLikeToPs(&$sb, array &$params)
- {
- $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
- $db = $this->getDb();
- // If selection is case insensitive use ILIKE for PostgreSQL or SQL
- // UPPER() function on column name for other databases.
- if ($this->ignoreStringCase) {
- if ($db instanceof DBPostgres) {
- if ($this->comparison === Criteria::LIKE) {
- $this->comparison = Criteria::ILIKE;
- } elseif ($this->comparison === Criteria::NOT_LIKE) {
- $this->comparison = Criteria::NOT_ILIKE;
- }
- } else {
- $field = $db->ignoreCase($field);
- }
- }
-
- $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $this->value);
-
- $sb .= $field . $this->comparison;
- // If selection is case insensitive use SQL UPPER() function
- // on criteria or, if Postgres we are using ILIKE, so not necessary.
- if ($this->ignoreStringCase && !($db instanceof DBPostgres)) {
- $sb .= $db->ignoreCase(':p'.count($params));
- } else {
- $sb .= ':p'.count($params);
- }
- }
- /**
- * Appends a Prepared Statement representation of the Criterion onto the buffer
- * For traditional expressions, e.g. table.column = ? or table.column >= ? etc.
- *
- * @param string &$sb The string that will receive the Prepared Statement
- * @param array $params A list to which Prepared Statement parameters will be appended
- */
- protected function appendBasicToPs(&$sb, array &$params)
- {
- $field = ($this->table === null) ? $this->column : $this->table . '.' . $this->column;
- // NULL VALUES need special treatment because the SQL syntax is different
- // i.e. table.column IS NULL rather than table.column = null
- if ($this->value !== null) {
- // ANSI SQL functions get inserted right into SQL (not escaped, etc.)
- if ($this->value === Criteria::CURRENT_DATE || $this->value === Criteria::CURRENT_TIME || $this->value === Criteria::CURRENT_TIMESTAMP) {
- $sb .= $field . $this->comparison . $this->value;
- } else {
-
- $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $this->value);
-
- // default case, it is a normal col = value expression; value
- // will be replaced w/ '?' and will be inserted later using PDO bindValue()
- if ($this->ignoreStringCase) {
- $sb .= $this->getDb()->ignoreCase($field) . $this->comparison . $this->getDb()->ignoreCase(':p'.count($params));
- } else {
- $sb .= $field . $this->comparison . ':p'.count($params);
- }
-
- }
- } else {
- // value is null, which means it was either not specified or specifically
- // set to null.
- if ($this->comparison === Criteria::EQUAL || $this->comparison === Criteria::ISNULL) {
- $sb .= $field . Criteria::ISNULL;
- } elseif ($this->comparison === Criteria::NOT_EQUAL || $this->comparison === Criteria::ISNOTNULL) {
- $sb .= $field . Criteria::ISNOTNULL;
- } else {
- // for now throw an exception, because not sure how to interpret this
- throw new PropelException("Could not build SQL for expression: $field " . $this->comparison . " NULL");
- }
- }
- }
-
- /**
- * This method checks another Criteria to see if they contain
- * the same attributes and hashtable entries.
- * @return boolean
- */
- public function equals($obj)
- {
- // TODO: optimize me with early outs
- if ($this === $obj) {
- return true;
- }
- if (($obj === null) || !($obj instanceof Criterion)) {
- return false;
- }
- $crit = $obj;
- $isEquiv = ( ( ($this->table === null && $crit->getTable() === null)
- || ( $this->table !== null && $this->table === $crit->getTable() )
- )
- && $this->column === $crit->getColumn()
- && $this->comparison === $crit->getComparison());
- // check chained criterion
- $clausesLength = count($this->clauses);
- $isEquiv &= (count($crit->getClauses()) == $clausesLength);
- $critConjunctions = $crit->getConjunctions();
- $critClauses = $crit->getClauses();
- for ($i=0; $i < $clausesLength && $isEquiv; $i++) {
- $isEquiv &= ($this->conjunctions[$i] === $critConjunctions[$i]);
- $isEquiv &= ($this->clauses[$i] === $critClauses[$i]);
- }
- if ($isEquiv) {
- $isEquiv &= $this->value === $crit->getValue();
- }
- return $isEquiv;
- }
- /**
- * Returns a hash code value for the object.
- */
- public function hashCode()
- {
- $h = crc32(serialize($this->value)) ^ crc32($this->comparison);
- if ($this->table !== null) {
- $h ^= crc32($this->table);
- }
- if ($this->column !== null) {
- $h ^= crc32($this->column);
- }
- foreach ( $this->clauses as $clause ) {
- // TODO: i KNOW there is a php incompatibility with the following line
- // but i dont remember what it is, someone care to look it up and
- // replace it if it doesnt bother us?
- // $clause->appendPsTo($sb='',$params=array());
- $sb = '';
- $params = array();
- $clause->appendPsTo($sb,$params);
- $h ^= crc32(serialize(array($sb,$params)));
- unset ( $sb, $params );
- }
- return $h;
- }
- /**
- * Get all tables from nested criterion objects
- * @return array
- */
- public function getAllTables()
- {
- $tables = array();
- $this->addCriterionTable($this, $tables);
- return $tables;
- }
- /**
- * method supporting recursion through all criterions to give
- * us a string array of tables from each criterion
- * @return void
- */
- private function addCriterionTable(Criterion $c, array &$s)
- {
- $s[] = $c->getTable();
- foreach ( $c->getClauses() as $clause ) {
- $this->addCriterionTable($clause, $s);
- }
- }
-
- /**
- * get an array of all criterion attached to this
- * recursing through all sub criterion
- * @return array Criterion[]
- */
- public function getAttachedCriterion()
- {
- $criterions = array($this);
- foreach ($this->getClauses() as $criterion) {
- $criterions = array_merge($criterions, $criterion->getAttachedCriterion());
- }
- return $criterions;
- }
-
- /**
- * Ensures deep cloning of attached objects
- */
- public function __clone()
- {
- foreach ($this->clauses as $key => $criterion) {
- $this->clauses[$key] = clone $criterion;
- }
- }
- }
|