src/Propel/Runtime/ActiveQuery/Criteria.php
<?php
/**
* MIT License. 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.
*/
namespace Propel\Runtime\ActiveQuery;
use Exception;
use Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion;
use Propel\Runtime\ActiveQuery\Criterion\CriterionFactory;
use Propel\Runtime\ActiveQuery\QueryExecutor\CountQueryExecutor;
use Propel\Runtime\ActiveQuery\QueryExecutor\DeleteAllQueryExecutor;
use Propel\Runtime\ActiveQuery\QueryExecutor\DeleteQueryExecutor;
use Propel\Runtime\ActiveQuery\QueryExecutor\InsertQueryExecutor;
use Propel\Runtime\ActiveQuery\QueryExecutor\SelectQueryExecutor;
use Propel\Runtime\ActiveQuery\QueryExecutor\UpdateQueryExecutor;
use Propel\Runtime\ActiveQuery\SqlBuilder\SelectQuerySqlBuilder;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\DataFetcher\DataFetcherInterface;
use Propel\Runtime\Exception\LogicException;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\Propel;
use Propel\Runtime\Util\PropelConditionalProxy;
/**
* This is a utility class for holding criteria information for a query.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Kaspars Jaudzems <kaspars.jaudzems@inbox.lv> (Propel)
* @author Frank Y. Kim <frank.kim@clearink.com> (Torque)
* @author John D. McNally <jmcnally@collab.net> (Torque)
* @author Brett McLaughlin <bmclaugh@algx.net> (Torque)
* @author Eric Dobbs <eric@dobbse.net> (Torque)
* @author Henning P. Schmiedehausen <hps@intermeta.de> (Torque)
* @author Sam Joseph <sam@neurogrid.com> (Torque)
*/
class Criteria
{
/**
* @var string
*/
public const EQUAL = '=';
/**
* @var string
*/
public const NOT_EQUAL = '<>';
/**
* @var string
*/
public const ALT_NOT_EQUAL = '!=';
/**
* @var string
*/
public const GREATER_THAN = '>';
/**
* @var string
*/
public const LESS_THAN = '<';
/**
* @var string
*/
public const GREATER_EQUAL = '>=';
/**
* @var string
*/
public const LESS_EQUAL = '<=';
/**
* @var string
*/
public const LIKE = ' LIKE ';
/**
* @var string
*/
public const NOT_LIKE = ' NOT LIKE ';
/**
* @var string
*/
public const CONTAINS_ALL = 'CONTAINS_ALL';
/**
* @var string
*/
public const CONTAINS_SOME = 'CONTAINS_SOME';
/**
* @var string
*/
public const CONTAINS_NONE = 'CONTAINS_NONE';
/**
* @var string
*/
public const ILIKE = ' ILIKE ';
/**
* @var string
*/
public const NOT_ILIKE = ' NOT ILIKE ';
/**
* @var string
*/
public const CUSTOM = 'CUSTOM';
/**
* @var string
*/
public const RAW = 'RAW';
/**
* @var string
*/
public const CUSTOM_EQUAL = 'CUSTOM_EQUAL';
/**
* @var string
*/
public const DISTINCT = 'DISTINCT';
/**
* @var string
*/
public const IN = ' IN ';
/**
* @var string
*/
public const NOT_IN = ' NOT IN ';
/**
* @var string
*/
public const ALL = 'ALL';
/**
* @var string
*/
public const JOIN = 'JOIN';
/**
* @var string
*/
public const BINARY_AND = '&';
/**
* @var string
*/
public const BINARY_OR = '|';
/**
* @var string
*/
public const BINARY_ALL = 'BINARY_ALL';
/**
* @var string
*/
public const BINARY_NONE = 'BINARY_NONE';
/**
* @var string
*/
public const ASC = 'ASC';
/**
* @var string
*/
public const DESC = 'DESC';
/**
* @var string
*/
public const ISNULL = ' IS NULL ';
/**
* @var string
*/
public const ISNOTNULL = ' IS NOT NULL ';
/**
* @var string
*/
public const CURRENT_DATE = 'CURRENT_DATE';
/**
* @var string
*/
public const CURRENT_TIME = 'CURRENT_TIME';
/**
* @var string
*/
public const CURRENT_TIMESTAMP = 'CURRENT_TIMESTAMP';
/**
* @var string
*/
public const LEFT_JOIN = 'LEFT JOIN';
/**
* @var string
*/
public const RIGHT_JOIN = 'RIGHT JOIN';
/**
* @var string
*/
public const INNER_JOIN = 'INNER JOIN';
/**
* @var string
*/
public const LOGICAL_OR = 'OR';
/**
* @var string
*/
public const LOGICAL_AND = 'AND';
/**
* @var bool
*/
protected $ignoreCase = false;
/**
* @var bool
*/
protected $singleRecord = false;
/**
* Storage of select data. Collection of column names.
*
* @var array<string>
*/
protected $selectColumns = [];
/**
* Storage of aliased select data. Collection of column names.
*
* @var array<string>
*/
protected $asColumns = [];
/**
* Storage of select modifiers data. Collection of modifier names.
*
* @var array<string>
*/
protected $selectModifiers = [];
/**
* Lock to be used to retrieve rows (if any).
*
* @var \Propel\Runtime\ActiveQuery\Lock|null
*/
protected $lock;
/**
* Storage of conditions data. Collection of Criterion objects.
*
* @var array<\Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion>
*/
protected $map = [];
/**
* Storage of ordering data. Collection of column names.
*
* @var array<string>
*/
protected $orderByColumns = [];
/**
* Storage of grouping data. Collection of column names.
*
* @var array<string>
*/
protected $groupByColumns = [];
/**
* Storage of having data.
*
* @var \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|null
*/
protected $having;
/**
* Storage of join data. collection of Join objects.
*
* @var array<\Propel\Runtime\ActiveQuery\Join>
*/
protected $joins = [];
/**
* @var array<\Propel\Runtime\ActiveQuery\Criteria>
*/
protected $selectQueries = [];
/**
* The name of the database.
*
* @var string
*/
protected $dbName;
/**
* The primary table for this Criteria.
* Useful in cases where there are no select or where
* columns.
*
* @var string
*/
protected $primaryTableName;
/**
* The name of the database as given in the constructor.
*
* @var string|null
*/
protected $originalDbName;
/**
* To limit the number of rows to return. <code>-1</code> means return all
* rows.
*
* @var int
*/
protected $limit = -1;
/**
* To start the results at a row other than the first one.
*
* @var int
*/
protected $offset = 0;
/**
* Comment to add to the SQL query
*
* @var string
*/
protected $queryComment;
/**
* @var array<string>
*/
protected $aliases = [];
/**
* @var bool
*/
protected $useTransaction = false;
/**
* Storage for Criterions expected to be combined
*
* @var array<string, \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion>
*/
protected $namedCriterions = [];
/**
* Default operator for combination of criterions
*
* @see addUsingOperator()
* @var string Criteria::LOGICAL_AND or Criteria::LOGICAL_OR
*/
protected $defaultCombineOperator = self::LOGICAL_AND;
/**
* @var \Propel\Runtime\Util\PropelConditionalProxy|null
*/
protected $conditionalProxy;
/**
* Whether identifier should be quoted.
*
* @var bool
*/
protected $identifierQuoting = false;
/**
* @var array
*/
public $replacedColumns = [];
/**
* temporary property used in replaceNames
*
* @var string|null
*/
protected $currentAlias;
/**
* @var bool
*/
protected $foundMatch = false;
/**
* Creates a new instance with the default capacity which corresponds to
* the specified database.
*
* @param string|null $dbName The database name.
*/
public function __construct(?string $dbName = null)
{
$this->setDbName($dbName);
$this->originalDbName = $dbName;
}
/**
* Get the criteria map, i.e. the array of Criterions
*
* @return array<\Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion>
*/
public function getMap(): array
{
return $this->map;
}
/**
* Brings this criteria back to its initial state, so that it
* can be reused as if it was new. Except if the criteria has grown in
* capacity, it is left at the current capacity.
*
* @return $this
*/
public function clear()
{
$this->map = [];
$this->namedCriterions = [];
$this->ignoreCase = false;
$this->singleRecord = false;
$this->selectModifiers = [];
$this->lock = null;
$this->selectColumns = [];
$this->orderByColumns = [];
$this->groupByColumns = [];
$this->having = null;
$this->asColumns = [];
$this->joins = [];
$this->selectQueries = [];
$this->dbName = $this->originalDbName;
$this->offset = 0;
$this->limit = -1;
$this->aliases = [];
$this->useTransaction = false;
return $this;
}
/**
* Add an AS clause to the select columns. Usage:
*
* <code>
* Criteria myCrit = new Criteria();
* myCrit->addAsColumn('alias', 'ALIAS('.MyTableMap::ID.')');
* </code>
*
* If the name already exists, it is replaced by the new clause.
*
* @param string $name Wanted Name of the column (alias).
* @param string $clause SQL clause to select from the table
*
* @return $this A modified Criteria object.
*/
public function addAsColumn(string $name, string $clause)
{
$this->asColumns[$name] = $clause;
return $this;
}
/**
* Get the column aliases.
*
* @return array An assoc array which map the column alias names
* to the alias clauses.
*/
public function getAsColumns(): array
{
return $this->asColumns;
}
/**
* Returns the column name associated with an alias (AS-column).
*
* @param string $as Alias
*
* @return string|null
*/
public function getColumnForAs(string $as): ?string
{
if (isset($this->asColumns[$as])) {
return $this->asColumns[$as];
}
return null;
}
/**
* Allows one to specify an alias for a table that can
* be used in various parts of the SQL.
*
* @param string $alias
* @param string $table
*
* @return $this A modified Criteria object.
*/
public function addAlias(string $alias, string $table)
{
$this->aliases[$alias] = $table;
return $this;
}
/**
* Remove an alias for a table (useful when merging Criterias).
*
* @param string $alias
*
* @return $this A modified Criteria object.
*/
public function removeAlias(string $alias)
{
unset($this->aliases[$alias]);
return $this;
}
/**
* Returns the aliases for this Criteria
*
* @return array
*/
public function getAliases(): array
{
return $this->aliases;
}
/**
* Returns the table name associated with an alias.
*
* @param string $alias
*
* @return string|null
*/
public function getTableForAlias(string $alias): ?string
{
if (isset($this->aliases[$alias])) {
return $this->aliases[$alias];
}
return null;
}
/**
* Returns the table name and alias based on a table alias or name.
* Use this method to get the details of a table name that comes in a clause,
* which can be either a table name or an alias name.
*
* Array($tableName, $tableAlias)
*
* @param string $tableAliasOrName
*
* @return array
*/
public function getTableNameAndAlias(string $tableAliasOrName): array
{
if (isset($this->aliases[$tableAliasOrName])) {
return [$this->aliases[$tableAliasOrName], $tableAliasOrName];
}
return [$tableAliasOrName, null];
}
/**
* Get the keys of the criteria map, i.e. the list of columns bearing a condition
* <code>
* print_r($c->keys());
* => array('book.price', 'book.title', 'author.first_name')
* </code>
*
* @return array
*/
public function keys(): array
{
return array_keys($this->map);
}
/**
* Does this Criteria object contain the specified key?
*
* @param string $column [table.]column
*
* @return bool True if this Criteria object contain the specified key.
*/
public function containsKey(string $column): bool
{
// must use array_key_exists() because the key could
// exist but have a NULL value (that'd be valid).
return isset($this->map[$column]);
}
/**
* Does this Criteria object contain the specified key and does it have a value set for the key
*
* @param string $column [table.]column
*
* @return bool True if this Criteria object contain the specified key and a value for that key
*/
public function keyContainsValue(string $column): bool
{
// must use array_key_exists() because the key could
// exist but have a NULL value (that'd be valid).
return isset($this->map[$column]) && $this->map[$column]->getValue() !== null;
}
/**
* Whether this Criteria has any where columns.
*
* This counts conditions added with the add() method.
*
* @see add()
*
* @return bool
*/
public function hasWhereClause(): bool
{
return (bool)$this->map;
}
/**
* Will force the sql represented by this criteria to be executed within
* a transaction. This is here primarily to support the oid type in
* postgresql. Though it can be used to require any single sql statement
* to use a transaction.
*
* @param bool $v
*
* @return $this
*/
public function setUseTransaction(bool $v)
{
$this->useTransaction = $v;
return $this;
}
/**
* Whether the sql command specified by this criteria must be wrapped
* in a transaction.
*
* @return bool
*/
public function isUseTransaction(): bool
{
return $this->useTransaction;
}
/**
* Method to return criteria related to columns in a table.
*
* Make sure you call containsKey($column) prior to calling this method,
* since no check on the existence of the $column is made in this method.
*
* @param string $column Column name.
*
* @return \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion A Criterion object.
*/
public function getCriterion(string $column): AbstractCriterion
{
return $this->map[$column];
}
/**
* Method to return the latest Criterion in a table.
*
* @return \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|null A Criterion or null no Criterion is added.
*/
public function getLastCriterion(): ?AbstractCriterion
{
$count = count($this->map);
if ($count) {
$map = array_values($this->map);
return $map[$count - 1];
}
return null;
}
/**
* Method to return a Criterion that is not added automatically
* to this Criteria. This can be used to chain the
* Criterions to form a more complex where clause.
*
* @param string $column Full name of column (for example TABLE.COLUMN).
* @param mixed|null $value
* @param string|int|null $comparison Criteria comparison constant or PDO binding type
*
* @return \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion
*/
public function getNewCriterion(string $column, $value = null, $comparison = null): AbstractCriterion
{
return CriterionFactory::build($this, $column, $comparison, $value);
}
/**
* Method to return a String table name.
*
* @param string $name Name of the key.
*
* @return string|null The value of the object at key.
*/
public function getColumnName(string $name): ?string
{
if (isset($this->map[$name])) {
return $this->map[$name]->getColumn();
}
return null;
}
/**
* Shortcut method to get an array of columns indexed by table.
* <code>
* print_r($c->getTablesColumns());
* => array(
* 'book' => array('book.price', 'book.title'),
* 'author' => array('author.first_name')
* )
* </code>
*
* @return array array(table => array(table.column1, table.column2))
*/
public function getTablesColumns(): array
{
$tables = [];
foreach ($this->keys() as $key) {
$tableName = substr($key, 0, strrpos($key, '.') ?: null);
$tables[$tableName][] = $key;
}
return $tables;
}
/**
* Method to return a comparison String.
*
* @param string $key String name of the key.
*
* @return string|null A String with the value of the object at key.
*/
public function getComparison(string $key): ?string
{
if (isset($this->map[$key])) {
return $this->map[$key]->getComparison();
}
return null;
}
/**
* Get the Database(Map) name.
*
* @return string A String with the Database(Map) name.
*/
public function getDbName(): string
{
return $this->dbName;
}
/**
* Set the DatabaseMap name. If <code>null</code> is supplied, uses value
* provided by <code>Configuration::getDefaultDatasource()</code>.
*
* @param string|null $dbName The Database (Map) name.
*
* @return $this
*/
public function setDbName(?string $dbName = null)
{
$this->dbName = ($dbName ?? Propel::getServiceContainer()->getDefaultDatasource());
return $this;
}
/**
* Get the primary table for this Criteria.
*
* This is useful for cases where a Criteria may not contain
* any SELECT columns or WHERE columns. This must be explicitly
* set, of course, in order to be useful.
*
* @return string|null
*/
public function getPrimaryTableName(): ?string
{
return $this->primaryTableName;
}
/**
* Sets the primary table for this Criteria.
*
* This is useful for cases where a Criteria may not contain
* any SELECT columns or WHERE columns. This must be explicitly
* set, of course, in order to be useful.
*
* @param string $tableName
*
* @return $this
*/
public function setPrimaryTableName(string $tableName)
{
$this->primaryTableName = $tableName;
return $this;
}
/**
* Method to return a String table name.
*
* @param string $name The name of the key.
*
* @return string|null The value of table for criterion at key.
*/
public function getTableName(string $name): ?string
{
if (isset($this->map[$name])) {
return $this->map[$name]->getTable();
}
return null;
}
/**
* Method to return the value that was added to Criteria.
*
* @param string $name A String with the name of the key.
*
* @return mixed The value of object at key.
*/
public function getValue(string $name)
{
if (isset($this->map[$name])) {
return $this->map[$name]->getValue();
}
return null;
}
/**
* An alias to getValue() -- exposing a Hashtable-like interface.
*
* @param string $key An Object.
*
* @return mixed The value within the Criterion (not the Criterion object).
*/
public function get(string $key)
{
return $this->getValue($key);
}
/**
* Overrides Hashtable put, so that this object is returned
* instead of the value previously in the Criteria object.
* The reason is so that it more closely matches the behavior
* of the add() methods. If you want to get the previous value
* then you should first Criteria.get() it yourself. Note, if
* you attempt to pass in an Object that is not a String, it will
* throw a NPE. The reason for this is that none of the add()
* methods support adding anything other than a String as a key.
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function put(string $key, $value)
{
$this->add($key, $value);
return $this;
}
/**
* Copies all of the mappings from the specified Map to this Criteria
* These mappings will replace any mappings that this Criteria had for any
* of the keys currently in the specified Map.
*
* if the map was another Criteria, its attributes are copied to this
* Criteria, overwriting previous settings.
*
* @param mixed $t Mappings to be stored in this map.
*
* @return $this
*/
public function putAll($t)
{
if (is_array($t)) {
foreach ($t as $key => $value) {
if ($value instanceof AbstractCriterion) {
$this->map[$key] = $value;
} else {
$this->put($key, $value);
}
}
} elseif ($t instanceof Criteria) {
$this->joins = $t->joins;
}
return $this;
}
/**
* This method adds a new criterion to the list of criterias.
* If a criterion for the requested column already exists, it is
* replaced. If is used as follow:
*
* <code>
* $crit = new Criteria();
* $crit->add($column, $value, Criteria::GREATER_THAN);
* </code>
*
* Any comparison can be used.
*
* The name of the table must be used implicitly in the column name,
* so the Column name must be something like 'TABLE.id'.
*
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1 The column to run the comparison on, or a Criterion object.
* @param mixed $value
* @param string|int|null $comparison A String.
*
* @return $this A modified Criteria object.
*/
public function add($p1, $value = null, $comparison = null)
{
if ($p1 instanceof AbstractCriterion) {
$this->map[$p1->getTable() . '.' . $p1->getColumn()] = $p1;
} else {
$this->map[$p1] = $this->getCriterionForCondition($p1, $value, $comparison);
}
return $this;
}
/**
* @return string
*/
public function __toString(): string
{
return implode(', ', $this->map) . "\n";
}
/**
* This method creates a new criterion but keeps it for later use with combine()
* Until combine() is called, the condition is not added to the query
*
* <code>
* $crit = new Criteria();
* $crit->addCond('cond1', $column1, $value1, Criteria::GREATER_THAN);
* $crit->addCond('cond2', $column2, $value2, Criteria::EQUAL);
* $crit->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR);
* </code>
*
* Any comparison can be used.
*
* The name of the table must be used implicitly in the column name,
* so the Column name must be something like 'TABLE.id'.
*
* @param string $name name to combine the criterion later
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1 The column to run the comparison on, or AbstractCriterion object.
* @param mixed|null $value
* @param string|null $comparison A String.
*
* @return $this A modified Criteria object.
*/
public function addCond(string $name, $p1, $value = null, ?string $comparison = null)
{
$this->namedCriterions[$name] = $this->getCriterionForCondition($p1, $value, $comparison);
return $this;
}
/**
* Combine several named criterions with a logical operator
*
* @param array $criterions array of the name of the criterions to combine
* @param string $operator logical operator, either Criteria::LOGICAL_AND, or Criteria::LOGICAL_OR
* @param string|null $name optional name to combine the criterion later
*
* @throws \Propel\Runtime\Exception\LogicException
*
* @return $this
*/
public function combine(array $criterions = [], string $operator = self::LOGICAL_AND, ?string $name = null)
{
$operatorMethod = (strtoupper($operator) === self::LOGICAL_AND) ? 'addAnd' : 'addOr';
$namedCriterions = [];
foreach ($criterions as $key) {
if (array_key_exists($key, $this->namedCriterions)) {
$namedCriterions[] = $this->namedCriterions[$key];
unset($this->namedCriterions[$key]);
} else {
throw new LogicException(sprintf('Cannot combine unknown condition %s', $key));
}
}
$firstCriterion = clone array_shift($namedCriterions);
foreach ($namedCriterions as $criterion) {
$firstCriterion->$operatorMethod($criterion);
}
if ($name === null) {
$this->addAnd($firstCriterion, null, null);
} else {
$this->addCond($name, $firstCriterion, null, null);
}
return $this;
}
/**
* This is the way that you should add a join of two tables.
* Example usage:
* <code>
* $c->addJoin(ProjectTableMap::ID, FooTableMap::PROJECT_ID, Criteria::LEFT_JOIN);
* // LEFT JOIN FOO ON (PROJECT.ID = FOO.PROJECT_ID)
* </code>
*
* @param array|string $left A String with the left side of the join.
* @param array|string $right A String with the right side of the join.
* @param string|null $joinType A String with the join operator
* among Criteria::INNER_JOIN, Criteria::LEFT_JOIN,
* and Criteria::RIGHT_JOIN
*
* @return $this A modified Criteria object.
*/
public function addJoin($left, $right, ?string $joinType = null)
{
if (is_array($left) && is_array($right)) {
$conditions = [];
foreach ($left as $key => $value) {
$condition = [$value, $right[$key]];
$conditions[] = $condition;
}
$this->addMultipleJoin($conditions, $joinType);
return $this;
}
$join = new Join();
$join->setIdentifierQuoting($this->isIdentifierQuotingEnabled());
// is the left table an alias ?
/** @phpstan-var string $left */
$dotpos = strrpos($left, '.') ?: null;
$leftTableAlias = substr($left, 0, $dotpos);
$leftColumnName = substr($left, $dotpos + 1);
[$leftTableName, $leftTableAlias] = $this->getTableNameAndAlias($leftTableAlias);
// is the right table an alias ?
/** @phpstan-var string $right */
$dotpos = strrpos($right, '.') ?: null;
$rightTableAlias = substr($right, 0, $dotpos);
$rightColumnName = substr($right, $dotpos + 1);
[$rightTableName, $rightTableAlias] = $this->getTableNameAndAlias($rightTableAlias);
$join->addExplicitCondition(
$leftTableName,
$leftColumnName,
$leftTableAlias,
$rightTableName,
$rightColumnName,
$rightTableAlias,
Join::EQUAL,
);
$join->setJoinType($joinType);
$this->addJoinObject($join);
return $this;
}
/**
* Add a join with multiple conditions
*
* @deprecated Use {@link \Propel\Runtime\ActiveQuery\Join::setJoinCondition()} instead
*
* @see http://propel.phpdb.org/trac/ticket/167, http://propel.phpdb.org/trac/ticket/606
*
* Example usage:
* $c->addMultipleJoin(array(
* array(LeftTableMap::LEFT_COLUMN, RightTableMap::RIGHT_COLUMN), // if no third argument, defaults to Criteria::EQUAL
* array(FoldersTableMap::alias( 'fo', FoldersTableMap::LFT ), FoldersTableMap::alias( 'parent', FoldersTableMap::RGT ), Criteria::LESS_EQUAL )
* ),
* Criteria::LEFT_JOIN
* );
* @see addJoin()
*
* @param array $conditions An array of conditions, each condition being an array (left, right, operator)
* @param string|null $joinType A String with the join operator. Defaults to an implicit join.
*
* @return $this A modified Criteria object.
*/
public function addMultipleJoin(array $conditions, ?string $joinType = null)
{
$join = new Join();
$join->setIdentifierQuoting($this->isIdentifierQuotingEnabled());
$joinCondition = null;
foreach ($conditions as $condition) {
$left = $condition[0];
$right = $condition[1];
$pos = strrpos($left, '.');
if ($pos) {
$leftTableAlias = substr($left, 0, $pos);
$leftColumnName = substr($left, $pos + 1);
[$leftTableName, $leftTableAlias] = $this->getTableNameAndAlias($leftTableAlias);
} else {
[$leftTableName, $leftTableAlias] = [null, null];
$leftColumnName = $left;
}
$pos = strrpos($right, '.');
if ($pos) {
$rightTableAlias = substr($right, 0, $pos);
$rightColumnName = substr($right, $pos + 1);
[$rightTableName, $rightTableAlias] = $this->getTableNameAndAlias($rightTableAlias);
} else {
[$rightTableName, $rightTableAlias] = [null, null];
$rightColumnName = $right;
}
if (!$join->getRightTableName()) {
$join->setRightTableName($rightTableName);
}
if (!$join->getRightTableAlias() && $rightTableAlias) {
$join->setRightTableAlias($rightTableAlias);
}
$conditionClause = $leftTableAlias ? $leftTableAlias . '.' : ($leftTableName ? $leftTableName . '.' : '');
$conditionClause .= $leftColumnName;
$conditionClause .= $condition[2] ?? Join::EQUAL;
$conditionClause .= $rightTableAlias ? $rightTableAlias . '.' : ($rightTableName ? $rightTableName . '.' : '');
$conditionClause .= $rightColumnName;
$fullColumnName = $leftTableName . '.' . $leftColumnName;
$criterion = CriterionFactory::build($this, $fullColumnName, self::CUSTOM, $conditionClause);
if ($joinCondition === null) {
$joinCondition = $criterion;
} else {
$joinCondition = $joinCondition->addAnd($criterion);
}
}
$join->setJoinType($joinType);
$join->setJoinCondition($joinCondition);
$this->addJoinObject($join);
return $this;
}
/**
* Add a join object to the Criteria
*
* @param \Propel\Runtime\ActiveQuery\Join $join A join object
*
* @return $this A modified Criteria object
*/
public function addJoinObject(Join $join)
{
if (!in_array($join, $this->joins)) { // compare equality, NOT identity
$this->joins[] = $join;
}
return $this;
}
/**
* Get the array of Joins.
*
* @return array<\Propel\Runtime\ActiveQuery\Join>
*/
public function getJoins(): array
{
return $this->joins;
}
/**
* This method returns an already defined join clause from the query
*
* @param string $name The name of the join clause
*
* @return \Propel\Runtime\ActiveQuery\Join A join object
*/
public function getJoin(string $name): Join
{
return $this->joins[$name];
}
/**
* @param string $name The name of the join clause
*
* @return bool
*/
public function hasJoin(string $name): bool
{
return isset($this->joins[$name]);
}
/**
* Adds a Criteria as subQuery in the From Clause.
*
* @param self $subQueryCriteria Criteria to build the subquery from
* @param string|null $alias alias for the subQuery
*
* @return $this this modified Criteria object (Fluid API)
*/
public function addSelectQuery(self $subQueryCriteria, ?string $alias = null)
{
if ($alias === null) {
$alias = 'alias_' . ($subQueryCriteria->forgeSelectQueryAlias() + count($this->selectQueries));
}
$this->selectQueries[$alias] = $subQueryCriteria;
return $this;
}
/**
* Checks whether this Criteria has a subquery.
*
* @return bool
*/
public function hasSelectQueries(): bool
{
return (bool)$this->selectQueries;
}
/**
* Get the associative array of Criteria for the subQueries per alias.
*
* @return array<\Propel\Runtime\ActiveQuery\Criteria>
*/
public function getSelectQueries(): array
{
return $this->selectQueries;
}
/**
* Get the Criteria for a specific subQuery.
*
* @param string $alias alias for the subQuery
*
* @return self
*/
public function getSelectQuery(string $alias): self
{
return $this->selectQueries[$alias];
}
/**
* checks if the Criteria for a specific subQuery is set.
*
* @param string $alias alias for the subQuery
*
* @return bool
*/
public function hasSelectQuery(string $alias): bool
{
return isset($this->selectQueries[$alias]);
}
/**
* @return int
*/
public function forgeSelectQueryAlias(): int
{
$aliasNumber = 0;
foreach ($this->getSelectQueries() as $c1) {
$aliasNumber += $c1->forgeSelectQueryAlias();
}
return ++$aliasNumber;
}
/**
* Adds 'ALL' modifier to the SQL statement.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setAll()
{
$this->removeSelectModifier(self::DISTINCT);
$this->addSelectModifier(self::ALL);
return $this;
}
/**
* Adds 'DISTINCT' modifier to the SQL statement.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setDistinct()
{
$this->removeSelectModifier(self::ALL);
$this->addSelectModifier(self::DISTINCT);
return $this;
}
/**
* Adds a modifier to the SQL statement.
* e.g. self::ALL, self::DISTINCT, 'SQL_CALC_FOUND_ROWS', 'HIGH_PRIORITY', etc.
*
* @param string $modifier The modifier to add
*
* @return $this Modified Criteria object (for fluent API)
*/
public function addSelectModifier(string $modifier)
{
// only allow the keyword once
if (!$this->hasSelectModifier($modifier)) {
$this->selectModifiers[] = $modifier;
}
return $this;
}
/**
* Removes a modifier to the SQL statement.
* Checks for existence before removal
*
* @param string $modifier The modifier to add
*
* @return $this Modified Criteria object (for fluent API)
*/
public function removeSelectModifier(string $modifier)
{
$this->selectModifiers = array_values(array_diff($this->selectModifiers, [$modifier]));
return $this;
}
/**
* Checks the existence of a SQL select modifier
*
* @param string $modifier The modifier to add
*
* @return bool
*/
public function hasSelectModifier(string $modifier): bool
{
return in_array($modifier, $this->selectModifiers, true);
}
/**
* @return \Propel\Runtime\ActiveQuery\Lock|null Get read lock value.
*/
public function getLock(): ?Lock
{
return $this->lock;
}
/**
* Apply a shared read lock to be used to retrieve rows.
*
* @param array<string> $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
public function lockForShare(array $tableNames = [], bool $noWait = false)
{
$this->withLock(Lock::SHARED, $tableNames, $noWait);
return $this;
}
/**
* Apply an exclusive read lock to be used to retrieve rows.
*
* @param array<string> $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
public function lockForUpdate(array $tableNames = [], bool $noWait = false)
{
$this->withLock(Lock::EXCLUSIVE, $tableNames, $noWait);
return $this;
}
/**
* Apply a read lock to be used to retrieve rows.
*
* @see Lock::SHARED
* @see Lock::EXCLUSIVE
*
* @param string $lockType
* @param array<string> $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
protected function withLock(string $lockType, array $tableNames = [], bool $noWait = false)
{
$this->lock = new Lock($lockType, $tableNames, $noWait);
return $this;
}
/**
* Retrieve rows without any read locking.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function withoutLock()
{
$this->lock = null;
return $this;
}
/**
* Sets ignore case.
*
* @param bool $b True if case should be ignored.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setIgnoreCase(bool $b)
{
$this->ignoreCase = $b;
return $this;
}
/**
* Is ignore case on or off?
*
* @return bool True if case is ignored.
*/
public function isIgnoreCase(): bool
{
return $this->ignoreCase;
}
/**
* Set single record? Set this to <code>true</code> if you expect the query
* to result in only a single result record (the default behaviour is to
* throw a PropelException if multiple records are returned when the query
* is executed). This should be used in situations where returning multiple
* rows would indicate an error of some sort. If your query might return
* multiple records but you are only interested in the first one then you
* should be using setLimit(1).
*
* @param bool $b Set to TRUE if you expect the query to select just one record.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setSingleRecord(bool $b)
{
$this->singleRecord = $b;
return $this;
}
/**
* Is single record?
*
* @return bool True if a single record is being returned.
*/
public function isSingleRecord(): bool
{
return $this->singleRecord;
}
/**
* Set limit.
*
* @param int $limit An int with the value for limit.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setLimit(int $limit)
{
$this->limit = $limit;
return $this;
}
/**
* Get limit.
*
* @return int An int with the value for limit.
*/
public function getLimit(): int
{
return $this->limit;
}
/**
* Set offset.
*
* @param int $offset An int with the value for offset.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setOffset(int $offset)
{
$this->offset = $offset;
return $this;
}
/**
* Get offset.
*
* @return int An int with the value for offset.
*/
public function getOffset(): int
{
return $this->offset;
}
/**
* Add select column.
*
* @param string $name Name of the select column.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function addSelectColumn(string $name)
{
$this->selectColumns[] = $name;
return $this;
}
/**
* Remove select column.
*
* @param string $name Name of the select column.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function removeSelectColumn(string $name)
{
while (($key = array_search($name, $this->selectColumns, true)) !== false) {
unset($this->selectColumns[$key]);
}
return $this;
}
/**
* Set the query comment, that appears after the first verb in the SQL query
*
* @param string|null $comment The comment to add to the query, without comment sign
*
* @return $this Modified Criteria object (for fluent API)
*/
public function setComment(?string $comment)
{
$this->queryComment = $comment;
return $this;
}
/**
* Get the query comment, that appears after the first verb in the SQL query
*
* @return string|null The comment to add to the query, without comment sign
*/
public function getComment(): ?string
{
return $this->queryComment;
}
/**
* Whether this Criteria has any select columns.
*
* This will include columns added with addAsColumn() method.
*
* @see addAsColumn()
* @see addSelectColumn()
*
* @return bool
*/
public function hasSelectClause(): bool
{
return (bool)$this->selectColumns || (bool)$this->asColumns;
}
/**
* Get select columns.
*
* @return array<string> An array with the name of the select columns.
*/
public function getSelectColumns(): array
{
return $this->selectColumns;
}
/**
* Clears current select columns.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function clearSelectColumns()
{
$this->selectColumns = $this->asColumns = [];
return $this;
}
/**
* Get select modifiers.
*
* @return array An array with the select modifiers.
*/
public function getSelectModifiers(): array
{
return $this->selectModifiers;
}
/**
* Add group by column name.
*
* @param string $groupBy The name of the column to group by.
*
* @return $this A modified Criteria object.
*/
public function addGroupByColumn(string $groupBy)
{
$this->groupByColumns[] = $groupBy;
return $this;
}
/**
* Add order by column name, explicitly specifying ascending.
*
* @param string $name The name of the column to order by.
*
* @return $this A modified Criteria object.
*/
public function addAscendingOrderByColumn(string $name)
{
$this->orderByColumns[] = $name . ' ' . self::ASC;
return $this;
}
/**
* Add order by column name, explicitly specifying descending.
*
* @param string $name The name of the column to order by.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function addDescendingOrderByColumn(string $name)
{
$this->orderByColumns[] = $name . ' ' . self::DESC;
return $this;
}
/**
* Get order by columns.
*
* @return array<string> An array with the name of the order columns.
*/
public function getOrderByColumns(): array
{
return $this->orderByColumns;
}
/**
* Clear the order-by columns.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function clearOrderByColumns()
{
$this->orderByColumns = [];
return $this;
}
/**
* Clear the group-by columns.
*
* @return $this
*/
public function clearGroupByColumns()
{
$this->groupByColumns = [];
return $this;
}
/**
* Get group by columns.
*
* @return array<string>
*/
public function getGroupByColumns(): array
{
return $this->groupByColumns;
}
/**
* Get Having Criterion.
*
* @return \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|null A Criterion object that is the having clause.
*/
public function getHaving(): ?AbstractCriterion
{
return $this->having;
}
/**
* Remove an object from the criteria.
*
* @param string $key A string with the key to be removed.
*
* @return mixed|null The removed value.
*/
public function remove(string $key)
{
if (!isset($this->map[$key])) {
return null;
}
/** @var \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|null $removed */
$removed = $this->map[$key];
unset($this->map[$key]);
if ($removed instanceof AbstractCriterion) {
return $removed->getValue();
}
return $removed;
}
/**
* Build a string representation of the Criteria.
*
* @return string A String with the representation of the Criteria.
*/
public function toString(): string
{
$sb = 'Criteria:';
try {
$params = [];
$sb .= "\nSQL (may not be complete): " . $this->createSelectSql($params);
$sb .= "\nParams: ";
$paramstr = [];
foreach ($params as $param) {
$paramstr[] = (isset($param['table']) ? $param['table'] . '.' : '')
. ($param['column'] ?? '')
. (isset($param['value']) ? ' => ' . var_export($param['value'], true) : '');
}
$sb .= implode(', ', $paramstr);
} catch (Exception $exc) {
$sb .= '(Error: ' . $exc->getMessage() . ')';
}
return $sb;
}
/**
* Returns the size (count) of this criteria.
*
* @return int
*/
public function size(): int
{
return count($this->map);
}
/**
* This method checks another Criteria to see if they contain
* the same attributes and hashtable entries.
*
* @param self $crit
*
* @return bool
*/
public function equals(self $crit): bool
{
if ($this === $crit) {
return true;
}
if ($this->size() === $crit->size()) {
// Important: nested criterion objects are checked
$criteria = $crit; // alias
if (
$this->offset === $criteria->getOffset()
&& $this->limit === $criteria->getLimit()
&& $this->ignoreCase === $criteria->isIgnoreCase()
&& $this->singleRecord === $criteria->isSingleRecord()
&& $this->dbName === $criteria->getDbName()
&& $this->selectModifiers === $criteria->getSelectModifiers()
&& $this->selectColumns === $criteria->getSelectColumns()
&& $this->asColumns === $criteria->getAsColumns()
&& $this->orderByColumns === $criteria->getOrderByColumns()
&& $this->groupByColumns === $criteria->getGroupByColumns()
&& $this->aliases === $criteria->getAliases()
) { // what about having ??
foreach ($criteria->keys() as $key) {
if ($this->containsKey($key)) {
$a = $this->getCriterion($key);
$b = $criteria->getCriterion($key);
if (!$a->equals($b)) {
return false;
}
} else {
return false;
}
}
$joins = $criteria->getJoins();
if (count($joins) !== count($this->joins)) {
return false;
}
foreach ($joins as $key => $join) {
if (empty($this->joins[$key]) || !$join->equals($this->joins[$key])) {
return false;
}
}
$aLock = $this->lock;
$bLock = $criteria->getLock();
if ($aLock instanceof Lock && !$aLock->equals($bLock)) {
return false;
}
if ($bLock instanceof Lock && !$bLock->equals($aLock)) {
return false;
}
return true;
} else {
return false;
}
}
return false;
}
/**
* Add the content of a Criteria to the current Criteria
* In case of conflict, the current Criteria keeps its properties
*
* @param self $criteria The criteria to read properties from
* @param string|null $operator The logical operator used to combine conditions
* Defaults to Criteria::LOGICAL_AND, also accepts Criteria::LOGICAL_OR
* This parameter is deprecated, use _or() instead
*
* @throws \Propel\Runtime\Exception\LogicException
*
* @return $this The current criteria object
*/
public function mergeWith(self $criteria, ?string $operator = null)
{
// merge limit
$limit = $criteria->getLimit();
if ($limit && $this->getLimit() === -1) {
$this->limit = $limit;
}
// merge offset
$offset = $criteria->getOffset();
if ($offset && $this->getOffset() === 0) {
$this->offset = $offset;
}
// merge select modifiers
$selectModifiers = $criteria->getSelectModifiers();
if ($selectModifiers && !$this->selectModifiers) {
$this->selectModifiers = $selectModifiers;
}
// merge lock
$lock = $criteria->getLock();
if ($lock && !$this->lock) {
$this->lock = $lock;
}
// merge select columns
$this->selectColumns = array_merge($this->getSelectColumns(), $criteria->getSelectColumns());
// merge as columns
$commonAsColumns = array_intersect_key($this->getAsColumns(), $criteria->getAsColumns());
if ($commonAsColumns) {
throw new LogicException('The given criteria contains an AsColumn with an alias already existing in the current object');
}
$this->asColumns = array_merge($this->getAsColumns(), $criteria->getAsColumns());
// merge orderByColumns
$orderByColumns = array_merge($this->getOrderByColumns(), $criteria->getOrderByColumns());
$this->orderByColumns = array_unique($orderByColumns);
// merge groupByColumns
$groupByColumns = array_merge($this->getGroupByColumns(), $criteria->getGroupByColumns());
$this->groupByColumns = array_unique($groupByColumns);
// merge where conditions
if ($operator === self::LOGICAL_OR) {
$this->_or();
}
$isFirstCondition = true;
foreach ($criteria->getMap() as $key => $criterion) {
if ($isFirstCondition && $this->defaultCombineOperator === self::LOGICAL_OR) {
$this->addOr($criterion, null, null, false);
$this->defaultCombineOperator = self::LOGICAL_AND;
} elseif ($this->containsKey($key)) {
$this->addAnd($criterion);
} else {
$this->add($criterion);
}
$isFirstCondition = false;
}
// merge having
$having = $criteria->getHaving();
if ($having) {
if ($this->getHaving()) {
$this->addHaving($this->getHaving()->addAnd($having));
} else {
$this->addHaving($having);
}
}
// merge alias
$commonAliases = array_intersect_key($this->getAliases(), $criteria->getAliases());
if ($commonAliases) {
throw new LogicException('The given criteria contains an alias already existing in the current object');
}
$this->aliases = array_merge($this->getAliases(), $criteria->getAliases());
// merge join
$this->joins = array_merge($this->getJoins(), $criteria->getJoins());
return $this;
}
/**
* This method adds a prepared Criterion object to the Criteria as a having clause.
* You can get a new, empty Criterion object with the
* getNewCriterion() method.
*
* <p>
* <code>
* $crit = new Criteria();
* $c = $crit->getNewCriterion(BaseTableMap::ID, 5, Criteria::LESS_THAN);
* $crit->addHaving($c);
* </code>
*
* @param mixed $p1 A Criterion, or a SQL clause with a question mark placeholder, or a column name
* @param mixed $value The value to bind in the condition
* @param mixed $comparison A PDO::PARAM_ class constant
*
* @return $this A modified Criteria object.
*/
public function addHaving($p1, $value = null, $comparison = null)
{
$this->having = $this->getCriterionForCondition($p1, $value, $comparison);
return $this;
}
/**
* Build a Criterion.
*
* This method has multiple signatures, and behaves differently according to it:
*
* - If the first argument is a Criterion, it just returns this Criterion.
* <code>$c->getCriterionForCondition($criterion); // returns $criterion</code>
*
* - If the last argument is a PDO::PARAM_* constant value, create a Criterion
* using Criteria::RAW and $comparison as a type.
* <code>$c->getCriterionForCondition('foo like ?', '%bar%', PDO::PARAM_STR);</code>
*
* - Otherwise, create a classic Criterion based on a column name and a comparison.
* <code>$c->getCriterionForCondition(BookTableMap::TITLE, 'War%', Criteria::LIKE);</code>
*
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1 A Criterion, or a SQL clause with a question mark placeholder, or a column name
* @param mixed|null $value The value to bind in the condition
* @param string|int|null $comparison A Criteria class constant, or a PDO::PARAM_ class constant
*
* @return \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion
*/
protected function getCriterionForCondition($p1, $value = null, $comparison = null): AbstractCriterion
{
if ($p1 instanceof AbstractCriterion) {
// it's already a Criterion, so ignore $value and $comparison
return $p1;
}
// $comparison is one of Criteria's constants, or a PDO binding type
// something like $c->add(BookTableMap::TITLE, 'War%', Criteria::LIKE);
return CriterionFactory::build($this, (string)$p1, $comparison, $value);
}
/**
* If a criterion for the requested column already exists, the condition is "AND"ed to the existing criterion (necessary for Propel 1.4 compatibility).
* If no criterion for the requested column already exists, the condition is "AND"ed to the latest criterion.
* If no criterion exist, the condition is added a new criterion
*
* Any comparison can be used.
*
* Supports a number of different signatures:
* - addAnd(column, value, comparison)
* - addAnd(column, value)
* - addAnd(Criterion)
*
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1
* @param mixed|null $value
* @param mixed|null $condition
* @param bool $preferColumnCondition
*
* @return $this A modified Criteria object.
*/
public function addAnd($p1, $value = null, $condition = null, bool $preferColumnCondition = true)
{
$criterion = $this->getCriterionForCondition($p1, $value, $condition);
$key = $criterion->getTable() . '.' . $criterion->getColumn();
if ($preferColumnCondition && $this->containsKey($key)) {
// FIXME: addAnd() operates preferably on existing conditions on the same column
// this may cause unexpected results, but it's there for BC with Propel 14
$this->getCriterion($key)->addAnd($criterion);
} else {
// simply add the condition to the list - this is the expected behavior
$this->add($criterion);
}
return $this;
}
/**
* If a prior criterion exists, the condition is "OR"ed to it.
* If no criterion exist, the condition is added a new criterion
*
* Any comparison can be used.
*
* Supports a number of different signatures:
* - addOr(column, value, comparison)
* - addOr(column, value)
* - addOr(Criterion)
*
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1
* @param mixed $value
* @param mixed $condition
* @param bool $preferColumnCondition
*
* @return $this A modified Criteria object.
*/
public function addOr($p1, $value = null, $condition = null, bool $preferColumnCondition = true)
{
$rightCriterion = $this->getCriterionForCondition($p1, $value, $condition);
$leftCriterion = $this->getLastCriterion();
if ($leftCriterion !== null) {
// combine the given criterion with the existing one with an 'OR'
$leftCriterion->addOr($rightCriterion);
} else {
// nothing to do OR / AND with, so make it first condition
$this->add($rightCriterion);
}
return $this;
}
/**
* Overrides Criteria::add() to use the default combine operator
*
* @see Criteria::add()
*
* @param \Propel\Runtime\ActiveQuery\Criterion\AbstractCriterion|string $p1 The column to run the comparison on (e.g. BookTableMap::ID), or Criterion object
* @param mixed $value
* @param string|null $operator A String, like Criteria::EQUAL.
* @param bool $preferColumnCondition If true, the condition is combined with an existing condition on the same column
* (necessary for Propel 1.4 compatibility).
* If false, the condition is combined with the last existing condition.
*
* @return $this A modified Criteria object.
*/
public function addUsingOperator($p1, $value = null, ?string $operator = null, bool $preferColumnCondition = true)
{
if ($this->defaultCombineOperator === self::LOGICAL_OR) {
$this->defaultCombineOperator = self::LOGICAL_AND;
$this->addOr($p1, $value, $operator, $preferColumnCondition);
return $this;
}
$this->addAnd($p1, $value, $operator, $preferColumnCondition);
return $this;
}
/**
* Method to create an SQL query based on values in a Criteria.
*
* This method creates only prepared statement SQL (using ? where values
* will go). The second parameter ($params) stores the values that need
* to be set before the statement is executed. The reason we do it this way
* is to let the PDO layer handle all escaping & value formatting.
*
* @param array $params Parameters that are to be replaced in prepared statement.
*
* @return string
*/
public function createSelectSql(array &$params): string
{
$preparedStatementDto = SelectQuerySqlBuilder::createSelectSql($this, $params);
$params = $preparedStatementDto->getParameters();
return $preparedStatementDto->getSqlStatement();
}
/**
* This method does only quote identifier, the method doReplaceNameInExpression of child ModelCriteria class does more.
*
* @param array $matches Matches found by preg_replace_callback
*
* @return string the column name replacement
*/
protected function doReplaceNameInExpression(array $matches): string
{
return $this->quoteIdentifier($matches[0]);
}
/**
* Quotes identifier based on $this->isIdentifierQuotingEnabled() and $tableMap->isIdentifierQuotingEnabled.
*
* @param string $string
* @param string $tableName
*
* @return string
*/
public function quoteIdentifier(string $string, string $tableName = ''): string
{
if ($this->isIdentifierQuotingEnabled()) {
$adapter = Propel::getServiceContainer()->getAdapter($this->getDbName());
return $adapter->quote($string);
}
//find table name and ask tableMap if quoting is enabled
$pos = strrpos($string, '.');
if (!$tableName && $pos !== false) {
$tableName = substr($string, 0, $pos);
}
$tableMapName = $this->getTableForAlias($tableName) ?: $tableName;
if ($tableMapName) {
$dbMap = Propel::getServiceContainer()->getDatabaseMap($this->getDbName());
if ($dbMap->hasTable($tableMapName)) {
$tableMap = $dbMap->getTable($tableMapName);
if ($tableMap->isIdentifierQuotingEnabled()) {
$adapter = Propel::getServiceContainer()->getAdapter($this->getDbName());
return $adapter->quote($string);
}
}
}
return $string;
}
/**
* Replaces complete column names (like Article.AuthorId) in an SQL clause
* by their exact Propel column fully qualified name (e.g. article.author_id)
* but ignores the column names inside quotes
* e.g. 'CONCAT(Book.AuthorID, "Book.AuthorID") = ?'
* => 'CONCAT(book.author_id, "Book.AuthorID") = ?'
*
* @param string $sql SQL clause to inspect (modified by the method)
*
* @return bool Whether the method managed to find and replace at least one column name
*/
public function replaceNames(string &$sql): bool
{
$this->replacedColumns = [];
$this->currentAlias = '';
$this->foundMatch = false;
$isAfterBackslash = false;
$isInString = false;
$stringQuotes = '';
$parsedString = '';
$stringToTransform = '';
$len = strlen($sql);
$pos = 0;
while ($pos < $len) {
$char = $sql[$pos];
// check flags for strings or escaper
switch ($char) {
case '\\':
$isAfterBackslash = true;
break;
case "'":
case '"':
if ($isInString && $stringQuotes == $char) {
if (!$isAfterBackslash) {
$isInString = false;
}
} elseif (!$isInString) {
$parsedString .= preg_replace_callback("/[\w\\\]+\.\w+/", [$this, 'doReplaceNameInExpression'], $stringToTransform);
$stringToTransform = '';
$stringQuotes = $char;
$isInString = true;
}
break;
}
if ($char !== '\\') {
$isAfterBackslash = false;
}
if ($isInString) {
$parsedString .= $char;
} else {
$stringToTransform .= $char;
}
$pos++;
}
if ($stringToTransform) {
$parsedString .= preg_replace_callback("/[\w\\\]+\.\w+/", [$this, 'doReplaceNameInExpression'], $stringToTransform);
}
$sql = $parsedString;
return $this->foundMatch;
}
/**
* Method to perform inserts based on values and keys in a
* Criteria.
* <p>
* If the primary key is auto incremented the data in Criteria
* will be inserted and the auto increment value will be returned.
* <p>
* If the primary key is included in Criteria then that value will
* be used to insert the row.
* <p>
* If no primary key is included in Criteria then we will try to
* figure out the primary key from the database map and insert the
* row with the next available id using util.db.IDBroker.
* <p>
* If no primary key is defined for the table the values will be
* inserted as specified in Criteria and null will be returned.
*
* @param \Propel\Runtime\Connection\ConnectionInterface|null $con A ConnectionInterface connection.
*
* @return mixed The primary key for the new row if the primary key is auto-generated. Otherwise will return null.
*/
public function doInsert(?ConnectionInterface $con = null)
{
return InsertQueryExecutor::execute($this, $con);
}
/**
* @param self|null $criteria
*
* @return \Propel\Runtime\Map\ColumnMap|null
*/
public function getPrimaryKey(?self $criteria = null): ?ColumnMap
{
if (!$criteria) {
$criteria = $this;
}
// Assume all the keys are for the same table.
$keys = $criteria->keys();
$key = $keys[0];
$table = $criteria->getTableName($key);
$pk = null;
if ($table) {
$dbMap = Propel::getServiceContainer()->getDatabaseMap($criteria->getDbName());
$pks = $dbMap->getTable($table)->getPrimaryKeys();
if ($pks) {
$pk = array_shift($pks);
}
}
return $pk;
}
/**
* Method used to update rows in the DB. Rows are selected based
* on selectCriteria and updated using values in updateValues.
* <p>
* Use this method for performing an update of the kind:
* <p>
* WHERE some_column = some value AND could_have_another_column =
* another value AND so on.
*
* @param \Propel\Runtime\ActiveQuery\Criteria $updateValues A Criteria object containing values used in set clause.
* @param \Propel\Runtime\Connection\ConnectionInterface $con The ConnectionInterface connection object to use.
*
* @return int The number of rows affected by last update statement.
* For most uses there is only one update statement executed, so this number will
* correspond to the number of rows affected by the call to this method.
* Note that the return value does require that this information is returned
* (supported) by the Propel db driver.
*/
public function doUpdate(Criteria $updateValues, ConnectionInterface $con): int
{
return UpdateQueryExecutor::execute($this, $updateValues, $con);
}
/**
* @param \Propel\Runtime\Connection\ConnectionInterface|null $con
*
* @return \Propel\Runtime\DataFetcher\DataFetcherInterface
*/
public function doCount(?ConnectionInterface $con = null): DataFetcherInterface
{
return CountQueryExecutor::execute($this, $con);
}
/**
* Checks whether the Criteria needs to use column aliasing
* This is implemented in a service class rather than in Criteria itself
* in order to avoid doing the tests when it's not necessary (e.g. for SELECTs)
*
* @return bool
*/
public function needsSelectAliases(): bool
{
$columnNames = [];
foreach ($this->getSelectColumns() as $fullyQualifiedColumnName) {
$pos = strrpos($fullyQualifiedColumnName, '.');
if ($pos) {
$columnName = substr($fullyQualifiedColumnName, $pos);
if (isset($columnNames[$columnName])) {
// more than one column with the same name, so aliasing is required
return true;
}
$columnNames[$columnName] = true;
}
}
return false;
}
/**
* Issue a DELETE query based on the current ModelCriteria
* This method is called by ModelCriteria::delete() inside a transaction
*
* @param \Propel\Runtime\Connection\ConnectionInterface|null $con a connection object
*
* @return int The number of deleted rows
*/
public function doDelete(?ConnectionInterface $con = null): int
{
return DeleteQueryExecutor::execute($this, $con);
}
/**
* Issue a DELETE query based on the current ModelCriteria deleting all rows in the table
* This method is called by ModelCriteria::deleteAll() inside a transaction
*
* @param \Propel\Runtime\Connection\ConnectionInterface|null $con a connection object
*
* @return int The number of deleted rows
*/
public function doDeleteAll(?ConnectionInterface $con = null): int
{
return DeleteAllQueryExecutor::execute($this, $con);
}
/**
* Builds, binds and executes a SELECT query based on the current object.
*
* @param \Propel\Runtime\Connection\ConnectionInterface|null $con A connection object
*
* @return \Propel\Runtime\DataFetcher\DataFetcherInterface A dataFetcher using the connection, ready to be fetched
*/
public function doSelect(?ConnectionInterface $con = null): DataFetcherInterface
{
return SelectQueryExecutor::execute($this, $con);
}
// Fluid operators
/**
* @return $this
*/
public function _or()
{
$this->defaultCombineOperator = self::LOGICAL_OR;
return $this;
}
/**
* @return $this
*/
public function _and()
{
$this->defaultCombineOperator = self::LOGICAL_AND;
return $this;
}
// Fluid Conditions
/**
* Returns the current object if the condition is true,
* or a PropelConditionalProxy instance otherwise.
* Allows for conditional statements in a fluid interface.
*
* @param mixed $cond Casts to bool for variable evaluation
*
* @return \Propel\Runtime\ActiveQuery\Criteria|\Propel\Runtime\Util\PropelConditionalProxy
*/
public function _if($cond)
{
$cond = (bool)$cond; // Intentionally not typing the param to allow for evaluation inside this function
$this->conditionalProxy = new PropelConditionalProxy($this, $cond, $this->conditionalProxy);
return $this->conditionalProxy->getCriteriaOrProxy();
}
/**
* Returns a PropelConditionalProxy instance.
* Allows for conditional statements in a fluid interface.
*
* @param mixed $cond Casts to bool for variable evaluation
*
* @throws \Propel\Runtime\Exception\LogicException
*
* @return \Propel\Runtime\ActiveQuery\Criteria|\Propel\Runtime\Util\PropelConditionalProxy
*/
public function _elseif($cond)
{
$cond = (bool)$cond; // Intentionally not typing the param to allow for evaluation inside this function
if (!$this->conditionalProxy) {
throw new LogicException(__METHOD__ . ' must be called after _if()');
}
return $this->conditionalProxy->_elseif($cond);
}
/**
* Returns a PropelConditionalProxy instance.
* Allows for conditional statements in a fluid interface.
*
* @throws \Propel\Runtime\Exception\LogicException
*
* @return \Propel\Runtime\ActiveQuery\Criteria|\Propel\Runtime\Util\PropelConditionalProxy
*/
public function _else()
{
if (!$this->conditionalProxy) {
throw new LogicException(__METHOD__ . ' must be called after _if()');
}
return $this->conditionalProxy->_else();
}
/**
* Returns the current object
* Allows for conditional statements in a fluid interface.
*
* @throws \Propel\Runtime\Exception\LogicException
*
* @return \Propel\Runtime\ActiveQuery\Criteria|\Propel\Runtime\Util\PropelConditionalProxy
*/
public function _endif()
{
if (!$this->conditionalProxy) {
throw new LogicException(__METHOD__ . ' must be called after _if()');
}
$this->conditionalProxy = $this->conditionalProxy->getParentProxy();
if ($this->conditionalProxy) {
return $this->conditionalProxy->getCriteriaOrProxy();
}
// reached last level
return $this;
}
/**
* Ensures deep cloning of attached objects
*
* @return void
*/
public function __clone()
{
foreach ($this->map as $key => $criterion) {
$this->map[$key] = clone $criterion;
}
foreach ($this->joins as $key => $join) {
$this->joins[$key] = clone $join;
}
if ($this->having !== null) {
$this->having = clone $this->having;
}
}
/**
* @return bool
*/
public function isIdentifierQuotingEnabled(): bool
{
return $this->identifierQuoting;
}
/**
* @param bool $identifierQuoting
*
* @return $this
*/
public function setIdentifierQuoting(bool $identifierQuoting)
{
$this->identifierQuoting = $identifierQuoting;
return $this;
}
}