diff --git a/generator/lib/behavior/sortable/SortableBehavior.php b/generator/lib/behavior/sortable/SortableBehavior.php index 358fbe584..118edf7e4 100644 --- a/generator/lib/behavior/sortable/SortableBehavior.php +++ b/generator/lib/behavior/sortable/SortableBehavior.php @@ -28,7 +28,7 @@ class SortableBehavior extends Behavior protected $parameters = array( 'rank_column' => 'sortable_rank', 'use_scope' => 'false', - 'scope_column' => 'sortable_scope', + 'scope_column' => '', ); protected $objectBuilderModifier, $queryBuilderModifier, $peerBuilderModifier; @@ -46,7 +46,8 @@ public function modifyTable() 'type' => 'INTEGER' )); } - if ($this->useScope() && !$table->containsColumn($this->getParameter('scope_column'))) { + if ($this->useScope() && !$this->hasMultipleScopes() && + !$table->containsColumn($this->getParameter('scope_column'))) { $table->addColumn(array( 'name' => $this->getParameter('scope_column'), 'type' => 'INTEGER' @@ -54,18 +55,136 @@ public function modifyTable() } if ($this->useScope()) { - $keys = $table->getColumnForeignKeys($this->getParameter('scope_column')); - foreach ($keys as $key) { - if ($key->isForeignPrimaryKey() && $key->getOnDelete() == ForeignKey::SETNULL) { - $foreignTable = $key->getForeignTable(); - $relationBehavior = new SortableRelationBehavior(); - $relationBehavior->addParameter(array('name' => 'foreign_table', 'value' => $table->getName())); - $relationBehavior->addParameter(array('name' => 'foreign_scope_column', 'value' => $this->getParameter('scope_column'))); - $relationBehavior->addParameter(array('name' => 'foreign_rank_column', 'value' => $this->getParameter('rank_column'))); - $foreignTable->addBehavior($relationBehavior); + $scopes = $this->getScopes(); + if (0 === count($scopes)) { + throw new \InvalidArgumentException(sprintf( + 'The sortable behavior in `%s` needs a `scope_column` parameter.', + $this->getTable()->getName() + )); + } + foreach ($scopes as $scope) { + $keys = $table->getColumnForeignKeys($scope); + foreach ($keys as $key) { + if ($key->isForeignPrimaryKey() && $key->getOnDelete() == ForeignKey::SETNULL) { + $foreignTable = $key->getForeignTable(); + $relationBehavior = new SortableRelationBehavior(); + $relationBehavior->addParameter(array('name' => 'foreign_table', 'value' => $table->getName())); + $relationBehavior->addParameter(array('name' => 'foreign_scope_column', 'value' => $scope)); + $relationBehavior->addParameter(array('name' => 'foreign_rank_column', 'value' => $this->getParameter('rank_column'))); + $foreignTable->addBehavior($relationBehavior); + } + } + } + } + } + + /** + * Generates the method argument signature, the appropriate phpDoc for @params, + * the scope builder php code and the scope variable builder php code/ + * + * @return array ($methodSignature, $paramsDoc, $scopeBuilder, $buildScopeVars) + */ + public function generateScopePhp() + { + + $methodSignature = ''; + $paramsDoc = ''; + $buildScope = ''; + $buildScopeVars = ''; + + if ($this->hasMultipleScopes()) { + + $methodSignature = array(); + $buildScope = array(); + $paramsDoc = array(); + + foreach ($this->getScopes() as $idx => $scope) { + + $column = $this->table->getColumn($scope); + $param = '$scope'.$column->getPhpName(); + + $buildScope[] = " \$scope[] = $param;\n"; + $buildScopeVars[] = " $param = \$scope[$idx];\n"; + $paramsDoc[] = " * @param ".$column->getPhpType()." $param Scope value for column `".$column->getPhpName()."`"; + + if (!$column->isNotNull()) { + $param .= ' = null'; + } + $methodSignature[] = $param; + } + + $methodSignature = implode(', ', $methodSignature); + $paramsDoc = implode("\n", $paramsDoc); + $buildScope = "\n".implode('', $buildScope)."\n"; + $buildScopeVars = "\n".implode('', $buildScopeVars)."\n"; + + } else if ($this->useScope()){ + $methodSignature = '$scope'; + if ($column = $this->table->getColumn($this->getParameter('scope_column'))) { + if (!$column->isNotNull()) { + $methodSignature .= ' = null'; } + $paramsDoc .= ' * @param '.$column->getPhpType().' $scope Scope to determine which objects node to return'; } } + + return array($methodSignature, $paramsDoc, $buildScope, $buildScopeVars); + } + + /** + * Returns the getter method name. + * + * @param string $name + * @return string + */ + public function getColumnGetter($name) + { + return 'get' . $this->getTable()->getColumn($name)->getPhpName(); + } + + /** + * Returns the setter method name. + * + * @param string $name + * @return string + */ + public function getColumnSetter($name) + { + return 'set' . $this->getTable()->getColumn($name)->getPhpName(); + } + + /** + * {@inheritdoc} + */ + public function addParameter($attribute) + { + if ('scope_column' === $attribute['name']) { + $this->parameters['scope_column'] .= ($this->parameters['scope_column'] ? ',' : '') . $attribute['value']; + } else { + parent::addParameter($attribute); + } + } + + /** + * Returns all scope columns as array. + * + * @return string[] + */ + public function getScopes() + { + return $this->getParameter('scope_column') + ? explode(',', str_replace(' ', '', trim($this->getParameter('scope_column')))) + : array(); + } + + /** + * Returns true if the behavior has multiple scope columns. + * + * @return bool + */ + public function hasMultipleScopes() + { + return count($this->getScopes()) > 1; } public function getObjectBuilderModifier() @@ -99,4 +218,5 @@ public function useScope() { return $this->getParameter('use_scope') == 'true'; } + } diff --git a/generator/lib/behavior/sortable/SortableBehaviorObjectBuilderModifier.php b/generator/lib/behavior/sortable/SortableBehaviorObjectBuilderModifier.php index 9a4b52968..09dfba238 100644 --- a/generator/lib/behavior/sortable/SortableBehaviorObjectBuilderModifier.php +++ b/generator/lib/behavior/sortable/SortableBehaviorObjectBuilderModifier.php @@ -18,9 +18,32 @@ */ class SortableBehaviorObjectBuilderModifier { - protected $behavior, $table, $builder, $objectClassname, $peerClassname; + /** + * @var SortableBehavior + */ + protected $behavior; + + /** + * @var Table + */ + protected $table; + + /** + * @var OMBuilder + */ + protected $builder; + + /** + * @var String + */ + protected $objectClassname; - public function __construct(SortableBehavior $behavior) + /** + * @var String + */ + protected $peerClassname; + + public function __construct($behavior) { $this->behavior = $behavior; $this->table = $behavior->getTable(); @@ -41,7 +64,7 @@ protected function getColumnPhpName($name) return $this->behavior->getColumnForParameter($name)->getPhpName(); } - protected function setBuilder(PHP5ObjectBuilder $builder) + protected function setBuilder($builder) { $this->builder = $builder; $this->objectClassname = $builder->getStubObjectBuilder()->getClassname(); @@ -52,8 +75,6 @@ protected function setBuilder(PHP5ObjectBuilder $builder) /** * Get the getter of the column of the behavior * - * @param string $columnName - * * @return string The related getter, e.g. 'getRank' */ protected function getColumnGetter($columnName = 'rank_column') @@ -64,8 +85,6 @@ protected function getColumnGetter($columnName = 'rank_column') /** * Get the setter of the column of the behavior * - * @param string $columnName - * * @return string The related setter, e.g. 'setRank' */ protected function getColumnSetter($columnName = 'rank_column') @@ -84,7 +103,7 @@ public function preInsert($builder) $this->setBuilder($builder); return "if (!\$this->isColumnModified({$this->peerClassname}::RANK_COL)) { - \$this->{$this->getColumnSetter()}({$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con) + 1); + \$this->{$this->getColumnSetter()}({$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con) + 1); } "; } @@ -94,12 +113,23 @@ public function preUpdate($builder) if ($this->behavior->useScope()) { $this->setBuilder($builder); - return "// if scope has changed and rank was not modified (if yes, assuming superior action) + $condition = array(); + + foreach ($this->behavior->getScopes() as $scope) { + $condition[] = "\$this->isColumnModified({$this->peerClassname}::".strtoupper($scope).")"; + } + + $condition = implode(' OR ', $condition); + + $script = "// if scope has changed and rank was not modified (if yes, assuming superior action) // insert object to the end of new scope and cleanup old one -if (\$this->isColumnModified({$this->peerClassname}::SCOPE_COL) && !\$this->isColumnModified({$this->peerClassname}::RANK_COL)) { {$this->peerClassname}::shiftRank(-1, \$this->{$this->getColumnGetter()}() + 1, null, \$this->oldScope, \$con); +if (($condition) && !\$this->isColumnModified({$this->peerClassname}::RANK_COL)) { + {$this->peerClassname}::shiftRank(-1, \$this->{$this->getColumnGetter()}() + 1, null, \$this->oldScope, \$con); \$this->insertAtBottom(\$con); } "; + + return $script; } } @@ -109,7 +139,7 @@ public function preDelete($builder) $this->setBuilder($builder); return " -{$this->peerClassname}::shiftRank(-1, \$this->{$this->getColumnGetter()}() + 1, null, " . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con); +{$this->peerClassname}::shiftRank(-1, \$this->{$this->getColumnGetter()}() + 1, null, " . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con); {$this->peerClassname}::clearInstancePool(); "; } @@ -123,8 +153,8 @@ public function objectAttributes($builder) */ protected \$sortableQueries = array(); "; - if ($this->behavior->useScope()) { - $script .= " + if ($this->behavior->useScope()) { + $script .= " /** * The old scope value. * @var int @@ -143,7 +173,8 @@ public function objectMethods($builder) if ($this->getParameter('rank_column') != 'rank') { $this->addRankAccessors($script); } - if ($this->behavior->useScope() && $this->getParameter('scope_column') != 'scope_value') { + if ($this->behavior->useScope() && + $this->getParameter('scope_column') != 'scope_value') { $this->addScopeAccessors($script); } $this->addIsFirst($script); @@ -168,13 +199,30 @@ public function objectMethods($builder) public function objectFilter(&$script, $builder) { if ($this->behavior->useScope()) { - $methodName = $this->getColumnSetter('scope_column'); - $search = "if (\$this->{$this->getColumnAttribute('scope_column')} !== \$v) {"; - $replace = $search . " + if ($this->behavior->hasMultipleScopes()) { + + foreach ($this->behavior->getScopes() as $idx => $scope) { + $name = strtolower($this->behavior->getTable()->getColumn($scope)->getName()); + + $search = "if (\$this->$name !== \$v) {"; + $replace = $search . " // sortable behavior - \$this->oldScope = \$this->{$this->getColumnGetter('scope_column')}(); + \$this->oldScope[$idx] = \$this->$name; "; - $script = str_replace($search, $replace, $script); + $script = str_replace($search, $replace, $script); + } + + } else { + $scope = current($this->behavior->getScopes()); + $name = strtolower($this->behavior->getTable()->getColumn($scope)->getName()); + + $search = "if (\$this->$name !== \$v) {"; + $replace = $search . " + // sortable behavior + \$this->oldScope = \$this->$name; +"; + $script = str_replace($search, $replace, $script); + } } } @@ -216,27 +264,72 @@ public function setRank(\$v) */ protected function addScopeAccessors(&$script) { + $script .= " /** * Wrap the getter for scope value * - * @return int + * @param boolean \$returnNulls If true and all scope values are null, this will return null instead of a array full with nulls + * + * @return mixed A array or a native type */ -public function getScopeValue() +public function getScopeValue(\$returnNulls = true) { - return \$this->{$this->getColumnAttribute('scope_column')}; +"; + if ($this->behavior->hasMultipleScopes()) { +$script .= " + \$result = array(); + \$onlyNulls = true; +"; + foreach ($this->behavior->getScopes() as $scopeField) { +$script .= " + \$onlyNulls &= null === (\$result[] = \$this->{$this->behavior->getColumnGetter($scopeField)}()); +"; + + } + +$script .= " + + return \$onlyNulls && \$returnNulls ? null : \$result; +"; + } else { + +$script .= " + + return \$this->{$this->getColumnGetter('scope_column')}(); +"; + } + +$script .= " } /** * Wrap the setter for scope value * - * @param int + * @param mixed A array or a native type * @return {$this->objectClassname} */ public function setScopeValue(\$v) { +"; + + if ($this->behavior->hasMultipleScopes()) { + + foreach ($this->behavior->getScopes() as $idx => $scopeField) { +$script .= " + \$this->{$this->behavior->getColumnSetter($scopeField)}(\$v === null ? null : \$v[$idx]); +"; + } + + } else { +$script .= " + return \$this->{$this->getColumnSetter('scope_column')}(\$v); +"; + + } +$script .= " } "; } @@ -269,7 +362,7 @@ protected function addIsLast(&$script) */ public function isLast(PropelPDO \$con = null) { - return \$this->{$this->getColumnGetter()}() == {$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con); + return \$this->{$this->getColumnGetter()}() == {$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con); } "; } @@ -277,6 +370,8 @@ public function isLast(PropelPDO \$con = null) protected function addGetNext(&$script) { $useScope = $this->behavior->useScope(); + list($methodSignature, $paramsDoc, $buildScope, $buildScopeVars) = $this->behavior->generateScopePhp(); + $script .= " /** * Get the next item in the list, i.e. the one for which rank is immediately higher @@ -287,20 +382,28 @@ protected function addGetNext(&$script) */ public function getNext(PropelPDO \$con = null) {"; - if ($this->behavior->getParameter('rank_column') == 'rank' && $useScope) { - $script .= " + $script .= " + + \$query = {$this->queryClassname}::create(); +"; + + if ($useScope) { + $methodSignature = str_replace(' = null', '', $methodSignature); - return {$this->queryClassname}::create() - ->filterByRank(\$this->{$this->getColumnGetter()}() + 1) - ->inList(\$this->{$this->getColumnGetter('scope_column')}()) - ->findOne(\$con);"; - } else { $script .= " + \$scope = \$this->getScopeValue(); + $buildScopeVars + \$query->filterByRank(\$this->{$this->getColumnGetter()}() + 1, $methodSignature); +"; + } else { - return {$this->queryClassname}::create()->findOneByRank(\$this->{$this->getColumnGetter()}() + 1, " . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con);"; + $script .= " + \$query->filterByRank(\$this->{$this->getColumnGetter()}() + 1); +"; } $script .= " + return \$query->findOne(\$con); } "; } @@ -308,6 +411,9 @@ public function getNext(PropelPDO \$con = null) protected function addGetPrevious(&$script) { $useScope = $this->behavior->useScope(); + + list($methodSignature, $paramsDoc, $buildScope, $buildScopeVars) = $this->behavior->generateScopePhp(); + $script .= " /** * Get the previous item in the list, i.e. the one for which rank is immediately lower @@ -318,19 +424,28 @@ protected function addGetPrevious(&$script) */ public function getPrevious(PropelPDO \$con = null) {"; - if ($this->behavior->getParameter('rank_column') == 'rank' && $useScope) { - $script .= " + $script .= " + + \$query = {$this->queryClassname}::create(); +"; + + if ($useScope) { + $methodSignature = str_replace(' = null', '', $methodSignature); - return {$this->queryClassname}::create() - ->filterByRank(\$this->{$this->getColumnGetter()}() - 1) - ->inList(\$this->{$this->getColumnGetter('scope_column')}()) - ->findOne(\$con);"; - } else { $script .= " + \$scope = \$this->getScopeValue(); + $buildScopeVars + \$query->filterByRank(\$this->{$this->getColumnGetter()}() - 1, $methodSignature); +"; + } else { - return {$this->queryClassname}::create()->findOneByRank(\$this->{$this->getColumnGetter()}() - 1, " . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con);"; + $script .= " + \$query->filterByRank(\$this->{$this->getColumnGetter()}() - 1); +"; } + $script .= " + return \$query->findOne(\$con); } "; } @@ -353,7 +468,7 @@ protected function addInsertAtRank(&$script) public function insertAtRank(\$rank, PropelPDO \$con = null) {"; $script .= " - \$maxRank = {$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con); + \$maxRank = {$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con); if (\$rank < 1 || \$rank > \$maxRank + 1) { throw new PropelException('Invalid rank ' . \$rank); } @@ -363,7 +478,7 @@ public function insertAtRank(\$rank, PropelPDO \$con = null) // Keep the list modification query for the save() transaction \$this->sortableQueries []= array( 'callable' => array(self::PEER, 'shiftRank'), - 'arguments' => array(1, \$rank, null, " . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}()" : '') . ") + 'arguments' => array(1, \$rank, null, " . ($useScope ? "\$this->getScopeValue()" : '') . ") ); } @@ -389,7 +504,7 @@ protected function addInsertAtBottom(&$script) public function insertAtBottom(PropelPDO \$con = null) {"; $script .= " - \$this->{$this->getColumnSetter()}({$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con) + 1); + \$this->{$this->getColumnSetter()}({$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con) + 1); return \$this; } @@ -436,7 +551,7 @@ public function moveToRank(\$newRank, PropelPDO \$con = null) if (\$con === null) { \$con = Propel::getConnection($peerClassname::DATABASE_NAME); } - if (\$newRank < 1 || \$newRank > {$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con)) { + if (\$newRank < 1 || \$newRank > {$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con)) { throw new PropelException('Invalid rank ' . \$newRank); } @@ -449,7 +564,7 @@ public function moveToRank(\$newRank, PropelPDO \$con = null) try { // shift the objects between the old and the new rank \$delta = (\$oldRank < \$newRank) ? -1 : 1; - $peerClassname::shiftRank(\$delta, min(\$oldRank, \$newRank), max(\$oldRank, \$newRank), " . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con); + $peerClassname::shiftRank(\$delta, min(\$oldRank, \$newRank), max(\$oldRank, \$newRank), " . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con); // move the object to its new rank \$this->{$this->getColumnSetter()}(\$newRank); @@ -488,14 +603,14 @@ public function swapWith(\$object, PropelPDO \$con = null) try {"; if ($this->behavior->useScope()) { $script .= " - \$oldScope = \$this->{$this->getColumnGetter('scope_column')}(); - \$newScope = \$object->{$this->getColumnGetter('scope_column')}(); + \$oldScope = \$this->getScopeValue(); + \$newScope = \$object->getScopeValue(); if (\$oldScope != \$newScope) { - \$this->{$this->getColumnSetter('scope_column')}(\$newScope); - \$object->{$this->getColumnSetter('scope_column')}(\$oldScope); + \$this->setScopeValue(\$newScope); + \$object->setScopeValue(\$oldScope); }"; } - $script .= " +$script .= " \$oldRank = \$this->{$this->getColumnGetter()}(); \$newRank = \$object->{$this->getColumnGetter()}(); \$this->{$this->getColumnSetter()}(\$newRank); @@ -621,7 +736,7 @@ public function moveToBottom(PropelPDO \$con = null) } \$con->beginTransaction(); try { - \$bottom = {$this->queryClassname}::create()->getMaxRank(" . ($useScope ? "\$this->{$this->getColumnGetter('scope_column')}(), " : '') . "\$con); + \$bottom = {$this->queryClassname}::create()->getMaxRankArray(" . ($useScope ? "\$this->getScopeValue(), " : '') . "\$con); \$res = \$this->moveToRank(\$bottom, \$con); \$con->commit(); @@ -639,7 +754,7 @@ protected function addRemoveFromList(&$script) $useScope = $this->behavior->useScope(); $script .= " /** - * Removes the current object from the list" . ($useScope ? ' (moves it to the null scope)' : '') . ". + * Removes the current object from the list".($useScope ? ' (moves it to the null scope)' : '').". * The modifications are not persisted until the object is saved. * * @param PropelPDO \$con optional connection @@ -649,21 +764,21 @@ protected function addRemoveFromList(&$script) public function removeFromList(PropelPDO \$con = null) {"; if ($useScope) { - $script .= " + $script .= " // check if object is already removed - if (\$this->{$this->getColumnGetter('scope_column')}() === null) { + if (\$this->getScopeValue() === null) { throw new PropelException('Object is already removed (has null scope)'); } // move the object to the end of null scope - \$this->{$this->getColumnSetter('scope_column')}(null); + \$this->setScopeValue(null); // \$this->insertAtBottom(\$con);"; } else { - $script .= " + $script .= " // Keep the list modification query for the save() transaction \$this->sortableQueries []= array( 'callable' => array(self::PEER, 'shiftRank'), - 'arguments' => array(-1, \$this->{$this->getColumnGetter()}() + 1, null" . ($useScope ? ", \$this->{$this->getColumnGetter('scope_column')}()" : '') . ") + 'arguments' => array(-1, \$this->{$this->getColumnGetter()}() + 1, null" . ($useScope ? ", \$this->getScopeValue()" : '') . ") ); // remove the object from the list \$this->{$this->getColumnSetter('rank_column')}(null);"; diff --git a/generator/lib/behavior/sortable/SortableBehaviorPeerBuilderModifier.php b/generator/lib/behavior/sortable/SortableBehaviorPeerBuilderModifier.php index 38a4c8dd8..fcbb3caa6 100644 --- a/generator/lib/behavior/sortable/SortableBehaviorPeerBuilderModifier.php +++ b/generator/lib/behavior/sortable/SortableBehaviorPeerBuilderModifier.php @@ -18,7 +18,30 @@ */ class SortableBehaviorPeerBuilderModifier { - protected $behavior, $table, $builder, $objectClassname, $peerClassname; + /** + * @var SortableBehavior + */ + protected $behavior; + + /** + * @var Table + */ + protected $table; + + /** + * @var OMBuilder + */ + protected $builder; + + /** + * @var String + */ + protected $objectClassname; + + /** + * @var String + */ + protected $peerClassname; public function __construct($behavior) { @@ -50,8 +73,8 @@ protected function setBuilder($builder) { $this->builder = $builder; $this->objectClassname = $builder->getStubObjectBuilder()->getClassname(); - $this->peerClassname = $builder->getStubPeerBuilder()->getClassname(); - $this->queryClassname = $builder->getStubQueryBuilder()->getClassname(); + $this->peerClassname = $builder->getStubPeerBuilder()->getClassname(); + $this->queryClassname = $builder->getStubQueryBuilder()->getClassname(); $builder->declareClassFromBuilder($builder->getStubObjectBuilder()); $builder->declareClassFromBuilder($builder->getStubQueryBuilder()); @@ -68,14 +91,36 @@ public function staticAttributes($builder) "; if ($this->behavior->useScope()) { - $script .= " + + if ($this->behavior->hasMultipleScopes()) { + foreach ($this->behavior->getScopes() as $scope) { + $col[] = "$tableName.".strtoupper($scope); + } + $col = json_encode($col); + $col = "'$col'"; + + $script .= " +/** + * If defined, the `SCOPE_COL` contains a json_encoded array with all columns. + * @var boolean + */ +const MULTI_SCOPE_COL = true; +"; + + } else { + $colNames = $this->getColumnConstant('scope_column'); + + $col = "'$tableName.$colNames'"; + } + + $script .= " /** * Scope column for the set */ -const SCOPE_COL = '" . $tableName . '.' . $this->getColumnConstant('scope_column') . "'; +const SCOPE_COL = $col; "; - } + } return $script; } @@ -97,12 +142,45 @@ public function staticMethods($builder) $this->addRetrieveList($script); $this->addCountList($script); $this->addDeleteList($script); + $this->addSortableApplyScopeCriteria($script); } $this->addShiftRank($script); return $script; } + public function addSortableApplyScopeCriteria(&$script) + { + $script .= " +/** + * Applies all scope fields to the given criteria. + * + * @param Criteria \$criteria Applies the values directly to this criteria. + * @param mixed \$scope The scope value as scalar type or array(\$value1, ...). + * @param string \$method The method we use to apply the values. + * + */ +public static function sortableApplyScopeCriteria(Criteria \$criteria, \$scope, \$method = 'add') +{ +"; + if ($this->behavior->hasMultipleScopes()) { + foreach ($this->behavior->getScopes() as $idx => $scope) { + $script .= " + \$criteria->\$method({$this->peerClassname}::".strtoupper($scope).", \$scope[$idx], Criteria::EQUAL); +"; + } + } else { + $script .= " + \$criteria->\$method({$this->peerClassname}::".strtoupper(current($this->behavior->getScopes())).", \$scope, Criteria::EQUAL); +"; + } + + $script .= " +} +"; + + } + protected function addGetMaxRank(&$script) { $useScope = $this->behavior->useScope(); @@ -128,8 +206,8 @@ public static function getMaxRank(" . ($useScope ? "\$scope = null, " : "") . "P \$c = new Criteria(); \$c->addSelectColumn('MAX(' . {$this->peerClassname}::RANK_COL . ')');"; if ($useScope) { - $script .= " - \$c->add({$this->peerClassname}::SCOPE_COL, \$scope, Criteria::EQUAL);"; + $script .= " + {$this->peerClassname}::sortableApplyScopeCriteria(\$c, \$scope);"; } $script .= " \$stmt = {$this->peerClassname}::doSelectStmt(\$c, \$con); @@ -167,7 +245,7 @@ public static function retrieveByRank(\$rank, " . ($useScope ? "\$scope = null, \$c->add($peerClassname::RANK_COL, \$rank);"; if ($useScope) { $script .= " - \$c->add($peerClassname::SCOPE_COL, \$scope, Criteria::EQUAL);"; + {$this->peerClassname}::sortableApplyScopeCriteria(\$c, \$scope);"; } $script .= " @@ -265,7 +343,7 @@ protected function addRetrieveList(&$script) /** * Return an array of sortable objects in the given scope ordered by position * - * @param int \$scope the scope of the list + * @param mixed \$scope the scope of the list * @param string \$order sorting order, to be chosen between Criteria::ASC (default) and Criteria::DESC * @param PropelPDO \$con optional connection * @@ -274,7 +352,7 @@ protected function addRetrieveList(&$script) public static function retrieveList(\$scope, \$order = Criteria::ASC, PropelPDO \$con = null) { \$c = new Criteria(); - \$c->add($peerClassname::SCOPE_COL, \$scope); + {$this->peerClassname}::sortableApplyScopeCriteria(\$c, \$scope); return $peerClassname::doSelectOrderByRank(\$c, \$order, \$con); } @@ -288,7 +366,7 @@ protected function addCountList(&$script) /** * Return the number of sortable objects in the given scope * - * @param int \$scope the scope of the list + * @param mixed \$scope the scope of the list * @param PropelPDO \$con optional connection * * @return array list of sortable objects @@ -296,7 +374,7 @@ protected function addCountList(&$script) public static function countList(\$scope, PropelPDO \$con = null) { \$c = new Criteria(); - \$c->add($peerClassname::SCOPE_COL, \$scope); + {$this->peerClassname}::sortableApplyScopeCriteria(\$c, \$scope); return $peerClassname::doCount(\$c, \$con); } @@ -310,7 +388,7 @@ protected function addDeleteList(&$script) /** * Deletes the sortable objects in the given scope * - * @param int \$scope the scope of the list + * @param mixed \$scope the scope of the list * @param PropelPDO \$con optional connection * * @return int number of deleted objects @@ -318,13 +396,12 @@ protected function addDeleteList(&$script) public static function deleteList(\$scope, PropelPDO \$con = null) { \$c = new Criteria(); - \$c->add($peerClassname::SCOPE_COL, \$scope); + {$this->peerClassname}::sortableApplyScopeCriteria(\$c, \$scope); return $peerClassname::doDelete(\$c, \$con); } "; } - protected function addShiftRank(&$script) { $useScope = $this->behavior->useScope(); @@ -339,7 +416,7 @@ protected function addShiftRank(&$script) * @param int \$last Last node to be shifted"; if ($useScope) { $script .= " - * @param int \$scope Scope to use for the shift"; + * @param mixed \$scope Scope to use for the shift. Scalar value (single scope) or array"; } $script .= " * @param PropelPDO \$con Connection to use. @@ -359,7 +436,7 @@ public static function shiftRank(\$delta, \$first = null, \$last = null, " . ($u }"; if ($useScope) { $script .= " - \$whereCriteria->add($peerClassname::SCOPE_COL, \$scope, Criteria::EQUAL);"; + {$this->peerClassname}::sortableApplyScopeCriteria(\$whereCriteria, \$scope);"; } $script .= " diff --git a/generator/lib/behavior/sortable/SortableBehaviorQueryBuilderModifier.php b/generator/lib/behavior/sortable/SortableBehaviorQueryBuilderModifier.php index 22b24276a..663ee4f8b 100644 --- a/generator/lib/behavior/sortable/SortableBehaviorQueryBuilderModifier.php +++ b/generator/lib/behavior/sortable/SortableBehaviorQueryBuilderModifier.php @@ -16,7 +16,30 @@ */ class SortableBehaviorQueryBuilderModifier { - protected $behavior, $table, $builder, $objectClassname, $peerClassname; + /** + * @var SortableBehavior + */ + protected $behavior; + + /** + * @var Table + */ + protected $table; + + /** + * @var OMBuilder + */ + protected $builder; + + /** + * @var String + */ + protected $objectClassname; + + /** + * @var String + */ + protected $peerClassname; public function __construct($behavior) { @@ -64,6 +87,7 @@ public function queryMethods($builder) // utilities $this->addGetMaxRank($script); + $this->addGetMaxRankArray($script); $this->addReorder($script); return $script; @@ -71,17 +95,22 @@ public function queryMethods($builder) protected function addInList(&$script) { + list($methodSignature, $paramsDoc, $buildScope) = $this->behavior->generateScopePhp(); + $script .= " /** * Returns the objects in a certain list, from the list scope * - * @param int \$scope Scope to determine which objects node to return +$paramsDoc * - * @return {$this->queryClassname} The current query, for fluid interface + * @return {$this->queryClassname} The current query, for fluid interface */ -public function inList(\$scope = null) +public function inList($methodSignature) { - return \$this->addUsingAlias({$this->peerClassname}::SCOPE_COL, \$scope, Criteria::EQUAL); + $buildScope + {$this->peerClassname}::sortableApplyScopeCriteria(\$this, \$scope, 'addUsingAlias'); + + return \$this; } "; } @@ -90,6 +119,11 @@ protected function addFilterByRank(&$script) { $useScope = $this->behavior->useScope(); $peerClassname = $this->peerClassname; + + if ($useScope) { + list($methodSignature, $paramsDoc, $buildScope) = $this->behavior->generateScopePhp(); + } + $script .= " /** * Filter the query based on a rank in the list @@ -97,18 +131,25 @@ protected function addFilterByRank(&$script) * @param integer \$rank rank"; if ($useScope) { $script .= " - * @param int \$scope Scope to determine which suite to consider"; +$paramsDoc +"; } $script .= " * * @return " . $this->queryClassname . " The current query, for fluid interface */ -public function filterByRank(\$rank" . ($useScope ? ", \$scope = null" : "") . ") +public function filterByRank(\$rank" . ($useScope ? ", $methodSignature" : "") . ") { +"; + if ($useScope) { + $methodSignature = str_replace(' = null', '', $methodSignature); + } + + $script .= " return \$this"; if ($useScope) { $script .= " - ->inList(\$scope)"; + ->inList($methodSignature)"; } $script .= " ->addUsingAlias($peerClassname::RANK_COL, \$rank, Criteria::EQUAL); @@ -147,7 +188,11 @@ public function orderByRank(\$order = Criteria::ASC) protected function addFindOneByRank(&$script) { $useScope = $this->behavior->useScope(); - $peerClassname = $this->peerClassname; + + if ($useScope) { + list($methodSignature, $paramsDoc, $buildScope) = $this->behavior->generateScopePhp(); + } + $script .= " /** * Get an item from the list based on its rank @@ -155,17 +200,23 @@ protected function addFindOneByRank(&$script) * @param integer \$rank rank"; if ($useScope) { $script .= " - * @param int \$scope Scope to determine which suite to consider"; +$paramsDoc"; } $script .= " * @param PropelPDO \$con optional connection * * @return {$this->objectClassname} */ -public function findOneByRank(\$rank, " . ($useScope ? "\$scope = null, " : "") . "PropelPDO \$con = null) -{ +public function findOneByRank(\$rank, " . ($useScope ? "$methodSignature, " : "") . "PropelPDO \$con = null) +{"; + + if ($useScope) { + $methodSignature = str_replace(' = null', '', $methodSignature); + } + + $script .= " return \$this - ->filterByRank(\$rank" . ($useScope ? ", \$scope" : "") . ") + ->filterByRank(\$rank" . ($useScope ? ", $methodSignature" : "") . ") ->findOne(\$con); } "; @@ -174,25 +225,38 @@ public function findOneByRank(\$rank, " . ($useScope ? "\$scope = null, " : "") protected function addFindList(&$script) { $useScope = $this->behavior->useScope(); + + if ($useScope) { + list($methodSignature, $paramsDoc, $buildScope) = $this->behavior->generateScopePhp(); + } + $script .= " /** - * Returns " . ($useScope ? 'a' : 'the') . " list of objects + * Returns " . ($useScope ? 'a' : 'the') ." list of objects *"; - if ($useScope) { - $script .= " - * @param int \$scope Scope to determine which list to return"; - } + if ($useScope) { + $script .= " +$paramsDoc +"; + } $script .= " * @param PropelPDO \$con Connection to use. * * @return mixed the list of results, formatted by the current formatter */ -public function findList(" . ($useScope ? "\$scope = null, " : "") . "\$con = null) +public function findList(" . ($useScope ? "$methodSignature, " : "") . "\$con = null) { +"; + + if ($useScope) { + $methodSignature = str_replace(' = null', '', $methodSignature); + } + + $script .= " return \$this"; if ($useScope) { $script .= " - ->inList(\$scope)"; + ->inList($methodSignature)"; } $script .= " ->orderByRank() @@ -205,29 +269,74 @@ protected function addGetMaxRank(&$script) { $this->builder->declareClasses('Propel'); $useScope = $this->behavior->useScope(); + + if ($useScope) { + list($methodSignature, $paramsDoc, $buildScope) = $this->behavior->generateScopePhp(); + } + $script .= " /** * Get the highest rank * "; if ($useScope) { $script .= " - * @param int \$scope Scope to determine which suite to consider"; +$paramsDoc +"; } $script .= " * @param PropelPDO optional connection * * @return integer highest position */ -public function getMaxRank(" . ($useScope ? "\$scope = null, " : "") . "PropelPDO \$con = null) +public function getMaxRank(" . ($useScope ? "$methodSignature, " : "") . "PropelPDO \$con = null) { if (\$con === null) { \$con = Propel::getConnection({$this->peerClassname}::DATABASE_NAME); } // shift the objects with a position lower than the one of object \$this->addSelectColumn('MAX(' . {$this->peerClassname}::RANK_COL . ')');"; + if ($useScope) { + $script .= " + $buildScope + {$this->peerClassname}::sortableApplyScopeCriteria(\$this, \$scope);"; + } + $script .= " + \$stmt = \$this->doSelect(\$con); + + return \$stmt->fetchColumn(); +} +"; + } + + protected function addGetMaxRankArray(&$script) + { + $this->builder->declareClasses('Propel'); + $useScope = $this->behavior->useScope(); + + $script .= " +/** + * Get the highest rank by a scope with a array format. + * "; if ($useScope) { $script .= " - \$this->add({$this->peerClassname}::SCOPE_COL, \$scope, Criteria::EQUAL);"; + * @param int \$scope The scope value as scalar type or array(\$value1, ...). +"; + } + $script .= " + * @param PropelPDO optional connection + * + * @return integer highest position + */ +public function getMaxRankArray(" . ($useScope ? "\$scope, " : "") . "PropelPDO \$con = null) +{ + if (\$con === null) { + \$con = Propel::getConnection({$this->peerClassname}::DATABASE_NAME); + } + // shift the objects with a position lower than the one of object + \$this->addSelectColumn('MAX(' . {$this->peerClassname}::RANK_COL . ')');"; + if ($useScope) { + $script .= " + {$this->peerClassname}::sortableApplyScopeCriteria(\$this, \$scope);"; } $script .= " \$stmt = \$this->doSelect(\$con); @@ -281,4 +390,5 @@ public function reorder(array \$order, PropelPDO \$con = null) } "; } + } diff --git a/test/fixtures/bookstore/behavior-sortable-schema.xml b/test/fixtures/bookstore/behavior-sortable-schema.xml index f7698d1db..42a78e779 100644 --- a/test/fixtures/bookstore/behavior-sortable-schema.xml +++ b/test/fixtures/bookstore/behavior-sortable-schema.xml @@ -18,13 +18,40 @@ + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ - - - + + + diff --git a/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php b/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php index 01dfb9c89..c43383654 100644 --- a/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php +++ b/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php @@ -275,5 +275,4 @@ public function testRemoveFromList() $expected = array(null => 'row2', 1 => 'row1', 2 => 'row3', 3 => 'row4'); $this->assertEquals($expected, $this->getFixturesArray(), 'removeFromList() changes the list once the object is saved'); } - } diff --git a/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php b/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php index 8d05a73d9..2fcaef7ee 100644 --- a/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php +++ b/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php @@ -423,4 +423,189 @@ public function testRemoveFromListNoScope() $t2->removeFromList(); } + /** + * @return SortableMultiScopes[] + */ + private function generateMultipleScopeEntries() + { + SortableMultiScopesPeer::doDeleteAll(); + + $items = array( + // cat scat title + array( 1, 1, 'item 1'), //1 + array( 2, 1, 'item 2'), //1 + array( 3, 1, 'item 3'), //1 + array( 3, 1, 'item 3.1'),//2 + array( 1, 1, 'item 1.1'),//2 + array( 1, 1, 'item 1.2'),//3 + array( 1, 2, 'item 1.3'),//1 + array( 1, 2, 'item 1.4'),//2 + ); + + $result = array(); + foreach ($items as $value) { + $item = new SortableMultiScopes(); + $item->setCategoryId($value[0]); + $item->setSubCategoryId($value[1]); + $item->setTitle($value[2]); + $item->save(); + $result[] = $item; + } + + return $result; + } + /** + * @return SortableMultiCommaScopes[] + */ + private function generateMultipleCommaScopeEntries() + { + SortableMultiCommaScopesPeer::doDeleteAll(); + + $items = array( + // cat scat title + array( 1, 1, 'item 1'), //1 + array( 2, 1, 'item 2'), //1 + array( 3, 1, 'item 3'), //1 + array( 3, 1, 'item 3.1'),//2 + array( 1, 1, 'item 1.1'),//2 + array( 1, 1, 'item 1.2'),//3 + array( 1, 2, 'item 1.3'),//1 + array( 1, 2, 'item 1.4'),//2 + ); + + $result = array(); + foreach ($items as $value) { + $item = new SortableMultiCommaScopes(); + $item->setCategoryId($value[0]); + $item->setSubCategoryId($value[1]); + $item->setTitle($value[2]); + $item->save(); + $result[] = $item; + } + + return $result; + } + + public function testMultipleScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t2->getRank(), 1); + + $this->assertEquals($t3->getRank(), 1); + $this->assertEquals($t3_1->getRank(), 2); + + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + $this->assertEquals($t1_3->getRank(), 1); + $this->assertEquals($t1_4->getRank(), 2); + + } + + public function testMoveMultipleScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->moveDown(); + $this->assertEquals($t1->getRank(), 2); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->moveDown(); + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + + $t1_1->moveUp(); //no changes + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + + $t1_2->moveUp(); //no changes + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 1); + } + + public function testDeleteMultipleScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->delete(); + + $t1_1->reload(); + $t1_2->reload(); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + } + + public function testMultipleCommaScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleCommaScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t2->getRank(), 1); + + $this->assertEquals($t3->getRank(), 1); + $this->assertEquals($t3_1->getRank(), 2); + + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + $this->assertEquals($t1_3->getRank(), 1); + $this->assertEquals($t1_4->getRank(), 2); + } + + public function testMoveMultipleCommaScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleCommaScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->moveDown(); + $this->assertEquals($t1->getRank(), 2); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->moveDown(); + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + + $t1_1->moveUp(); //no changes + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + + $t1_2->moveUp(); //no changes + $this->assertEquals($t1->getRank(), 3); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 1); + } + + public function testDeleteMultipleCommaScopes() + { + list($t1, $t2, $t3, $t3_1, $t1_1, $t1_2, $t1_3, $t1_4) = $this->generateMultipleCommaScopeEntries(); + + $this->assertEquals($t1->getRank(), 1); + $this->assertEquals($t1_1->getRank(), 2); + $this->assertEquals($t1_2->getRank(), 3); + + $t1->delete(); + + $t1_1->reload(); + $t1_2->reload(); + $this->assertEquals($t1_1->getRank(), 1); + $this->assertEquals($t1_2->getRank(), 2); + } } diff --git a/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php b/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php index 7ea2e60be..69a66a2fc 100644 --- a/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php +++ b/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php @@ -59,7 +59,7 @@ public function testRetrieveByRank() public function testReorder() { $c = new Criteria(); - $c->add(Table12Peer::SCOPE_COL, 1); + Table12Peer::sortableApplyScopeCriteria($c, 1); $objects = Table12Peer::doSelectOrderByRank($c); $ids = array(); foreach ($objects as $object) { @@ -77,7 +77,7 @@ public function testReorder() public function testDoSelectOrderByRank() { $c = new Criteria(); - $c->add(Table12Peer::SCOPE_COL, 1); + Table12Peer::sortableApplyScopeCriteria($c, 1); $objects = Table12Peer::doSelectOrderByRank($c); $oldRank = 0; while ($object = array_shift($objects)) { @@ -85,7 +85,7 @@ public function testDoSelectOrderByRank() $oldRank = $object->getRank(); } $c = new Criteria(); - $c->add(Table12Peer::SCOPE_COL, 1); + Table12Peer::sortableApplyScopeCriteria($c, 1); $objects = Table12Peer::doSelectOrderByRank($c, Criteria::DESC); $oldRank = 10; while ($object = array_shift($objects)) { diff --git a/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php b/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php index 602f7e00d..e2f3113ed 100644 --- a/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php +++ b/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php @@ -165,7 +165,7 @@ protected function getFixturesArray() protected function getFixturesArrayWithScope($scope = null) { $c = new Criteria(); - $c->add(Table12Peer::SCOPE_COL, $scope); + Table12Peer::sortableApplyScopeCriteria($c, $scope); $c->addAscendingOrderByColumn(Table12Peer::RANK_COL); $ts = Table12Peer::doSelect($c); $ret = array(); @@ -179,7 +179,7 @@ protected function getFixturesArrayWithScope($scope = null) protected function getFixturesArrayWithFkScope($scope = null) { $c = new Criteria(); - $c->add(FkScopeTablePeer::SCOPE_COL, $scope); + FkScopeTablePeer::sortableApplyScopeCriteria($c, $scope); $c->addAscendingOrderByColumn(FkScopeTablePeer::RANK_COL); $ts = FkScopeTablePeer::doSelect($c); $ret = array();