Skip to content

Commit

Permalink
changed relation handling
Browse files Browse the repository at this point in the history
  • Loading branch information
simialbi committed Jul 8, 2021
1 parent 091bece commit 43d8b40
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 69 deletions.
166 changes: 100 additions & 66 deletions src/ActiveQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,6 @@ public function createCommand($db = null)
return parent::createCommand($db);
}

/**
* {@inheritdoc}
*/
public function all($db = null)
{
return parent::all($db);
}

/**
* {@inheritdoc}
* @throws InvalidConfigException
*/
public function one($db = null)
{
$row = parent::one($db);
if ($row !== false) {
$models = $this->populate(isset($row[0]) ? $row : [$row]);

return reset($models) ?: null;
}

return null;
}

/**
* {@inheritdoc}
* @throws InvalidConfigException
Expand Down Expand Up @@ -134,46 +110,6 @@ public function prepare($builder)
return $query;
}

/**
* Joins with the specified relations.
*
* This method allows you to reuse existing relation definitions to perform JOIN queries.
* Based on the definition of the specified relation(s), the method will append one or multiple
* JOIN statements to the current query.
*
* @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
* an array with the following semantics:
*
* - Each array element represents a single relation.
* - You may specify the relation name as the array key and provide an anonymous functions that
* can be used to modify the relation queries on-the-fly as the array value.
* - If a relation query does not need modification, you may use the relation name as the array value.
*
* Sub-relations can also be specified, see [[with()]] for the syntax.
*
* In the following you find some examples:
*
* ```php
* // find all orders that contain books, and eager loading "books"
* Order::find()->joinWith('books')->all();
* // find all orders, eager loading "books", and sort the orders and books by the book names.
* Order::find()->joinWith([
* 'books' => function (\simialbi\yii2\rest\ActiveQuery $query) {
* $query->orderBy('item.name');
* }
* ])->all();
* // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
* Order::find()->joinWith(['books b'])->where(['b.category' => 'Science fiction'])->all();
* ```
*
* @return $this the query object itself
*/
public function joinWith($with)
{
$this->joinWith[] = (array)$with;
return $this;
}

/**
* Builds join with clauses
*/
Expand All @@ -196,7 +132,6 @@ private function buildJoinWith()
}
}


/**
* Modifies the current query by adding join fragments based on the given relations.
* @param ActiveRecord $model the primary model
Expand Down Expand Up @@ -224,6 +159,7 @@ protected function joinWithRelations($model, $with)
}
}
}

/**
* Joins a parent query with a child query.
* The current query object will be modified accordingly.
Expand All @@ -240,6 +176,31 @@ private function joinWithRelation($parent, $child)
}
}

/**
* {@inheritdoc}
*/
public function all($db = null)
{
return parent::all($db);
}

/**
* {@inheritdoc}
* @throws InvalidConfigException
*/
public function one($db = null)
{
$row = parent::one($db);
if ($row !== false) {
$models = $this->populate(isset($row[0]) ? $row : [$row]);

return reset($models) ?: null;
}

return null;
}


/**
* {@inheritdoc}
* @throws InvalidConfigException
Expand Down Expand Up @@ -268,14 +229,46 @@ public function populate($rows)
return $models;
}

/**
* {@inheritDoc}
*/
protected function createModels($rows)
{
if ($this->asArray) {
return $rows;
} else {
$models = [];
/* @var $class ActiveRecord */
$class = $this->modelClass;
foreach ($rows as $row) {
$model = $class::instantiate($row);
/** @var $modelClass ActiveRecord */
$modelClass = get_class($model);
$modelClass::populateRecord($model, $row);
if (!empty($this->join)) {
foreach ($this->join as $join) {
if (isset($join[1], $row[$join[1]])) {
$relation = $model->getRelation($join[1]);
$rows = (ArrayHelper::isAssociative($row[$join[1]])) ? [$row[$join[1]]] : $row[$join[1]];
$relations = $relation->populate($rows);
$model->populateRelation($join[1], $relation->multiple ? $relations : $relations[0]);
}
}
}
$models[] = $model;
}
return $models;
}
}

/**
* Removes duplicated models by checking their primary key values.
* This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
*
* @param array $models the models to be checked
*
* @throws InvalidConfigException if model primary key is empty
* @return array the distinctive models
* @throws InvalidConfigException if model primary key is empty
*/
private function removeDuplicatedModels($models)
{
Expand Down Expand Up @@ -303,6 +296,7 @@ private function removeDuplicatedModels($models)
}
}
} elseif (empty($pks)) {
/** @var $class string */
throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
} else {
// single column primary key
Expand All @@ -323,4 +317,44 @@ private function removeDuplicatedModels($models)

return array_values($models);
}

/**
* Joins with the specified relations.
*
* This method allows you to reuse existing relation definitions to perform JOIN queries.
* Based on the definition of the specified relation(s), the method will append one or multiple
* JOIN statements to the current query.
*
* @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
* an array with the following semantics:
*
* - Each array element represents a single relation.
* - You may specify the relation name as the array key and provide an anonymous functions that
* can be used to modify the relation queries on-the-fly as the array value.
* - If a relation query does not need modification, you may use the relation name as the array value.
*
* Sub-relations can also be specified, see [[with()]] for the syntax.
*
* In the following you find some examples:
*
* ```php
* // find all orders that contain books, and eager loading "books"
* Order::find()->joinWith('books')->all();
* // find all orders, eager loading "books", and sort the orders and books by the book names.
* Order::find()->joinWith([
* 'books' => function (\simialbi\yii2\rest\ActiveQuery $query) {
* $query->orderBy('item.name');
* }
* ])->all();
* // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
* Order::find()->joinWith(['books b'])->where(['b.category' => 'Science fiction'])->all();
* ```
*
* @return $this the query object itself
*/
public function joinWith($with)
{
$this->joinWith[] = (array)$with;
return $this;
}
}
23 changes: 21 additions & 2 deletions src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ActiveRecord extends BaseActiveRecord
public function attributes()
{
if (empty($this->_attributeFields)) {
$regex = '#^@property(?:-(read|write))?(?:(?:\s+)([^\s]+))?(?:\s+)\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#';
$regex = '#^@property(?:-(read|write))?(?:\s+([^\s]+))?\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#';
$typeRegex = '#^(bool(ean)?|int(eger)?|float|double|string|array)$#';
$reflection = new \ReflectionClass($this);
$docLines = preg_split('~\R~u', $reflection->getDocComment());
Expand All @@ -53,10 +53,11 @@ public function attributes()

/**
* {@inheritdoc}
* @throws InvalidConfigException
*/
public static function primaryKey()
{
new InvalidConfigException('The primaryKey() method of RestClient ActiveRecord has to be implemented by child classes.');
throw new InvalidConfigException('The primaryKey() method of RestClient ActiveRecord has to be implemented by child classes.');
}

/**
Expand Down Expand Up @@ -222,4 +223,22 @@ public function unlinkAll($name, $delete = false)
{
throw new NotSupportedException('unlinkAll() is not supported by RestClient, use unlink() instead.');
}

/**
* {@inheritDoc}
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
*/
public function hasOne($class, $link)
{
return parent::hasOne($class, $link);
}

/**
* {@inheritDoc}
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
*/
public function hasMany($class, $link)
{
return parent::hasMany($class, $link);
}
}
22 changes: 21 additions & 1 deletion tests/RelationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected function setUp()
Yii::$app->log->logger->flush();
}

public function testRelationGet()
public function testRelationGetAll()
{
$fixture = new RestModelFixture();
$fixture->load();
Expand All @@ -42,4 +42,24 @@ public function testRelationGet()
$this->assertEquals('GET', $logEntry['method']);
$this->assertStringStartsWith('https://api.site.com/related-rest-models?filter%5Brest_model_id%5D=1', $logEntry['url']);
}

public function testRelationGetOne()
{
$fixture = new RestModelFixture();
$fixture->load();

/* @var $model RestModel */
$model = $fixture->getModel(0);

$this->assertInstanceOf(RestModel::class, $model);

Yii::$app->log->logger->flush();

$related = $model->relatedRest;

$logEntry = $this->parseLogs();

$this->assertEquals('GET', $logEntry['method']);
$this->assertStringStartsWith('https://api.site.com/related-rest-models?filter%5Brest_model_id%5D=1', $logEntry['url']);
}
}
10 changes: 10 additions & 0 deletions tests/models/RestModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* @property string $updated_by
*
* @property-read RelatedRestModel[] $relatedRests
* @property-read RelatedRestModel $relatedRest
*/
class RestModel extends ActiveRecord
{
Expand All @@ -42,4 +43,13 @@ public function getRelatedRests()
{
return $this->hasMany(RelatedRestModel::class, ['rest_model_id' => 'id']);
}

/**
* Get related rest
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
*/
public function getRelatedRest()
{
return $this->hasOne(RelatedRestModel::class, ['rest_model_id' => 'id']);
}
}

0 comments on commit 43d8b40

Please sign in to comment.