src/Propel/Generator/Builder/Om/QueryBuilder.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\Generator\Builder\Om;
use Propel\Generator\Builder\Util\PropelTemplate;
use Propel\Generator\Model\Column;
use Propel\Generator\Model\CrossForeignKeys;
use Propel\Generator\Model\ForeignKey;
use Propel\Generator\Model\PropelTypes;
use Propel\Generator\Model\Table;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\Criterion\ExistsQueryCriterion;
/**
* Generates a base Query class for user object model (OM).
*
* This class produces the base query class (e.g. BaseBookQuery) which contains
* all the custom-built query methods.
*
* @author Francois Zaninotto
*/
class QueryBuilder extends AbstractOMBuilder
{
/**
* Returns the package for the [base] object classes.
*
* @return string
*/
public function getPackage(): string
{
return parent::getPackage() . '.Base';
}
/**
* Returns the namespace for the query object classes.
*
* @return string|null
*/
public function getNamespace(): ?string
{
$namespace = parent::getNamespace();
if ($namespace) {
return $namespace . '\\Base';
}
return 'Base';
}
/**
* Returns the name of the current class being built.
*
* @return string
*/
public function getUnprefixedClassName(): string
{
return $this->getStubQueryBuilder()->getUnprefixedClassName();
}
/**
* Returns parent class name that extends TableQuery Object if is set this class must extends ModelCriteria for be compatible
*
* @return string
*/
public function getParentClass(): string
{
$parentClass = $this->getBehaviorContent('parentClass');
if ($parentClass) {
return $parentClass;
}
$baseQueryClass = $this->getTable()->getBaseQueryClass();
if ($baseQueryClass) {
return $baseQueryClass;
}
return 'ModelCriteria';
}
/**
* Adds class phpdoc comment and opening of class.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addClassOpen(string &$script): void
{
$table = $this->getTable();
$vars = [
'tableName' => $table->getName(),
'tableDesc' => $table->getDescription(),
'queryClass' => $this->getQueryClassName(),
'modelClass' => $this->getObjectClassName(),
'parentClass' => $this->getParentClass(),
'entityNotFoundExceptionClass' => $this->getEntityNotFoundExceptionClass(),
'unqualifiedClassName' => $this->getUnqualifiedClassName(),
'addTimestamp' => $this->getBuildProperty('generator.objectModel.addTimeStamp'),
'propelVersion' => $this->getBuildProperty('general.version'),
'columns' => $table->getColumns(),
'relationNames' => $this->getRelationNames(),
'relatedTableQueryClassNames' => $this->getRelatedTableQueryClassNames(),
];
$templatePath = $this->getTemplatePath(__DIR__);
$template = new PropelTemplate();
$filePath = $templatePath . 'baseQueryClassHeader.php';
$template->setTemplateFile($filePath);
$script .= $template->render($vars);
}
/**
* Get names of all foreign key relations to and from this table.
*
* @return array<string>
*/
protected function getRelationNames(): array
{
$table = $this->getTable();
$fkRelationNames = array_map([$this, 'getFKPhpNameAffix'], $table->getForeignKeys());
$refFkRelationNames = array_filter(array_map([$this, 'getRefFKPhpNameAffix'], $table->getReferrers()));
return array_merge($fkRelationNames, $refFkRelationNames);
}
/**
* Get query class names of all tables connected to this table with a foreign key relation.
*
* @return array<string>
*/
protected function getRelatedTableQueryClassNames(): array
{
$table = $this->getTable();
$fkTables = array_map(fn ($fk) => $fk->getForeignTable(), $table->getForeignKeys());
$refFkTables = array_map(fn ($fk) => $fk->getTable(), $table->getReferrers());
$relationTables = array_merge($fkTables, $refFkTables);
return array_map(fn ($table) => $this->getNewStubQueryBuilder($table)->getQueryClassName(true), $relationTables);
}
/**
* Specifies the methods that are added as part of the stub object class.
*
* By default there are no methods for the empty stub classes; override this method
* if you want to change that behavior.
*
* @see ObjectBuilder::addClassBody()
*
* @param string $script
*
* @return void
*/
protected function addClassBody(string &$script): void
{
$table = $this->getTable();
// namespaces
$this->declareClasses(
'\Propel\Runtime\Propel',
'\Propel\Runtime\ActiveQuery\ModelCriteria',
'\Propel\Runtime\ActiveQuery\Criteria',
'\Propel\Runtime\ActiveQuery\Criterion\CriterionFactory',
'\Propel\Runtime\ActiveQuery\ModelJoin',
'\Exception',
'\Propel\Runtime\Exception\PropelException',
);
$this->declareClassFromBuilder($this->getStubQueryBuilder(), 'Child');
$this->declareClassFromBuilder($this->getTableMapBuilder());
$additionalModelClasses = $table->getAdditionalModelClassImports();
if ($additionalModelClasses) {
$this->declareClasses(...$additionalModelClasses);
}
// apply behaviors
$this->applyBehaviorModifier('queryAttributes', $script, ' ');
$this->addEntityNotFoundExceptionClass($script);
$this->addConstructor($script);
$this->addFactory($script);
$this->addFindPk($script);
$this->addFindPkSimple($script);
$this->addFindPkComplex($script);
$this->addFindPks($script);
$this->addFilterByPrimaryKey($script);
$this->addFilterByPrimaryKeys($script);
foreach ($this->getTable()->getColumns() as $col) {
$this->addFilterByCol($script, $col);
if ($col->isNamePlural()) {
if ($col->getType() === PropelTypes::PHP_ARRAY) {
$this->addFilterByArrayCol($script, $col);
} elseif ($col->isSetType()) {
$this->addFilterBySetCol($script, $col);
}
}
}
foreach ($this->getTable()->getForeignKeys() as $fk) {
$this->addFilterByFK($script, $fk);
$this->addJoinFk($script, $fk);
$this->addUseFKQuery($script, $fk);
}
foreach ($this->getTable()->getReferrers() as $refFK) {
$this->addFilterByRefFK($script, $refFK);
$this->addJoinRefFk($script, $refFK);
$this->addUseRefFKQuery($script, $refFK);
}
foreach ($this->getTable()->getCrossFks() as $crossFKs) {
$this->addFilterByCrossFK($script, $crossFKs);
}
$this->addPrune($script);
$this->addBasePreSelect($script);
$this->addBasePreDelete($script);
$this->addBasePostDelete($script);
$this->addBasePreUpdate($script);
$this->addBasePostUpdate($script);
// add the insert, update, delete, etc. methods
if (!$table->isAlias() && !$table->isReadOnly()) {
$this->addDeleteMethods($script);
}
// apply behaviors
$this->applyBehaviorModifier('staticConstants', $script, ' ');
$this->applyBehaviorModifier('staticAttributes', $script, ' ');
$this->applyBehaviorModifier('staticMethods', $script, ' ');
$this->applyBehaviorModifier('queryMethods', $script, ' ');
}
/**
* Adds the entityNotFoundExceptionClass property which is necessary for the `requireOne` method
* of the `ModelCriteria`
*
* @param string $script
*
* @return void
*/
protected function addEntityNotFoundExceptionClass(string &$script): void
{
$script .= " protected \$entityNotFoundExceptionClass = '" . addslashes($this->getEntityNotFoundExceptionClass()) . "';\n";
}
/**
* @return string|null
*/
private function getEntityNotFoundExceptionClass(): ?string
{
return $this->getBuildProperty('generator.objectModel.entityNotFoundExceptionClass');
}
/**
* Adds the doDeleteAll(), etc. methods.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addDeleteMethods(string &$script): void
{
$this->addDoDeleteAll($script);
$this->addDelete($script);
if ($this->isDeleteCascadeEmulationNeeded()) {
$this->addDoOnDeleteCascade($script);
}
if ($this->isDeleteSetNullEmulationNeeded()) {
$this->addDoOnDeleteSetNull($script);
}
}
/**
* Whether the platform in use requires ON DELETE CASCADE emulation and whether there are references to this table.
*
* @return bool
*/
protected function isDeleteCascadeEmulationNeeded(): bool
{
$table = $this->getTable();
if ((!$this->getPlatform()->supportsNativeDeleteTrigger() || $this->getBuildProperty('generator.objectModel.emulateForeignKeyConstraints')) && count($table->getReferrers()) > 0) {
foreach ($table->getReferrers() as $fk) {
if ($fk->getOnDelete() === ForeignKey::CASCADE) {
return true;
}
}
}
return false;
}
/**
* Whether the platform in use requires ON DELETE SETNULL emulation and whether there are references to this table.
*
* @return bool
*/
protected function isDeleteSetNullEmulationNeeded(): bool
{
$table = $this->getTable();
if ((!$this->getPlatform()->supportsNativeDeleteTrigger() || $this->getBuildProperty('generator.objectModel.emulateForeignKeyConstraints')) && count($table->getReferrers()) > 0) {
foreach ($table->getReferrers() as $fk) {
if ($fk->getOnDelete() === ForeignKey::SETNULL) {
return true;
}
}
}
return false;
}
/**
* Closes class.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addClassClose(string &$script): void
{
$script .= "
}
";
$this->applyBehaviorModifier('queryFilter', $script, '');
}
/**
* Adds the constructor for this object.
*
* @see addConstructor()
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addConstructor(string &$script): void
{
$this->addConstructorComment($script);
$this->addConstructorOpen($script);
$this->addConstructorBody($script);
$this->addConstructorClose($script);
}
/**
* Adds the comment for the constructor
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addConstructorComment(string &$script): void
{
$script .= "
/**
* Initializes internal state of " . $this->getClassName() . " object.
*
* @param string \$dbName The database name
* @param string \$modelName The phpName of a model, e.g. 'Book'
* @param string \$modelAlias The alias for the model in this query, e.g. 'b'
*/";
}
/**
* Adds the function declaration for the constructor
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addConstructorOpen(string &$script): void
{
$table = $this->getTable();
$script .= "
public function __construct(\$dbName = '" . $table->getDatabase()->getName() . "', \$modelName = '" . addslashes($this->getNewStubObjectBuilder($table)->getFullyQualifiedClassName()) . "', \$modelAlias = null)
{";
}
/**
* Adds the function body for the constructor
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addConstructorBody(string &$script): void
{
$script .= "
parent::__construct(\$dbName, \$modelName, \$modelAlias);";
}
/**
* Adds the function close for the constructor
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addConstructorClose(string &$script): void
{
$script .= "
}
";
}
/**
* Adds the factory for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFactory(string &$script): void
{
$this->addFactoryComment($script);
$this->addFactoryOpen($script);
$this->addFactoryBody($script);
$this->addFactoryClose($script);
}
/**
* Adds the comment for the factory
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFactoryComment(string &$script): void
{
$classname = $this->getClassNameFromBuilder($this->getNewStubQueryBuilder($this->getTable()));
$script .= "
/**
* Returns a new " . $classname . " object.
*
* @param string \$modelAlias The alias of a model in the query
* @param Criteria \$criteria Optional Criteria to build the query from
*
* @return " . $classname . "
*/";
}
/**
* Adds the function declaration for the factory
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFactoryOpen(string &$script): void
{
$script .= "
public static function create(?string \$modelAlias = null, ?Criteria \$criteria = null): Criteria
{";
}
/**
* Adds the function body for the factory
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFactoryBody(string &$script): void
{
$classname = $this->getClassNameFromBuilder($this->getNewStubQueryBuilder($this->getTable()));
$script .= "
if (\$criteria instanceof " . $classname . ") {
return \$criteria;
}
\$query = new " . $classname . "();
if (null !== \$modelAlias) {
\$query->setModelAlias(\$modelAlias);
}
if (\$criteria instanceof Criteria) {
\$query->mergeWith(\$criteria);
}
return \$query;";
}
/**
* Adds the function close for the factory
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFactoryClose(string &$script): void
{
$script .= "
}
";
}
/**
* @param string $script
*
* @return void
*/
protected function addFindPk(string &$script): void
{
$class = $this->getObjectClassName();
$tableMapClassName = $this->getTableMapClassName();
$table = $this->getTable();
$script .= "
/**
* Find object by primary key.
* Propel uses the instance pool to skip the database if the object exists.
* Go fast if the query is untouched.
*";
if ($table->hasCompositePrimaryKey()) {
$pks = $table->getPrimaryKey();
$examplePk = array_slice([12, 34, 56, 78, 91], 0, count($pks));
$colNames = [];
foreach ($pks as $col) {
$colNames[] = '$' . $col->getName();
}
$pkType = 'array[' . implode(', ', $colNames) . ']';
$script .= "
* <code>
* \$obj = \$c->findPk(array(" . implode(', ', $examplePk) . '), $con);';
} else {
$pkType = 'mixed';
$script .= "
* <code>
* \$obj = \$c->findPk(12, \$con);";
}
$script .= "
* </code>
*
* @param " . $pkType . " \$key Primary key to use for the query
* @param ConnectionInterface \$con an optional connection object
*
* @return $class|array|mixed the result, formatted by the current formatter
*/
public function findPk(\$key, ?ConnectionInterface \$con = null)
{";
if (!$table->hasPrimaryKey()) {
$this->declareClass('Propel\\Runtime\\Exception\\LogicException');
$script .= "
throw new LogicException('The {$this->getObjectName()} object has no primary key');
}
";
return;
}
$script .= "
if (\$key === null) {
return null;
}";
if ($table->hasCompositePrimaryKey()) {
$numberOfPks = count($table->getPrimaryKey());
$pkIndexes = range(0, $numberOfPks - 1);
$pks = preg_filter('/(\d+)/', '$key[${1}]', $pkIndexes); // put ids into "$key[]"
} else {
$pks = '$key';
}
$pkHash = $this->getTableMapBuilder()->getInstancePoolKeySnippet($pks);
$script .= "
if (\$con === null) {
\$con = Propel::getServiceContainer()->getReadConnection({$this->getTableMapClass()}::DATABASE_NAME);
}
\$this->basePreSelect(\$con);
if (
\$this->formatter || \$this->modelAlias || \$this->with || \$this->select
|| \$this->selectColumns || \$this->asColumns || \$this->selectModifiers
|| \$this->map || \$this->having || \$this->joins
) {
return \$this->findPkComplex(\$key, \$con);
}
if ((null !== (\$obj = {$tableMapClassName}::getInstanceFromPool({$pkHash})))) {
// the object is already in the instance pool
return \$obj;
}
return \$this->findPkSimple(\$key, \$con);
}
";
}
/**
* @param string $script
*
* @return void
*/
protected function addFindPkSimple(string &$script): void
{
$table = $this->getTable();
// this method is not needed if the table has no primary key
if (!$table->hasPrimaryKey()) {
return;
}
$usesConcreteInheritance = $table->usesConcreteInheritance();
if ($table->isAbstract() && !$usesConcreteInheritance) {
$tableName = $table->getPhpName();
$script .= "
protected function findPkSimple(\$key, ConnectionInterface \$con)
{
throw new PropelException('$tableName is declared abstract, you cannot query it.');
}
";
return;
}
$platform = $this->getPlatform();
$tableMapClassName = $this->getTableMapClassName();
$ARClassName = $this->getObjectClassName();
$this->declareClassFromBuilder($this->getStubObjectBuilder());
$this->declareClasses('\PDO');
$selectColumns = [];
foreach ($table->getColumns() as $column) {
if (!$column->isLazyLoad()) {
$selectColumns[] = $this->quoteIdentifier($column->getName());
}
}
$conditions = [];
foreach ($table->getPrimaryKey() as $index => $column) {
$conditions[] = sprintf('%s = :p%d', $this->quoteIdentifier($column->getName()), $index);
}
$query = sprintf(
'SELECT %s FROM %s WHERE %s',
implode(', ', $selectColumns),
$this->quoteIdentifier($table->getName()),
implode(' AND ', $conditions),
);
$pks = [];
if ($table->hasCompositePrimaryKey()) {
foreach ($table->getPrimaryKey() as $index => $column) {
$pks[] = "\$key[$index]";
}
} else {
$pks[] = '$key';
}
$pkHashFromRow = $this->getTableMapBuilder()->getInstancePoolKeySnippet($pks);
$script .= "
/**
* Find object by primary key using raw SQL to go fast.
* Bypass doSelect() and the object formatter by using generated code.
*
* @param mixed \$key Primary key to use for the query
* @param ConnectionInterface \$con A connection object
*
* @throws \\Propel\\Runtime\\Exception\\PropelException
*
* @return $ARClassName A model object, or null if the key is not found
*/
protected function findPkSimple(\$key, ConnectionInterface \$con)
{
\$sql = '$query';
try {
\$stmt = \$con->prepare(\$sql);";
if ($table->hasCompositePrimaryKey()) {
foreach ($table->getPrimaryKey() as $index => $column) {
$script .= $platform->getColumnBindingPHP($column, "':p$index'", "\$key[$index]", ' ');
}
} else {
$pk = $table->getPrimaryKey();
$column = $pk[0];
$script .= $platform->getColumnBindingPHP($column, "':p0'", '$key', ' ');
}
$script .= "
\$stmt->execute();
} catch (Exception \$e) {
Propel::log(\$e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', \$sql), 0, \$e);
}
\$obj = null;
if (\$row = \$stmt->fetch(\PDO::FETCH_NUM)) {";
if ($usesConcreteInheritance) {
$script .= "
\$cls = {$tableMapClassName}::getOMClass(\$row, 0, false);
/** @var $ARClassName \$obj */
\$obj = new \$cls();";
} else {
$script .= "
/** @var $ARClassName \$obj */
\$obj = new $ARClassName();";
}
$script .= "
\$obj->hydrate(\$row);
{$tableMapClassName}::addInstanceToPool(\$obj, $pkHashFromRow);
}
\$stmt->closeCursor();
return \$obj;
}
";
}
/**
* Adds the findPk method for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFindPkComplex(string &$script): void
{
$class = $this->getObjectClassName();
$table = $this->getTable();
// this method is not needed if the table has no primary key
if (!$table->hasPrimaryKey()) {
return;
}
$this->declareClasses('\Propel\Runtime\Connection\ConnectionInterface');
$script .= "
/**
* Find object by primary key.
*
* @param mixed \$key Primary key to use for the query
* @param ConnectionInterface \$con A connection object
*
* @return " . $class . "|array|mixed the result, formatted by the current formatter
*/
protected function findPkComplex(\$key, ConnectionInterface \$con)
{
// As the query uses a PK condition, no limit(1) is necessary.
\$criteria = \$this->isKeepQuery() ? clone \$this : \$this;
\$dataFetcher = \$criteria
->filterByPrimaryKey(\$key)
->doSelect(\$con);
return \$criteria->getFormatter()->init(\$criteria)->formatOne(\$dataFetcher);
}
";
}
/**
* Adds the findPks method for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFindPks(string &$script): void
{
$this->declareClasses(
'\Propel\Runtime\Collection\Collection',
'\Propel\Runtime\Connection\ConnectionInterface',
'\Propel\Runtime\Propel',
);
$table = $this->getTable();
$pks = $table->getPrimaryKey();
$count = count($pks);
$script .= "
/**
* Find objects by primary key
* <code>";
if ($count === 1) {
$script .= "
* \$objs = \$c->findPks(array(12, 56, 832), \$con);";
} else {
$script .= "
* \$objs = \$c->findPks(array(array(12, 56), array(832, 123), array(123, 456)), \$con);";
}
$script .= "
* </code>
* @param array \$keys Primary keys to use for the query
* @param ConnectionInterface \$con an optional connection object
*
* @return Collection|array|mixed the list of results, formatted by the current formatter
*/
public function findPks(\$keys, ?ConnectionInterface \$con = null)
{";
if (!$table->hasPrimaryKey()) {
$this->declareClass('Propel\\Runtime\\Exception\\LogicException');
$script .= "
throw new LogicException('The {$this->getObjectName()} object has no primary key');
}
";
return;
}
$script .= "
if (null === \$con) {
\$con = Propel::getServiceContainer()->getReadConnection(\$this->getDbName());
}
\$this->basePreSelect(\$con);
\$criteria = \$this->isKeepQuery() ? clone \$this : \$this;
\$dataFetcher = \$criteria
->filterByPrimaryKeys(\$keys)
->doSelect(\$con);
return \$criteria->getFormatter()->init(\$criteria)->format(\$dataFetcher);
}
";
}
/**
* Adds the filterByPrimaryKey method for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFilterByPrimaryKey(string &$script): void
{
$script .= "
/**
* Filter the query by primary key
*
* @param mixed \$key Primary key to use for the query
*
* @return \$this The current query, for fluid interface
*/
public function filterByPrimaryKey(\$key)
{";
$table = $this->getTable();
if (!$table->hasPrimaryKey()) {
$this->declareClass('Propel\\Runtime\\Exception\\LogicException');
$script .= "
throw new LogicException('The {$this->getObjectName()} object has no primary key');
}
";
return;
}
$pks = $table->getPrimaryKey();
if (count($pks) === 1) {
// simple primary key
$col = $pks[0];
$const = $this->getColumnConstant($col);
$script .= "
\$this->addUsingAlias($const, \$key, Criteria::EQUAL);
return \$this;";
} else {
// composite primary key
$i = 0;
foreach ($pks as $col) {
$const = $this->getColumnConstant($col);
$script .= "
\$this->addUsingAlias($const, \$key[$i], Criteria::EQUAL);";
$i++;
}
$script .= "
return \$this;";
}
$script .= "
}
";
}
/**
* Adds the filterByPrimaryKey method for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addFilterByPrimaryKeys(string &$script): void
{
$script .= "
/**
* Filter the query by a list of primary keys
*
* @param array|int \$keys The list of primary key to use for the query
*
* @return \$this The current query, for fluid interface
*/
public function filterByPrimaryKeys(\$keys)
{";
$table = $this->getTable();
if (!$table->hasPrimaryKey()) {
$this->declareClass('Propel\\Runtime\\Exception\\LogicException');
$script .= "
throw new LogicException('The {$this->getObjectName()} object has no primary key');
}
";
return;
}
$pks = $table->getPrimaryKey();
if (count($pks) === 1) {
// simple primary key
$col = $pks[0];
$const = $this->getColumnConstant($col);
$script .= "
\$this->addUsingAlias($const, \$keys, Criteria::IN);
return \$this;";
} else {
// composite primary key
$script .= "
if (empty(\$keys)) {
\$this->add(null, '1<>1', Criteria::CUSTOM);
return \$this;
}
foreach (\$keys as \$key) {";
$i = 0;
foreach ($pks as $col) {
$const = $this->getColumnConstant($col);
$script .= "
\$cton$i = \$this->getNewCriterion($const, \$key[$i], Criteria::EQUAL);";
if ($i > 0) {
$script .= "
\$cton0->addAnd(\$cton$i);";
}
$i++;
}
$script .= "
\$this->addOr(\$cton0);
}";
$script .= "
return \$this;";
}
$script .= "
}
";
}
/**
* Adds the filterByCol method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Column $col
*
* @return void
*/
protected function addFilterByCol(string &$script, Column $col): void
{
$colPhpName = $col->getPhpName();
$colName = $col->getName();
$variableName = $col->getCamelCaseName();
$qualifiedName = $this->getColumnConstant($col);
$script .= "
/**
* Filter the query on the $colName column
*";
if ($col->isNumericType()) {
$script .= "
* Example usage:
* <code>
* \$query->filterBy$colPhpName(1234); // WHERE $colName = 1234
* \$query->filterBy$colPhpName(array(12, 34)); // WHERE $colName IN (12, 34)
* \$query->filterBy$colPhpName(array('min' => 12)); // WHERE $colName > 12
* </code>";
if ($col->isForeignKey()) {
foreach ($col->getForeignKeys() as $fk) {
$script .= "
*
* @see filterBy" . $this->getFKPhpNameAffix($fk) . '()';
}
}
$script .= "
*
* @param mixed \$$variableName The value to use as filter.
* Use scalar values for equality.
* Use array values for in_array() equivalent.
* Use associative array('min' => \$minValue, 'max' => \$maxValue) for intervals.";
} elseif ($col->isTemporalType()) {
$script .= "
* Example usage:
* <code>
* \$query->filterBy$colPhpName('2011-03-14'); // WHERE $colName = '2011-03-14'
* \$query->filterBy$colPhpName('now'); // WHERE $colName = '2011-03-14'
* \$query->filterBy$colPhpName(array('max' => 'yesterday')); // WHERE $colName > '2011-03-13'
* </code>
*
* @param mixed \$$variableName The value to use as filter.
* Values can be integers (unix timestamps), DateTime objects, or strings.
* Empty strings are treated as NULL.
* Use scalar values for equality.
* Use array values for in_array() equivalent.
* Use associative array('min' => \$minValue, 'max' => \$maxValue) for intervals.";
} elseif ($col->getType() == PropelTypes::PHP_ARRAY) {
$script .= "
* @param array \$$variableName The values to use as filter.";
} elseif ($col->isTextType()) {
$script .= "
* Example usage:
* <code>
* \$query->filterBy$colPhpName('fooValue'); // WHERE $colName = 'fooValue'
* \$query->filterBy$colPhpName('%fooValue%', Criteria::LIKE); // WHERE $colName LIKE '%fooValue%'
* \$query->filterBy$colPhpName(['foo', 'bar']); // WHERE $colName IN ('foo', 'bar')
* </code>
*
* @param string|string[] \$$variableName The value to use as filter.";
} elseif ($col->isBooleanType()) {
$script .= "
* Example usage:
* <code>
* \$query->filterBy$colPhpName(true); // WHERE $colName = true
* \$query->filterBy$colPhpName('yes'); // WHERE $colName = true
* </code>
*
* @param bool|string \$$variableName The value to use as filter.
* Non-boolean arguments are converted using the following rules:
* * 1, '1', 'true', 'on', and 'yes' are converted to boolean true
* * 0, '0', 'false', 'off', and 'no' are converted to boolean false
* Check on string values is case insensitive (so 'FaLsE' is seen as 'false').";
} else {
$script .= "
* @param mixed \$$variableName The value to use as filter";
}
$script .= "
* @param string|null \$comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return \$this The current query, for fluid interface
*/
public function filterBy$colPhpName(\$$variableName = null, ?string \$comparison = null)
{";
if ($col->isNumericType() || $col->isTemporalType()) {
$script .= "
if (is_array(\$$variableName)) {
\$useMinMax = false;
if (isset(\${$variableName}['min'])) {
\$this->addUsingAlias($qualifiedName, \${$variableName}['min'], Criteria::GREATER_EQUAL);
\$useMinMax = true;
}
if (isset(\${$variableName}['max'])) {
\$this->addUsingAlias($qualifiedName, \${$variableName}['max'], Criteria::LESS_EQUAL);
\$useMinMax = true;
}
if (\$useMinMax) {
return \$this;
}
if (null === \$comparison) {
\$comparison = Criteria::IN;
}
}";
} elseif ($col->getType() == PropelTypes::OBJECT) {
$script .= "
if (is_object(\$$variableName)) {
\$$variableName = serialize(\$$variableName);
}";
} elseif ($col->getType() == PropelTypes::PHP_ARRAY) {
$script .= "
\$key = \$this->getAliasedColName($qualifiedName);
if (null === \$comparison || \$comparison == Criteria::CONTAINS_ALL) {
foreach (\$$variableName as \$value) {
\$value = '%| ' . \$value . ' |%';
if (\$this->containsKey(\$key)) {
\$this->addAnd(\$key, \$value, Criteria::LIKE);
} else {
\$this->add(\$key, \$value, Criteria::LIKE);
}
}
return \$this;
} elseif (\$comparison == Criteria::CONTAINS_SOME) {
foreach (\$$variableName as \$value) {
\$value = '%| ' . \$value . ' |%';
if (\$this->containsKey(\$key)) {
\$this->addOr(\$key, \$value, Criteria::LIKE);
} else {
\$this->add(\$key, \$value, Criteria::LIKE);
}
}
return \$this;
} elseif (\$comparison == Criteria::CONTAINS_NONE) {
foreach (\$$variableName as \$value) {
\$value = '%| ' . \$value . ' |%';
if (\$this->containsKey(\$key)) {
\$this->addAnd(\$key, \$value, Criteria::NOT_LIKE);
} else {
\$this->add(\$key, \$value, Criteria::NOT_LIKE);
}
}
\$this->addOr(\$key, null, Criteria::ISNULL);
return \$this;
}";
} elseif ($col->isSetType()) {
$this->declareClasses(
'Propel\Common\Util\SetColumnConverter',
'Propel\Common\Exception\SetColumnConverterException',
);
$script .= "
\$valueSet = " . $this->getTableMapClassName() . '::getValueSet(' . $this->getColumnConstant($col) . ");
try {
\${$variableName} = SetColumnConverter::convertToInt(\${$variableName}, \$valueSet);
} catch (SetColumnConverterException \$e) {
throw new PropelException(sprintf('Value \"%s\" is not accepted in this set column', \$e->getValue()), \$e->getCode(), \$e);
}
if (null === \$comparison || \$comparison == Criteria::CONTAINS_ALL) {
if (\${$variableName} === '0') {
return \$this;
}
\$comparison = Criteria::BINARY_ALL;
} elseif (\$comparison == Criteria::CONTAINS_SOME || \$comparison == Criteria::IN) {
if (\${$variableName} === '0') {
return \$this;
}
\$comparison = Criteria::BINARY_AND;
} elseif (\$comparison == Criteria::CONTAINS_NONE) {
\$key = \$this->getAliasedColName($qualifiedName);
if (\${$variableName} !== '0') {
\$this->add(\$key, \${$variableName}, Criteria::BINARY_NONE);
}
\$this->addOr(\$key, null, Criteria::ISNULL);
return \$this;
}";
} elseif ($col->getType() == PropelTypes::ENUM) {
$script .= "
\$valueSet = " . $this->getTableMapClassName() . '::getValueSet(' . $this->getColumnConstant($col) . ");
if (is_scalar(\$$variableName)) {
if (!in_array(\$$variableName, \$valueSet)) {
throw new PropelException(sprintf('Value \"%s\" is not accepted in this enumerated column', \$$variableName));
}
\$$variableName = array_search(\$$variableName, \$valueSet);
} elseif (is_array(\$$variableName)) {
\$convertedValues = [];
foreach (\$$variableName as \$value) {
if (!in_array(\$value, \$valueSet)) {
throw new PropelException(sprintf('Value \"%s\" is not accepted in this enumerated column', \$value));
}
\$convertedValues []= array_search(\$value, \$valueSet);
}
\$$variableName = \$convertedValues;
if (null === \$comparison) {
\$comparison = Criteria::IN;
}
}";
} elseif ($col->isTextType()) {
$script .= "
if (null === \$comparison) {
if (is_array(\$$variableName)) {
\$comparison = Criteria::IN;
}
}";
} elseif ($col->isBooleanType()) {
$script .= "
if (is_string(\$$variableName)) {
\$$variableName = in_array(strtolower(\$$variableName), array('false', 'off', '-', 'no', 'n', '0', ''), true) ? false : true;
}";
} elseif ($col->isUuidBinaryType()) {
$uuidSwapFlag = $this->getUuidSwapFlagLiteral();
$script .= "
\$$variableName = UuidConverter::uuidToBinRecursive(\$$variableName, $uuidSwapFlag);";
}
$script .= "
\$this->addUsingAlias($qualifiedName, \$$variableName, \$comparison);
return \$this;
}
";
}
/**
* Adds the singular filterByCol method for an Array column.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Column $col
*
* @return void
*/
protected function addFilterByArrayCol(string &$script, Column $col): void
{
$singularPhpName = $col->getPhpSingularName();
$colName = $col->getName();
$variableName = $col->getCamelCaseName();
$qualifiedName = $this->getColumnConstant($col);
$script .= "
/**
* Filter the query on the $colName column
* @param mixed \$$variableName The value to use as filter
* @param string|null \$comparison Operator to use for the column comparison, defaults to Criteria::CONTAINS_ALL
*
* @return \$this The current query, for fluid interface
*/
public function filterBy$singularPhpName(\$$variableName = null, ?string \$comparison = null)
{
if (null === \$comparison || \$comparison == Criteria::CONTAINS_ALL) {
if (is_scalar(\$$variableName)) {
\$$variableName = '%| ' . \$$variableName . ' |%';
\$comparison = Criteria::LIKE;
}
} elseif (\$comparison == Criteria::CONTAINS_NONE) {
\$$variableName = '%| ' . \$$variableName . ' |%';
\$comparison = Criteria::NOT_LIKE;
\$key = \$this->getAliasedColName($qualifiedName);
if (\$this->containsKey(\$key)) {
\$this->addAnd(\$key, \$$variableName, \$comparison);
} else {
\$this->addAnd(\$key, \$$variableName, \$comparison);
}
\$this->addOr(\$key, null, Criteria::ISNULL);
return \$this;
}
\$this->addUsingAlias($qualifiedName, \$$variableName, \$comparison);
return \$this;
}
";
}
/**
* Adds the singular filterByCol method for an Array column.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Column $col
*
* @return void
*/
protected function addFilterBySetCol(string &$script, Column $col): void
{
$colPhpName = $col->getPhpName();
$singularPhpName = $col->getPhpSingularName();
$colName = $col->getName();
$variableName = $col->getCamelCaseName();
$script .= "
/**
* Filter the query on the $colName column
* @param mixed \$$variableName The value to use as filter
* @param string \$comparison Operator to use for the column comparison, defaults to Criteria::CONTAINS_ALL
*
* @return \$this The current query, for fluid interface
*/
public function filterBy$singularPhpName(\$$variableName = null, ?string \$comparison = null)
{
\$this->filterBy$colPhpName(\$$variableName, \$comparison);
return \$this;
}
";
}
/**
* Adds the filterByFk method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk ForeignKey
*
* @return void
*/
protected function addFilterByFk(string &$script, ForeignKey $fk): void
{
$this->declareClasses(
'\Propel\Runtime\Collection\ObjectCollection',
'\Propel\Runtime\Exception\PropelException',
);
$fkTable = $fk->getForeignTable();
$fkStubObjectBuilder = $this->getNewStubObjectBuilder($fkTable);
$this->declareClassFromBuilder($fkStubObjectBuilder);
$fkPhpName = $this->getClassNameFromBuilder($fkStubObjectBuilder, true);
$relationName = $this->getFKPhpNameAffix($fk);
$objectName = '$' . $fkTable->getCamelCaseName();
$script .= "
/**
* Filter the query by a related $fkPhpName object
*";
if ($fk->isComposite()) {
$script .= "
* @param $fkPhpName $objectName The related object to use as filter";
} else {
$script .= "
* @param $fkPhpName|ObjectCollection $objectName The related object(s) to use as filter";
}
$script .= "
* @param string|null \$comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @throws \\Propel\\Runtime\\Exception\\PropelException
*
* @return \$this The current query, for fluid interface
*/
public function filterBy$relationName($objectName, ?string \$comparison = null)
{
if ($objectName instanceof $fkPhpName) {
return \$this";
foreach ($fk->getMapping() as $mapping) {
[$localColumn, $rightValueOrColumn] = $mapping;
if ($rightValueOrColumn instanceof Column) {
$script .= "
->addUsingAlias(" . $this->getColumnConstant($localColumn) . ', ' . $objectName . '->get' . $rightValueOrColumn->getPhpName() . '(), $comparison)';
} else {
$value = var_export($rightValueOrColumn, true);
$script .= "
->addUsingAlias(" . $this->getColumnConstant($localColumn) . ", $value, \$comparison)";
}
}
$script .= ';';
if (!$fk->isComposite()) {
$localColumnConstant = $this->getColumnConstant($fk->getLocalColumn());
$foreignColumnName = $fk->getForeignColumn()->getPhpName();
$keyColumn = $fk->getForeignTable()->hasCompositePrimaryKey() ? $foreignColumnName : 'PrimaryKey';
$script .= "
} elseif ($objectName instanceof ObjectCollection) {
if (null === \$comparison) {
\$comparison = Criteria::IN;
}
\$this
->addUsingAlias($localColumnConstant, {$objectName}->toKeyValue('$keyColumn', '$foreignColumnName'), \$comparison);
return \$this;";
}
$script .= "
} else {";
if ($fk->isComposite()) {
$script .= "
throw new PropelException('filterBy$relationName() only accepts arguments of type $fkPhpName');";
} else {
$script .= "
throw new PropelException('filterBy$relationName() only accepts arguments of type $fkPhpName or Collection');";
}
$script .= "
}
}
";
}
/**
* Adds the filterByRefFk method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk
*
* @return void
*/
protected function addFilterByRefFk(string &$script, ForeignKey $fk): void
{
$this->declareClasses(
'\Propel\Runtime\Collection\ObjectCollection',
'\Propel\Runtime\Exception\PropelException',
);
$fkTable = $this->getTable()->getDatabase()->getTable($fk->getTableName());
$fkStubObjectBuilder = $this->getNewStubObjectBuilder($fkTable);
$this->declareClassFromBuilder($fkStubObjectBuilder);
$fkPhpName = $this->getClassNameFromBuilder($fkStubObjectBuilder, true);
$relationName = $this->getRefFKPhpNameAffix($fk);
$objectName = '$' . $fkTable->getCamelCaseName();
$script .= "
/**
* Filter the query by a related $fkPhpName object
*
* @param $fkPhpName|ObjectCollection $objectName the related object to use as filter
* @param string|null \$comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return \$this The current query, for fluid interface
*/
public function filterBy$relationName($objectName, ?string \$comparison = null)
{
if ($objectName instanceof $fkPhpName) {
\$this";
foreach ($fk->getInverseMapping() as $mapping) {
/** @var \Propel\Generator\Model\Column $foreignColumn */
[$localValueOrColumn, $foreignColumn] = $mapping;
$rightValue = "{$objectName}->get" . $foreignColumn->getPhpName() . '()';
if ($localValueOrColumn instanceof Column) {
$script .= "
->addUsingAlias(" . $this->getColumnConstant($localValueOrColumn) . ", $rightValue, \$comparison)";
} else {
$leftValue = var_export($localValueOrColumn, true);
$bindingType = $foreignColumn->getPDOType();
$script .= "
->where(\"$leftValue = ?\", $rightValue, $bindingType)";
}
}
$script .= ';
return $this;';
if (!$fk->isComposite()) {
$script .= "
} elseif ($objectName instanceof ObjectCollection) {
\$this
->use{$relationName}Query()
->filterByPrimaryKeys({$objectName}->getPrimaryKeys())
->endUse();
return \$this;";
}
$script .= "
} else {";
if ($fk->isComposite()) {
$script .= "
throw new PropelException('filterBy$relationName() only accepts arguments of type $fkPhpName');";
} else {
$script .= "
throw new PropelException('filterBy$relationName() only accepts arguments of type $fkPhpName or Collection');";
}
$script .= "
}
}
";
}
/**
* Adds the joinFk method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk ForeignKey
*
* @return void
*/
protected function addJoinFk(string &$script, ForeignKey $fk): void
{
$queryClass = $this->getQueryClassName();
$fkTable = $fk->getForeignTable();
$relationName = $this->getFKPhpNameAffix($fk);
$joinType = $this->getJoinType($fk);
$this->addJoinRelated($script, $fkTable, $queryClass, $relationName, $joinType);
}
/**
* Adds the joinRefFk method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk
*
* @return void
*/
protected function addJoinRefFk(string &$script, ForeignKey $fk): void
{
$queryClass = $this->getQueryClassName();
$fkTable = $this->getTable()->getDatabase()->getTable($fk->getTableName());
$relationName = $this->getRefFKPhpNameAffix($fk);
$joinType = $this->getJoinType($fk);
$this->addJoinRelated($script, $fkTable, $queryClass, $relationName, $joinType);
}
/**
* Adds a joinRelated method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Table $fkTable
* @param string $queryClass
* @param string $relationName
* @param string $joinType
*
* @return void
*/
protected function addJoinRelated(
string &$script,
Table $fkTable,
string $queryClass,
string $relationName,
string $joinType
): void {
$script .= "
/**
* Adds a JOIN clause to the query using the " . $relationName . " relation
*
* @param string|null \$relationAlias Optional alias for the relation
* @param string|null \$joinType Accepted values are null, 'left join', 'right join', 'inner join'
*
* @return \$this The current query, for fluid interface
*/
public function join" . $relationName . '(?string $relationAlias = null, ?string $joinType = ' . $joinType . ")
{
\$tableMap = \$this->getTableMap();
\$relationMap = \$tableMap->getRelation('" . $relationName . "');
// create a ModelJoin object for this join
\$join = new ModelJoin();
\$join->setJoinType(\$joinType);
\$join->setRelationMap(\$relationMap, \$this->useAliasInSQL ? \$this->getModelAlias() : null, \$relationAlias);
if (\$previousJoin = \$this->getPreviousJoin()) {
\$join->setPreviousJoin(\$previousJoin);
}
// add the ModelJoin to the current object
if (\$relationAlias) {
\$this->addAlias(\$relationAlias, \$relationMap->getRightTable()->getName());
\$this->addJoinObject(\$join, \$relationAlias);
} else {
\$this->addJoinObject(\$join, '" . $relationName . "');
}
return \$this;
}
";
}
/**
* Adds the useFkQuery method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk ForeignKey
*
* @return void
*/
protected function addUseFkQuery(string &$script, ForeignKey $fk): void
{
$fkTable = $fk->getForeignTable();
$fkQueryBuilder = $this->getNewStubQueryBuilder($fkTable);
$queryClass = $this->getClassNameFromBuilder($fkQueryBuilder, true);
$relationName = $this->getFKPhpNameAffix($fk);
$joinType = $this->getJoinType($fk);
$this->addUseRelatedQuery($script, $fkTable, $queryClass, $relationName, $joinType);
$this->addWithRelatedQuery($script, $fkTable, $queryClass, $relationName, $joinType);
$this->addUseRelatedExistsQuery($script, $fkTable, $queryClass, $relationName);
$this->addUseRelatedInQuery($script, $fkTable, $queryClass, $relationName);
}
/**
* Adds the useFkQuery method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\ForeignKey $fk
*
* @return void
*/
protected function addUseRefFkQuery(string &$script, ForeignKey $fk): void
{
$fkTable = $this->getTable()->getDatabase()->getTable($fk->getTableName());
$fkQueryBuilder = $this->getNewStubQueryBuilder($fkTable);
$queryClass = $this->getClassNameFromBuilder($fkQueryBuilder, true);
$relationName = $this->getRefFKPhpNameAffix($fk);
$joinType = $this->getJoinType($fk);
$this->addUseRelatedQuery($script, $fkTable, $queryClass, $relationName, $joinType);
$this->addWithRelatedQuery($script, $fkTable, $queryClass, $relationName, $joinType);
$this->addUseRelatedExistsQuery($script, $fkTable, $queryClass, $relationName);
$this->addUseRelatedInQuery($script, $fkTable, $queryClass, $relationName);
}
/**
* Adds a useRelatedQuery method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Table $fkTable
* @param string $queryClass
* @param string $relationName
* @param string $joinType
*
* @return void
*/
protected function addUseRelatedQuery(string &$script, Table $fkTable, string $queryClass, string $relationName, string $joinType): void
{
$script .= "
/**
* Use the $relationName relation " . $fkTable->getPhpName() . " object
*
* @see useQuery()
*
* @param string \$relationAlias optional alias for the relation,
* to be used as main alias in the secondary query
* @param string \$joinType Accepted values are null, 'left join', 'right join', 'inner join'
*
* @return $queryClass A secondary query class using the current class as primary query
*/
public function use" . $relationName . 'Query($relationAlias = null, $joinType = ' . $joinType . ")
{
return \$this
->join" . $relationName . "(\$relationAlias, \$joinType)
->useQuery(\$relationAlias ? \$relationAlias : '$relationName', '$queryClass');
}
";
}
/**
* Adds a useExistsQuery and useNotExistsQuery to the object script.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Table $fkTable The target of the relation
* @param string $queryClass Query object class name that will be returned by the exists statement.
* @param string $relationName Name of the relation
*
* @return void
*/
protected function addUseRelatedExistsQuery(string &$script, Table $fkTable, string $queryClass, string $relationName): void
{
$vars = [
'queryClass' => $queryClass,
'relationDescription' => $this->getRelationDescription($relationName, $fkTable),
'relationName' => $relationName,
'existsType' => ExistsQueryCriterion::TYPE_EXISTS,
'notExistsType' => ExistsQueryCriterion::TYPE_NOT_EXISTS,
];
$templatePath = $this->getTemplatePath(__DIR__);
$template = new PropelTemplate();
$filePath = $templatePath . 'baseQueryExistsMethods.php';
$template->setTemplateFile($filePath);
$script .= $template->render($vars);
}
/**
* Adds a useInQuery and useNotInQuery to the object script.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Table $fkTable The target of the relation
* @param string $queryClass Query object class name that will be returned by the IN statement.
* @param string $relationName Name of the relation
*
* @return void
*/
protected function addUseRelatedInQuery(string &$script, Table $fkTable, string $queryClass, string $relationName): void
{
$vars = [
'queryClass' => $queryClass,
'relationDescription' => $this->getRelationDescription($relationName, $fkTable),
'relationName' => $relationName,
'inType' => trim(Criteria::IN),
'notInType' => trim(Criteria::NOT_IN),
];
$templatePath = $this->getTemplatePath(__DIR__);
$template = new PropelTemplate();
$filePath = $templatePath . 'baseQueryInMethods.php';
$template->setTemplateFile($filePath);
$script .= $template->render($vars);
}
/**
* @param string $relationName
* @param \Propel\Generator\Model\Table $fkTable
*
* @return string
*/
protected function getRelationDescription(string $relationName, Table $fkTable): string
{
return ($relationName === $fkTable->getPhpName()) ?
"relation to $relationName table" :
"$relationName relation to the {$fkTable->getPhpName()} table";
}
/**
* Adds a withRelatedQuery method for this object.
*
* @param string $script The script will be modified in this method.
* @param \Propel\Generator\Model\Table $fkTable
* @param string $queryClass
* @param string $relationName
* @param string $joinType
*
* @return void
*/
protected function addWithRelatedQuery(string &$script, Table $fkTable, string $queryClass, string $relationName, string $joinType): void
{
$script .= "
/**
* Use the {$relationName} relation {$fkTable->getPhpName()} object
*
* @param callable({$queryClass}):{$queryClass} \$callable A function working on the related query
*
* @param string|null \$relationAlias optional alias for the relation
*
* @param string|null \$joinType Accepted values are null, 'left join', 'right join', 'inner join'
*
* @return \$this
*/
public function with{$relationName}Query(
callable \$callable,
string \$relationAlias = null,
?string \$joinType = {$joinType}
) {
\$relatedQuery = \$this->use{$relationName}Query(
\$relationAlias,
\$joinType
);
\$callable(\$relatedQuery);
\$relatedQuery->endUse();
return \$this;
}
";
}
/**
* @param string $script
* @param \Propel\Generator\Model\CrossForeignKeys $crossFKs
*
* @return void
*/
protected function addFilterByCrossFK(string &$script, CrossForeignKeys $crossFKs): void
{
$relationName = $this->getRefFKPhpNameAffix($crossFKs->getIncomingForeignKey(), false);
foreach ($crossFKs->getCrossForeignKeys() as $crossFK) {
$crossRefTable = $crossFK->getTable();
$foreignTable = $crossFK->getForeignTable();
$fkPhpName = $foreignTable->getPhpName();
$crossTableName = $crossRefTable->getName();
$relName = $this->getFKPhpNameAffix($crossFK, false);
$objectName = '$' . $foreignTable->getCamelCaseName();
$script .= "
/**
* Filter the query by a related $fkPhpName object
* using the $crossTableName table as cross reference
*
* @param $fkPhpName $objectName the related object to use as filter
* @param string \$comparison Operator to use for the column comparison, defaults to Criteria::EQUAL and Criteria::IN for queries
*
* @return \$this The current query, for fluid interface
*/
public function filterBy{$relName}($objectName, string \$comparison = null)
{
\$this
->use{$relationName}Query()
->filterBy{$relName}($objectName, \$comparison)
->endUse();
return \$this;
}
";
}
}
/**
* Adds the prune method for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addPrune(string &$script): void
{
$table = $this->getTable();
$class = $this->getObjectClassName();
$objectName = '$' . $table->getCamelCaseName();
$script .= "
/**
* Exclude object from result
*
* @param $class $objectName Object to remove from the list of results
*
* @return \$this The current query, for fluid interface
*/
public function prune($objectName = null)
{
if ($objectName) {";
$pks = $table->getPrimaryKey();
if (count($pks) > 1) {
$i = 0;
$conditions = [];
foreach ($pks as $col) {
$const = $this->getColumnConstant($col);
$condName = "'pruneCond" . $i . "'";
$conditions[] = $condName;
$script .= "
\$this->addCond(" . $condName . ", \$this->getAliasedColName($const), " . $objectName . '->get' . $col->getPhpName() . '(), Criteria::NOT_EQUAL);';
$i++;
}
$conditionsString = implode(', ', $conditions);
$script .= "
\$this->combine(array(" . $conditionsString . '), Criteria::LOGICAL_OR);';
} elseif ($table->hasPrimaryKey()) {
$col = $pks[0];
$const = $this->getColumnConstant($col);
$script .= "
\$this->addUsingAlias($const, " . $objectName . '->get' . $col->getPhpName() . '(), Criteria::NOT_EQUAL);';
} else {
$this->declareClass('Propel\\Runtime\\Exception\\LogicException');
$script .= "
throw new LogicException('{$this->getObjectName()} object has no primary key');
";
}
$script .= "
}
return \$this;
}
";
}
/**
* Adds the basePreSelect hook for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addBasePreSelect(string &$script): void
{
$behaviorCode = '';
$this->applyBehaviorModifier('preSelectQuery', $behaviorCode, ' ');
if (!$behaviorCode) {
return;
}
$script .= "
/**
* Code to execute before every SELECT statement
*
* @param ConnectionInterface \$con The connection object used by the query
*/
protected function basePreSelect(ConnectionInterface \$con): void
{" . $behaviorCode . "
\$this->preSelect(\$con);
}
";
}
/**
* Adds the basePreDelete hook for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addBasePreDelete(string &$script): void
{
$behaviorCode = '';
$this->applyBehaviorModifier('preDeleteQuery', $behaviorCode, ' ');
if (!$behaviorCode) {
return;
}
$script .= "
/**
* Code to execute before every DELETE statement
*
* @param ConnectionInterface \$con The connection object used by the query
* @return int|null
*/
protected function basePreDelete(ConnectionInterface \$con): ?int
{" . $behaviorCode . "
return \$this->preDelete(\$con);
}
";
}
/**
* Adds the basePostDelete hook for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addBasePostDelete(string &$script): void
{
$behaviorCode = '';
$this->applyBehaviorModifier('postDeleteQuery', $behaviorCode, ' ');
if (!$behaviorCode) {
return;
}
$script .= "
/**
* Code to execute after every DELETE statement
*
* @param int \$affectedRows the number of deleted rows
* @param ConnectionInterface \$con The connection object used by the query
* @return int|null
*/
protected function basePostDelete(int \$affectedRows, ConnectionInterface \$con): ?int
{" . $behaviorCode . "
return \$this->postDelete(\$affectedRows, \$con);
}
";
}
/**
* Adds the basePreUpdate hook for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addBasePreUpdate(string &$script): void
{
$behaviorCode = '';
$this->applyBehaviorModifier('preUpdateQuery', $behaviorCode, ' ');
if (!$behaviorCode) {
return;
}
$script .= "
/**
* Code to execute before every UPDATE statement
*
* @param array \$values The associative array of columns and values for the update
* @param ConnectionInterface \$con The connection object used by the query
* @param bool \$forceIndividualSaves If false (default), the resulting call is a Criteria::doUpdate(), otherwise it is a series of save() calls on all the found objects
*
* @return int|null
*/
protected function basePreUpdate(&\$values, ConnectionInterface \$con, \$forceIndividualSaves = false): ?int
{" . $behaviorCode . "
return \$this->preUpdate(\$values, \$con, \$forceIndividualSaves);
}
";
}
/**
* Adds the basePostUpdate hook for this object.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addBasePostUpdate(string &$script): void
{
$behaviorCode = '';
$this->applyBehaviorModifier('postUpdateQuery', $behaviorCode, ' ');
if (!$behaviorCode) {
return;
}
$script .= "
/**
* Code to execute after every UPDATE statement
*
* @param int \$affectedRows the number of updated rows
* @param ConnectionInterface \$con The connection object used by the query
*
* @return int|null
*/
protected function basePostUpdate(\$affectedRows, ConnectionInterface \$con): ?int
{" . $behaviorCode . "
return \$this->postUpdate(\$affectedRows, \$con);
}
";
}
/**
* Checks whether any registered behavior on that table has a modifier for a hook
*
* @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
* @param string $modifier
*
* @return bool
*/
public function hasBehaviorModifier(string $hookName, string $modifier = ''): bool
{
return parent::hasBehaviorModifier($hookName, 'QueryBuilderModifier');
}
/**
* Checks whether any registered behavior on that table has a modifier for a hook
*
* @param string $hookName The name of the hook as called from one of this class methods, e.g. "preSave"
* @param string $script The script will be modified in this method.
* @param string $tab
*
* @return string
*/
public function applyBehaviorModifier(string $hookName, string &$script, string $tab = ' '): string
{
$this->applyBehaviorModifierBase($hookName, 'QueryBuilderModifier', $script, $tab);
return $script;
}
/**
* Checks whether any registered behavior content creator on that table exists a contentName
*
* @param string $contentName The name of the content as called from one of this class methods, e.g. "parentClassName"
*
* @return string|null
*/
public function getBehaviorContent(string $contentName): ?string
{
return $this->getBehaviorContentBase($contentName, 'QueryBuilderModifier');
}
/**
* Adds the doDelete() method.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addDelete(string &$script): void
{
$script .= "
/**
* Performs a DELETE on the database based on the current ModelCriteria
*
* @param ConnectionInterface \$con the connection to use
* @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows
* if supported by native driver or if emulated using Propel.
* @throws \Propel\Runtime\Exception\PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public function delete(?ConnectionInterface \$con = null): int
{
if (null === \$con) {
\$con = Propel::getServiceContainer()->getWriteConnection(" . $this->getTableMapClass() . "::DATABASE_NAME);
}
\$criteria = \$this;
// Set the correct dbName
\$criteria->setDbName(" . $this->getTableMapClass() . "::DATABASE_NAME);
// use transaction because \$criteria could contain info
// for more than one table or we could emulating ON DELETE CASCADE, etc.
return \$con->transaction(function () use (\$con, \$criteria) {
\$affectedRows = 0; // initialize var to track total num of affected rows
";
if ($this->isDeleteCascadeEmulationNeeded()) {
$script .= "
// cloning the Criteria in case it's modified by doSelect() or doSelectStmt()
\$c = clone \$criteria;
\$affectedRows += \$c->doOnDeleteCascade(\$con);
";
}
if ($this->isDeleteSetNullEmulationNeeded()) {
$script .= "
// cloning the Criteria in case it's modified by doSelect() or doSelectStmt()
\$c = clone \$criteria;
\$c->doOnDeleteSetNull(\$con);
";
}
$script .= "
{$this->getTableMapClassName()}::removeInstanceFromPool(\$criteria);
";
$script .= "
\$affectedRows += ModelCriteria::delete(\$con);
{$this->getTableMapClassName()}::clearRelatedInstancePool();
return \$affectedRows;
});
}
";
}
/**
* Adds the doOnDeleteCascade() method, which provides ON DELETE CASCADE emulation.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addDoOnDeleteCascade(string &$script): void
{
$table = $this->getTable();
$script .= "
/**
* This is a method for emulating ON DELETE CASCADE for DBs that don't support this
* feature (like MySQL or SQLite).
*
* This method is not very speedy because it must perform a query first to get
* the implicated records and then perform the deletes by calling those Query classes.
*
* This method should be used within a transaction if possible.
*
* @param ConnectionInterface \$con
* @return int The number of affected rows (if supported by underlying database driver).
*/
protected function doOnDeleteCascade(ConnectionInterface \$con): int
{
// initialize var to track total num of affected rows
\$affectedRows = 0;
// first find the objects that are implicated by the \$this
\$objects = {$this->getQueryClassName()}::create(null, \$this)->find(\$con);
foreach (\$objects as \$obj) {
";
foreach ($table->getReferrers() as $fk) {
// $fk is the foreign key in the other table, so localTableName will
// actually be the table name of other table
$tblFK = $fk->getTable();
$joinedTableTableMapBuilder = $this->getNewTableMapBuilder($tblFK);
if (!$tblFK->isForReferenceOnly()) {
// we can't perform operations on tables that are
// not within the schema (i.e. that we have no map for, etc.)
$fkClassName = $joinedTableTableMapBuilder->getObjectClassName();
if ($fk->getOnDelete() === ForeignKey::CASCADE) {
// backwards on purpose
$columnNamesF = $fk->getLocalColumns();
$columnNamesL = $fk->getForeignColumns();
$this->declareClassFromBuilder($joinedTableTableMapBuilder->getTableMapBuilder());
$script .= "
// delete related $fkClassName objects
\$query = new " . $joinedTableTableMapBuilder->getQueryClassName(true) . ";
";
for ($x = 0, $xlen = count($columnNamesF); $x < $xlen; $x++) {
$columnFK = $tblFK->getColumn($columnNamesF[$x]);
$columnL = $table->getColumn($columnNamesL[$x]);
$script .= "
\$query->add(" . $joinedTableTableMapBuilder->getColumnConstant($columnFK) . ', $obj->get' . $columnL->getPhpName() . '());';
}
$script .= "
\$affectedRows += \$query->delete(\$con);";
}
}
}
$script .= "
}
return \$affectedRows;
}
";
}
// end addDoOnDeleteCascade
/**
* Adds the doOnDeleteSetNull() method, which provides ON DELETE SET NULL emulation.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addDoOnDeleteSetNull(string &$script): void
{
$table = $this->getTable();
$script .= "
/**
* This is a method for emulating ON DELETE SET NULL DBs that don't support this
* feature (like MySQL or SQLite).
*
* This method is not very speedy because it must perform a query first to get
* the implicated records and then perform the deletes by calling those query classes.
*
* This method should be used within a transaction if possible.
*
* @param ConnectionInterface \$con
* @return void
*/
protected function doOnDeleteSetNull(ConnectionInterface \$con): void
{
// first find the objects that are implicated by the \$this
\$objects = {$this->getQueryClassName()}::create(null, \$this)->find(\$con);
foreach (\$objects as \$obj) {
";
// This logic is almost exactly the same as that in doOnDeleteCascade()
// it may make sense to refactor this, provided that things don't
// get too complicated.
foreach ($table->getReferrers() as $fk) {
// $fk is the foreign key in the other table, so localTableName will
// actually be the table name of other table
$tblFK = $fk->getTable();
$refTableTableMapBuilder = $this->getNewTableMapBuilder($tblFK);
if (!$tblFK->isForReferenceOnly()) {
// we can't perform operations on tables that are
// not within the schema (i.e. that we have no map for, etc.)
$fkClassName = $refTableTableMapBuilder->getObjectClassName();
if ($fk->getOnDelete() === ForeignKey::SETNULL) {
// backwards on purpose
$columnNamesF = $fk->getLocalColumns();
$columnNamesL = $fk->getForeignColumns(); // should be same num as foreign
$this->declareClassFromBuilder($refTableTableMapBuilder);
$script .= "
// set fkey col in related $fkClassName rows to NULL
\$query = new " . $refTableTableMapBuilder->getQueryClassName(true) . "();
\$updateValues = new Criteria();";
for ($x = 0, $xlen = count($columnNamesF); $x < $xlen; $x++) {
$columnFK = $tblFK->getColumn($columnNamesF[$x]);
$columnL = $table->getColumn($columnNamesL[$x]);
$script .= "
\$query->add(" . $refTableTableMapBuilder->getColumnConstant($columnFK) . ', $obj->get' . $columnL->getPhpName() . "());
\$updateValues->add(" . $refTableTableMapBuilder->getColumnConstant($columnFK) . ", null);
";
}
$script .= "\$query->update(\$updateValues, \$con);
";
}
}
}
$script .= "
}
}
";
}
/**
* Adds the doDeleteAll() method.
*
* @param string $script The script will be modified in this method.
*
* @return void
*/
protected function addDoDeleteAll(string &$script): void
{
$table = $this->getTable();
$script .= "
/**
* Deletes all rows from the " . $table->getName() . " table.
*
* @param ConnectionInterface \$con the connection to use
* @return int The number of affected rows (if supported by underlying database driver).
*/
public function doDeleteAll(?ConnectionInterface \$con = null): int
{
if (null === \$con) {
\$con = Propel::getServiceContainer()->getWriteConnection(" . $this->getTableMapClass() . "::DATABASE_NAME);
}
// use transaction because \$criteria could contain info
// for more than one table or we could emulating ON DELETE CASCADE, etc.
return \$con->transaction(function () use (\$con) {
\$affectedRows = 0; // initialize var to track total num of affected rows
";
if ($this->isDeleteCascadeEmulationNeeded()) {
$script .= "\$affectedRows += \$this->doOnDeleteCascade(\$con);
";
}
if ($this->isDeleteSetNullEmulationNeeded()) {
$script .= "\$this->doOnDeleteSetNull(\$con);
";
}
$script .= "\$affectedRows += parent::doDeleteAll(\$con);
// Because this db requires some delete cascade/set null emulation, we have to
// clear the cached instance *after* the emulation has happened (since
// instances get re-added by the select statement contained therein).
{$this->getTableMapClassName()}::clearInstancePool();
{$this->getTableMapClassName()}::clearRelatedInstancePool();
return \$affectedRows;
});
}
";
}
}