Commit 9b8ffdfa by Qiang Xue

Fixes #2653: Fixed the bug that unsetting an unpopulated AR relation would…

Fixes #2653: Fixed the bug that unsetting an unpopulated AR relation would trigger exception (qiangxue)
parent 11baf619
...@@ -52,6 +52,7 @@ Yii Framework 2 Change Log ...@@ -52,6 +52,7 @@ Yii Framework 2 Change Log
- Bug #2559: Going back on browser history breaks GridView filtering with `Pjax` (tonydspaniard) - Bug #2559: Going back on browser history breaks GridView filtering with `Pjax` (tonydspaniard)
- Bug #2607: `yii message` tool wasn't updating `message` table (mitalcoi) - Bug #2607: `yii message` tool wasn't updating `message` table (mitalcoi)
- Bug #2624: Html::textArea() should respect "name" option. (qiangxue) - Bug #2624: Html::textArea() should respect "name" option. (qiangxue)
- Bug #2653: Fixed the bug that unsetting an unpopulated AR relation would trigger exception (qiangxue)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
......
...@@ -272,9 +272,10 @@ interface ActiveRecordInterface ...@@ -272,9 +272,10 @@ interface ActiveRecordInterface
* (normally this would be a relational [[ActiveQuery]] object). * (normally this would be a relational [[ActiveQuery]] object).
* It can be declared in either the ActiveRecord class itself or one of its behaviors. * It can be declared in either the ActiveRecord class itself or one of its behaviors.
* @param string $name the relation name * @param string $name the relation name
* @param boolean $throwException whether to throw exception if the relation does not exist.
* @return ActiveQueryInterface the relational query object * @return ActiveQueryInterface the relational query object
*/ */
public function getRelation($name); public function getRelation($name, $throwException = true);
/** /**
* Establishes the relationship between two records. * Establishes the relationship between two records.
......
...@@ -305,7 +305,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -305,7 +305,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
unset($this->_attributes[$name]); unset($this->_attributes[$name]);
} elseif (array_key_exists($name, $this->_related)) { } elseif (array_key_exists($name, $this->_related)) {
unset($this->_related[$name]); unset($this->_related[$name]);
} else { } elseif ($this->getRelation($name, false) === null) {
parent::__unset($name); parent::__unset($name);
} }
} }
...@@ -1063,20 +1063,30 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -1063,20 +1063,30 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object. * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
* It can be declared in either the Active Record class itself or one of its behaviors. * It can be declared in either the Active Record class itself or one of its behaviors.
* @param string $name the relation name * @param string $name the relation name
* @return ActiveQueryInterface|ActiveQuery the relational query object * @param boolean $throwException whether to throw exception if the relation does not exist.
* @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
* and `$throwException` is false, null will be returned.
* @throws InvalidParamException if the named relation does not exist. * @throws InvalidParamException if the named relation does not exist.
*/ */
public function getRelation($name) public function getRelation($name, $throwException = true)
{ {
$getter = 'get' . $name; $getter = 'get' . $name;
try { try {
// the relation could be defined in a behavior // the relation could be defined in a behavior
$relation = $this->$getter(); $relation = $this->$getter();
} catch (UnknownMethodException $e) { } catch (UnknownMethodException $e) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); if ($throwException) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
} else {
return null;
}
} }
if (!$relation instanceof ActiveQueryInterface) { if (!$relation instanceof ActiveQueryInterface) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); if ($throwException) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
} else {
return null;
}
} }
if (method_exists($this, $getter)) { if (method_exists($this, $getter)) {
...@@ -1084,7 +1094,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -1084,7 +1094,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$method = new \ReflectionMethod($this, $getter); $method = new \ReflectionMethod($this, $getter);
$realName = lcfirst(substr($method->getName(), 3)); $realName = lcfirst(substr($method->getName(), 3));
if ($realName !== $name) { if ($realName !== $name) {
throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\"."); if ($throwException) {
throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
} else {
return null;
}
} }
} }
...@@ -1367,4 +1381,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -1367,4 +1381,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$fields = array_keys($this->getRelatedRecords()); $fields = array_keys($this->getRelatedRecords());
return array_combine($fields, $fields); return array_combine($fields, $fields);
} }
/**
* Sets the element value at the specified offset to null.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `unset($model[$offset])`.
* @param mixed $offset the offset to unset element
*/
public function offsetUnset($offset)
{
// use unset to trigger __unset()
unset($this->$offset);
}
} }
...@@ -184,7 +184,7 @@ trait ActiveRecordTestTrait ...@@ -184,7 +184,7 @@ trait ActiveRecordTestTrait
$this->assertEquals(['user3', 'user2', 'user1'], $this->callCustomerFind()->orderBy(['name' => SORT_DESC])->column('name')); $this->assertEquals(['user3', 'user2', 'user1'], $this->callCustomerFind()->orderBy(['name' => SORT_DESC])->column('name'));
} }
public function testfindIndexBy() public function testFindIndexBy()
{ {
$customerClass = $this->getCustomerClass(); $customerClass = $this->getCustomerClass();
/** @var TestCase|ActiveRecordTestTrait $this */ /** @var TestCase|ActiveRecordTestTrait $this */
...@@ -205,7 +205,7 @@ trait ActiveRecordTestTrait ...@@ -205,7 +205,7 @@ trait ActiveRecordTestTrait
$this->assertTrue($customers['3-user3'] instanceof $customerClass); $this->assertTrue($customers['3-user3'] instanceof $customerClass);
} }
public function testfindIndexByAsArray() public function testFindIndexByAsArray()
{ {
/** @var TestCase|ActiveRecordTestTrait $this */ /** @var TestCase|ActiveRecordTestTrait $this */
// indexBy + asArray // indexBy + asArray
...@@ -399,6 +399,10 @@ trait ActiveRecordTestTrait ...@@ -399,6 +399,10 @@ trait ActiveRecordTestTrait
$this->assertEquals(2, count($orders)); $this->assertEquals(2, count($orders));
$this->assertEquals(1, count($customer->relatedRecords)); $this->assertEquals(1, count($customer->relatedRecords));
// unset
unset($customer['orders']);
$this->assertFalse($customer->isRelationPopulated('orders'));
/** @var Customer $customer */ /** @var Customer $customer */
$customer = $this->callCustomerFind(2); $customer = $this->callCustomerFind(2);
$this->assertFalse($customer->isRelationPopulated('orders')); $this->assertFalse($customer->isRelationPopulated('orders'));
...@@ -422,6 +426,9 @@ trait ActiveRecordTestTrait ...@@ -422,6 +426,9 @@ trait ActiveRecordTestTrait
$this->assertEquals(1, count($customers[1]->orders)); $this->assertEquals(1, count($customers[1]->orders));
$this->assertEquals(2, count($customers[2]->orders)); $this->assertEquals(2, count($customers[2]->orders));
$this->assertEquals(0, count($customers[3]->orders)); $this->assertEquals(0, count($customers[3]->orders));
// unset
unset($customers[1]->orders);
$this->assertFalse($customers[1]->isRelationPopulated('orders'));
$customer = $this->callCustomerFind()->where(['id' => 1])->with('orders')->one(); $customer = $this->callCustomerFind()->where(['id' => 1])->with('orders')->one();
$this->assertTrue($customer->isRelationPopulated('orders')); $this->assertTrue($customer->isRelationPopulated('orders'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment