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
- 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 #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 incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
......
......@@ -272,9 +272,10 @@ interface ActiveRecordInterface
* (normally this would be a relational [[ActiveQuery]] object).
* It can be declared in either the ActiveRecord class itself or one of its behaviors.
* @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
*/
public function getRelation($name);
public function getRelation($name, $throwException = true);
/**
* Establishes the relationship between two records.
......
......@@ -305,7 +305,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
unset($this->_attributes[$name]);
} elseif (array_key_exists($name, $this->_related)) {
unset($this->_related[$name]);
} else {
} elseif ($this->getRelation($name, false) === null) {
parent::__unset($name);
}
}
......@@ -1063,20 +1063,30 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* 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.
* @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.
*/
public function getRelation($name)
public function getRelation($name, $throwException = true)
{
$getter = 'get' . $name;
try {
// the relation could be defined in a behavior
$relation = $this->$getter();
} 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) {
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)) {
......@@ -1084,7 +1094,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$method = new \ReflectionMethod($this, $getter);
$realName = lcfirst(substr($method->getName(), 3));
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
$fields = array_keys($this->getRelatedRecords());
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
$this->assertEquals(['user3', 'user2', 'user1'], $this->callCustomerFind()->orderBy(['name' => SORT_DESC])->column('name'));
}
public function testfindIndexBy()
public function testFindIndexBy()
{
$customerClass = $this->getCustomerClass();
/** @var TestCase|ActiveRecordTestTrait $this */
......@@ -205,7 +205,7 @@ trait ActiveRecordTestTrait
$this->assertTrue($customers['3-user3'] instanceof $customerClass);
}
public function testfindIndexByAsArray()
public function testFindIndexByAsArray()
{
/** @var TestCase|ActiveRecordTestTrait $this */
// indexBy + asArray
......@@ -399,6 +399,10 @@ trait ActiveRecordTestTrait
$this->assertEquals(2, count($orders));
$this->assertEquals(1, count($customer->relatedRecords));
// unset
unset($customer['orders']);
$this->assertFalse($customer->isRelationPopulated('orders'));
/** @var Customer $customer */
$customer = $this->callCustomerFind(2);
$this->assertFalse($customer->isRelationPopulated('orders'));
......@@ -422,6 +426,9 @@ trait ActiveRecordTestTrait
$this->assertEquals(1, count($customers[1]->orders));
$this->assertEquals(2, count($customers[2]->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();
$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