Commit 81d23332 by Carsten Brandt

afterFind event in AR is now called after relations have been populated

fixes #1993
parent c5022203
......@@ -103,7 +103,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
unset($row);
}
$models = $this->createModels($result['hits']['hits']);
$models = $this->createModels($result['hits']['hits'], false);
if ($this->asArray && !$this->indexBy) {
foreach($models as $key => $model) {
if ($pk === '_id') {
......@@ -116,6 +116,11 @@ class ActiveQuery extends Query implements ActiveQueryInterface
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
}
......@@ -144,13 +149,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($result);
$model = $class::create($result, false);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
}
......@@ -161,7 +169,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
{
$result = $this->createCommand($db)->search($options);
if (!empty($result['hits']['hits'])) {
$models = $this->createModels($result['hits']['hits']);
$models = $this->createModels($result['hits']['hits'], false);
if ($this->asArray) {
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
......@@ -177,6 +185,11 @@ class ActiveQuery extends Query implements ActiveQueryInterface
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
$result['hits']['hits'] = $models;
}
return $result;
......
......@@ -261,15 +261,22 @@ class ActiveRecord extends BaseActiveRecord
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @param bool $callAfterFind whether this is a create after find and afterFind() should be called directly after create.
* This may be set to false to call afterFind later.
* @return ActiveRecord the newly created active record.
*/
public static function create($row)
public static function create($row, $callAfterFind = true)
{
$record = parent::create($row['_source']);
$record = parent::create($row['_source'], false);
$pk = static::primaryKey()[0];
if ($pk === '_id') {
$record->$pk = $row['_id'];
}
$record->_score = isset($row['_score']) ? $row['_score'] : null;
$record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available...
if ($callAfterFind) {
$record->afterFind();
}
return $record;
}
......
......@@ -4,6 +4,7 @@ Yii Framework 2 elasticsearch extension Change Log
2.0.0 beta under development
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
......
......@@ -49,12 +49,19 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$cursor = $this->buildCursor($db);
$rows = $this->fetchRows($cursor);
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->with)) {
$models = $this->createModels($rows, false);
$this->findWith($this->with, $models);
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return $this->createModels($rows);
}
} else {
return [];
}
}
......@@ -76,13 +83,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::create($row, false);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
} else {
return null;
......
......@@ -49,12 +49,19 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$cursor = $this->buildCursor($db);
$rows = $this->fetchRows($cursor);
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->with)) {
$models = $this->createModels($rows, false);
$this->findWith($this->with, $models);
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return $this->createModels($rows);
}
} else {
return [];
}
}
......@@ -76,13 +83,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::create($row, false);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
} else {
return null;
......@@ -91,7 +101,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
/**
* Returns the Mongo collection for this query.
* @param \yii\mongo\Connection $db Mongo connection.
* @param \yii\mongodb\Connection $db Mongo connection.
* @return Collection collection instance.
*/
public function getCollection($db = null)
......
......@@ -72,12 +72,19 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
$rows[] = $row;
}
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->with)) {
$models = $this->createModels($rows, false);
$this->findWith($this->with, $models);
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return $this->createModels($rows);
}
} else {
return [];
}
}
......@@ -107,13 +114,16 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::create($row, false);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
}
......
......@@ -4,6 +4,7 @@ Yii Framework 2 redis extension Change Log
2.0.0 beta under development
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
2.0.0 alpha, December 1, 2013
......
......@@ -105,11 +105,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$command = $this->createCommand($db);
$rows = $command->queryAll();
if (!empty($rows)) {
$models = $this->createModels($rows);
$models = $this->createModels($rows, false);
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
$models = $this->fillUpSnippets($models);
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return [];
......@@ -134,7 +139,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::create($row, false);
}
if (!empty($this->with)) {
$models = [$model];
......@@ -142,6 +147,9 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$model = $models[0];
}
list ($model) = $this->fillUpSnippets([$model]);
if (!$this->asArray) {
$model->afterFind();
}
return $model;
} else {
return null;
......
......@@ -623,9 +623,11 @@ abstract class ActiveRecord extends BaseActiveRecord
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @param bool $callAfterFind whether this is a create after find and afterFind() should be called directly after create.
* This may be set to false to call afterFind later.
* @return ActiveRecord the newly created active record.
*/
public static function create($row)
public static function create($row, $callAfterFind = true)
{
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns;
......@@ -641,7 +643,9 @@ abstract class ActiveRecord extends BaseActiveRecord
}
}
$record->setOldAttributes($record->getAttributes());
if ($callAfterFind) {
$record->afterFind();
}
return $record;
}
......
......@@ -4,6 +4,7 @@ Yii Framework 2 sphinx extension Change Log
2.0.0 beta under development
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
2.0.0 alpha, December 1, 2013
......
......@@ -668,7 +668,7 @@ class Query extends Component implements QueryInterface
* Fills the query result rows with the snippets built from source determined by
* [[snippetCallback]] result.
* @param array $rows raw query result rows.
* @return array query result rows with filled up snippets.
* @return array|ActiveRecord[] query result rows with filled up snippets.
*/
protected function fillUpSnippets($rows)
{
......
......@@ -29,6 +29,7 @@ Yii Framework 2 Change Log
- Bug #1937: Fixed wrong behavior or advanced app's `init --env` when called without parameter actually specified (samdark)
- Bug #1959: `Html::activeCheckbox` wasn't respecting custom values for checked/unchecked state (klevron, samdark)
- Bug #1965: `Controller::findLayoutFile()` returns incorrect file path when layout name starts with a slash (qiangxue)
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe)
- 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)
......
......@@ -67,13 +67,18 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$command = $this->createCommand($db);
$rows = $command->queryAll();
if (!empty($rows)) {
$models = $this->createModels($rows);
$models = $this->createModels($rows, false);
if (!empty($this->join) && $this->indexBy === null) {
$models = $this->removeDuplicatedModels($models);
}
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return [];
......@@ -139,13 +144,16 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::create($row, false);
}
if (!empty($this->with)) {
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
if (!$this->asArray) {
$model->afterFind();
}
return $model;
} else {
return null;
......
......@@ -132,9 +132,10 @@ trait ActiveQueryTrait
/**
* Converts found rows into model instances
* @param array $rows
* @param bool $callAfterFind
* @return array|ActiveRecord[]
*/
private function createModels($rows)
private function createModels($rows, $callAfterFind = true)
{
$models = [];
if ($this->asArray) {
......@@ -154,11 +155,11 @@ trait ActiveQueryTrait
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
$models[] = $class::create($row, $callAfterFind);
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
$model = $class::create($row, $callAfterFind);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
......
......@@ -987,9 +987,11 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @param bool $callAfterFind whether this is a create after find and afterFind() should be called directly after create.
* This may be set to false to call afterFind later.
* @return ActiveRecord the newly created active record.
*/
public static function create($row)
public static function create($row, $callAfterFind = true)
{
$record = static::instantiate($row);
$columns = array_flip($record->attributes());
......@@ -1001,7 +1003,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
}
$record->_oldAttributes = $record->_attributes;
if ($callAfterFind) {
$record->afterFind();
}
return $record;
}
......
......@@ -58,6 +58,7 @@ class Customer extends ActiveRecord
static::type() => [
"_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"],
"properties" => [
"id" => ["type" => "integer"],
"name" => ["type" => "string", "index" => "not_analyzed"],
"email" => ["type" => "string", "index" => "not_analyzed"],
"address" => ["type" => "string", "index" => "analyzed"],
......
......@@ -2,6 +2,8 @@
namespace yiiunit\extensions\elasticsearch;
use yii\base\Event;
use yii\db\BaseActiveRecord;
use yii\elasticsearch\Connection;
use yii\helpers\Json;
use yiiunit\framework\ar\ActiveRecordTestTrait;
......@@ -10,6 +12,7 @@ use yiiunit\data\ar\elasticsearch\Customer;
use yiiunit\data\ar\elasticsearch\OrderItem;
use yiiunit\data\ar\elasticsearch\Order;
use yiiunit\data\ar\elasticsearch\Item;
use yiiunit\TestCase;
/**
* @group elasticsearch
......@@ -495,5 +498,34 @@ class ActiveRecordTest extends ElasticSearchTestCase
$this->assertArrayNotHasKey('status', $customers['3-user3']);
}
public function testAfterFindGet()
{
/** @var BaseActiveRecord $customerClass */
$customerClass = $this->getCustomerClass();
$afterFindCalls = [];
Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND, function($event) use (&$afterFindCalls) {
/** @var BaseActiveRecord $ar */
$ar = $event->sender;
$afterFindCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')];
});
$customer = Customer::get(1);
$this->assertNotNull($customer);
$this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls);
$afterFindCalls = [];
$customer = Customer::mget([1, 2]);
$this->assertNotNull($customer);
$this->assertEquals([
[$customerClass, false, 1, false],
[$customerClass, false, 2, false],
], $afterFindCalls);
$afterFindCalls = [];
Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND);
}
// TODO test AR with not mapped PK
}
\ No newline at end of file
......@@ -7,7 +7,9 @@
namespace yiiunit\framework\ar;
use yii\base\Event;
use yii\db\ActiveQueryInterface;
use yii\db\BaseActiveRecord;
use yiiunit\TestCase;
use yiiunit\data\ar\Customer;
use yiiunit\data\ar\Order;
......@@ -835,4 +837,62 @@ trait ActiveRecordTestTrait
$customers = $this->callCustomerFind()->where(['status' => false])->all();
$this->assertEquals(1, count($customers));
}
public function testAfterFind()
{
/** @var BaseActiveRecord $customerClass */
$customerClass = $this->getCustomerClass();
/** @var BaseActiveRecord $orderClass */
$orderClass = $this->getOrderClass();
/** @var TestCase|ActiveRecordTestTrait $this */
$afterFindCalls = [];
Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND, function($event) use (&$afterFindCalls) {
/** @var BaseActiveRecord $ar */
$ar = $event->sender;
$afterFindCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')];
});
$customer = $this->callCustomerFind(1);
$this->assertNotNull($customer);
$this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls);
$afterFindCalls = [];
$customer = $this->callCustomerFind()->where(['id' => 1])->one();
$this->assertNotNull($customer);
$this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls);
$afterFindCalls = [];
$customer = $this->callCustomerFind()->where(['id' => 1])->all();
$this->assertNotNull($customer);
$this->assertEquals([[$customerClass, false, 1, false]], $afterFindCalls);
$afterFindCalls = [];
$customer = $this->callCustomerFind()->where(['id' => 1])->with('orders')->all();
$this->assertNotNull($customer);
$this->assertEquals([
[$this->getOrderClass(), false, 1, false],
[$customerClass, false, 1, true],
], $afterFindCalls);
$afterFindCalls = [];
if ($this instanceof \yiiunit\extensions\redis\ActiveRecordTest) { // TODO redis does not support orderBy() yet
$customer = $this->callCustomerFind()->where(['id' => [1, 2]])->with('orders')->all();
} else {
// orderBy is needed to avoid random test failure
$customer = $this->callCustomerFind()->where(['id' => [1, 2]])->with('orders')->orderBy('name')->all();
}
$this->assertNotNull($customer);
$this->assertEquals([
[$orderClass, false, 1, false],
[$orderClass, false, 2, false],
[$orderClass, false, 3, false],
[$customerClass, false, 1, true],
[$customerClass, false, 2, true],
], $afterFindCalls);
$afterFindCalls = [];
Event::off(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_FIND);
}
}
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