Skip to content

Commit

Permalink
Implement __clone() in Database\Query and remaining expressions
Browse files Browse the repository at this point in the history
Move __clone() into Database\Query and implement deep cloning on all the
inner parts. This solves shared state in clones. Also implement
__clone() on the other expression objects that contain mutable state and
might be cloned with queries.

Refs #7533
  • Loading branch information
markstory committed Oct 18, 2015
1 parent 4b7707d commit 426af95
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 3 deletions.
14 changes: 14 additions & 0 deletions src/Database/Expression/BetweenExpression.php
Expand Up @@ -120,4 +120,18 @@ protected function _bindValue($value, $generator, $type)
$generator->bind($placeholder, $value, $type);
return $placeholder;
}

/**
* Do a deep clone of this expression.
*
* @return void
*/
public function __clone()
{
foreach (['_field', '_from', '_to'] as $part) {
if ($this->{$part} instanceof ExpressionInterface) {
$this->{$part} = clone $this->{$part};
}
}
}
}
16 changes: 16 additions & 0 deletions src/Database/Expression/Comparison.php
Expand Up @@ -152,6 +152,22 @@ public function traverse(callable $callable)
}
}

/**
* Create a deep clone.
*
* Clones the field and value if they are expression objects.
*
* @return void
*/
public function __clone()
{
foreach (['_value', '_field'] as $prop) {
if ($prop instanceof ExpressionInterface) {
$this->{$prop} = clone $this->{$prop};
}
}
}

/**
* Returns a template and a placeholder for the value after registering it
* with the placeholder $generator
Expand Down
12 changes: 12 additions & 0 deletions src/Database/Expression/OrderClauseExpression.php
Expand Up @@ -67,4 +67,16 @@ public function traverse(callable $visitor)
$this->_field->traverse($visitor);
}
}

/**
* Create a deep clone of the order clause.
*
* @return void
*/
public function __clone()
{
if ($this->_field instanceof ExpressionInterface) {
$this->_field = clone $this->_field;
}
}
}
12 changes: 12 additions & 0 deletions src/Database/Expression/UnaryExpression.php
Expand Up @@ -102,4 +102,16 @@ public function traverse(callable $callable)
$callable($this->_value);
}
}

/**
* Perform a deep clone of the inner expression.
*
* @return void
*/
public function __clone()
{
if ($this->_value instanceof ExpressionInterface) {
$this->_value = clone $this->_value;
}
}
}
31 changes: 31 additions & 0 deletions src/Database/Query.php
Expand Up @@ -1739,6 +1739,37 @@ protected function _dirty()
}
}

/**
* Do a deep clone on this object.
*
* Will clone all of the expression objects used in
* each of the clauses, as well as the valueBinder.
*
* @return void
*/
public function __clone()
{
$this->_iterator = null;
if ($this->_valueBinder) {
$this->_valueBinder = clone $this->_valueBinder;
}
foreach ($this->_parts as $name => $part) {
if (empty($part)) {
continue;
}
if (is_array($part)) {
foreach ($part as $i => $piece) {
if ($piece instanceof ExpressionInterface) {
$this->_parts[$name][$i] = clone $piece;
}
}
}
if ($part instanceof ExpressionInterface) {
$this->_parts[$name] = clone $part;
}
}
}

/**
* Returns string representation of this query (complete SQL statement).
*
Expand Down
7 changes: 4 additions & 3 deletions src/ORM/Query.php
Expand Up @@ -675,9 +675,10 @@ public function cleanCopy()
*/
public function __clone()
{
$this->_iterator = null;
$this->eagerLoader(clone $this->eagerLoader());
$this->valueBinder(clone $this->valueBinder());
parent::__clone();
if ($this->_eagerLoader) {
$this->_eagerLoader = clone $this->_eagerLoader;
}
}

/**
Expand Down
32 changes: 32 additions & 0 deletions tests/TestCase/Database/QueryTest.php
Expand Up @@ -3437,6 +3437,38 @@ public function testUnbufferedQuery()
$result->closeCursor();
}

/**
* Test that cloning goes deep.
*
* @return void
*/
public function testDeepClone()
{
$query = new Query($this->connection);
$query->select(['id', 'title' => $query->func()->concat(['title' => 'literal', 'test'])])
->from('articles')
->where(['Articles.id' => 1])
->offset(10)
->limit(1)
->order(['Articles.id' => 'DESC']);
$dupe = clone $query;

$this->assertEquals($query->clause('where'), $dupe->clause('where'));
$this->assertNotSame($query->clause('where'), $dupe->clause('where'));
$dupe->where(['Articles.title' => 'thinger']);
$this->assertNotEquals($query->clause('where'), $dupe->clause('where'));

$this->assertNotSame(
$query->clause('select')['title'],
$dupe->clause('select')['title']
);
$this->assertEquals($query->clause('order'), $dupe->clause('order'));
$this->assertNotSame($query->clause('order'), $dupe->clause('order'));

$query->order(['Articles.title' => 'ASC']);
$this->assertNotEquals($query->clause('order'), $dupe->clause('order'));
}

/**
* Assertion for comparing a table's contents with what is in it.
*
Expand Down

0 comments on commit 426af95

Please sign in to comment.