Skip to content

Commit 43d8b40

Browse files
author
Simon Karlen
committed
changed relation handling
1 parent 091bece commit 43d8b40

File tree

4 files changed

+152
-69
lines changed

4 files changed

+152
-69
lines changed

src/ActiveQuery.php

Lines changed: 100 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,6 @@ public function createCommand($db = null)
5656
return parent::createCommand($db);
5757
}
5858

59-
/**
60-
* {@inheritdoc}
61-
*/
62-
public function all($db = null)
63-
{
64-
return parent::all($db);
65-
}
66-
67-
/**
68-
* {@inheritdoc}
69-
* @throws InvalidConfigException
70-
*/
71-
public function one($db = null)
72-
{
73-
$row = parent::one($db);
74-
if ($row !== false) {
75-
$models = $this->populate(isset($row[0]) ? $row : [$row]);
76-
77-
return reset($models) ?: null;
78-
}
79-
80-
return null;
81-
}
82-
8359
/**
8460
* {@inheritdoc}
8561
* @throws InvalidConfigException
@@ -134,46 +110,6 @@ public function prepare($builder)
134110
return $query;
135111
}
136112

137-
/**
138-
* Joins with the specified relations.
139-
*
140-
* This method allows you to reuse existing relation definitions to perform JOIN queries.
141-
* Based on the definition of the specified relation(s), the method will append one or multiple
142-
* JOIN statements to the current query.
143-
*
144-
* @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
145-
* an array with the following semantics:
146-
*
147-
* - Each array element represents a single relation.
148-
* - You may specify the relation name as the array key and provide an anonymous functions that
149-
* can be used to modify the relation queries on-the-fly as the array value.
150-
* - If a relation query does not need modification, you may use the relation name as the array value.
151-
*
152-
* Sub-relations can also be specified, see [[with()]] for the syntax.
153-
*
154-
* In the following you find some examples:
155-
*
156-
* ```php
157-
* // find all orders that contain books, and eager loading "books"
158-
* Order::find()->joinWith('books')->all();
159-
* // find all orders, eager loading "books", and sort the orders and books by the book names.
160-
* Order::find()->joinWith([
161-
* 'books' => function (\simialbi\yii2\rest\ActiveQuery $query) {
162-
* $query->orderBy('item.name');
163-
* }
164-
* ])->all();
165-
* // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
166-
* Order::find()->joinWith(['books b'])->where(['b.category' => 'Science fiction'])->all();
167-
* ```
168-
*
169-
* @return $this the query object itself
170-
*/
171-
public function joinWith($with)
172-
{
173-
$this->joinWith[] = (array)$with;
174-
return $this;
175-
}
176-
177113
/**
178114
* Builds join with clauses
179115
*/
@@ -196,7 +132,6 @@ private function buildJoinWith()
196132
}
197133
}
198134

199-
200135
/**
201136
* Modifies the current query by adding join fragments based on the given relations.
202137
* @param ActiveRecord $model the primary model
@@ -224,6 +159,7 @@ protected function joinWithRelations($model, $with)
224159
}
225160
}
226161
}
162+
227163
/**
228164
* Joins a parent query with a child query.
229165
* The current query object will be modified accordingly.
@@ -240,6 +176,31 @@ private function joinWithRelation($parent, $child)
240176
}
241177
}
242178

179+
/**
180+
* {@inheritdoc}
181+
*/
182+
public function all($db = null)
183+
{
184+
return parent::all($db);
185+
}
186+
187+
/**
188+
* {@inheritdoc}
189+
* @throws InvalidConfigException
190+
*/
191+
public function one($db = null)
192+
{
193+
$row = parent::one($db);
194+
if ($row !== false) {
195+
$models = $this->populate(isset($row[0]) ? $row : [$row]);
196+
197+
return reset($models) ?: null;
198+
}
199+
200+
return null;
201+
}
202+
203+
243204
/**
244205
* {@inheritdoc}
245206
* @throws InvalidConfigException
@@ -268,14 +229,46 @@ public function populate($rows)
268229
return $models;
269230
}
270231

232+
/**
233+
* {@inheritDoc}
234+
*/
235+
protected function createModels($rows)
236+
{
237+
if ($this->asArray) {
238+
return $rows;
239+
} else {
240+
$models = [];
241+
/* @var $class ActiveRecord */
242+
$class = $this->modelClass;
243+
foreach ($rows as $row) {
244+
$model = $class::instantiate($row);
245+
/** @var $modelClass ActiveRecord */
246+
$modelClass = get_class($model);
247+
$modelClass::populateRecord($model, $row);
248+
if (!empty($this->join)) {
249+
foreach ($this->join as $join) {
250+
if (isset($join[1], $row[$join[1]])) {
251+
$relation = $model->getRelation($join[1]);
252+
$rows = (ArrayHelper::isAssociative($row[$join[1]])) ? [$row[$join[1]]] : $row[$join[1]];
253+
$relations = $relation->populate($rows);
254+
$model->populateRelation($join[1], $relation->multiple ? $relations : $relations[0]);
255+
}
256+
}
257+
}
258+
$models[] = $model;
259+
}
260+
return $models;
261+
}
262+
}
263+
271264
/**
272265
* Removes duplicated models by checking their primary key values.
273266
* This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
274267
*
275268
* @param array $models the models to be checked
276269
*
277-
* @throws InvalidConfigException if model primary key is empty
278270
* @return array the distinctive models
271+
* @throws InvalidConfigException if model primary key is empty
279272
*/
280273
private function removeDuplicatedModels($models)
281274
{
@@ -303,6 +296,7 @@ private function removeDuplicatedModels($models)
303296
}
304297
}
305298
} elseif (empty($pks)) {
299+
/** @var $class string */
306300
throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
307301
} else {
308302
// single column primary key
@@ -323,4 +317,44 @@ private function removeDuplicatedModels($models)
323317

324318
return array_values($models);
325319
}
320+
321+
/**
322+
* Joins with the specified relations.
323+
*
324+
* This method allows you to reuse existing relation definitions to perform JOIN queries.
325+
* Based on the definition of the specified relation(s), the method will append one or multiple
326+
* JOIN statements to the current query.
327+
*
328+
* @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
329+
* an array with the following semantics:
330+
*
331+
* - Each array element represents a single relation.
332+
* - You may specify the relation name as the array key and provide an anonymous functions that
333+
* can be used to modify the relation queries on-the-fly as the array value.
334+
* - If a relation query does not need modification, you may use the relation name as the array value.
335+
*
336+
* Sub-relations can also be specified, see [[with()]] for the syntax.
337+
*
338+
* In the following you find some examples:
339+
*
340+
* ```php
341+
* // find all orders that contain books, and eager loading "books"
342+
* Order::find()->joinWith('books')->all();
343+
* // find all orders, eager loading "books", and sort the orders and books by the book names.
344+
* Order::find()->joinWith([
345+
* 'books' => function (\simialbi\yii2\rest\ActiveQuery $query) {
346+
* $query->orderBy('item.name');
347+
* }
348+
* ])->all();
349+
* // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
350+
* Order::find()->joinWith(['books b'])->where(['b.category' => 'Science fiction'])->all();
351+
* ```
352+
*
353+
* @return $this the query object itself
354+
*/
355+
public function joinWith($with)
356+
{
357+
$this->joinWith[] = (array)$with;
358+
return $this;
359+
}
326360
}

src/ActiveRecord.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ActiveRecord extends BaseActiveRecord
3232
public function attributes()
3333
{
3434
if (empty($this->_attributeFields)) {
35-
$regex = '#^@property(?:-(read|write))?(?:(?:\s+)([^\s]+))?(?:\s+)\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#';
35+
$regex = '#^@property(?:-(read|write))?(?:\s+([^\s]+))?\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#';
3636
$typeRegex = '#^(bool(ean)?|int(eger)?|float|double|string|array)$#';
3737
$reflection = new \ReflectionClass($this);
3838
$docLines = preg_split('~\R~u', $reflection->getDocComment());
@@ -53,10 +53,11 @@ public function attributes()
5353

5454
/**
5555
* {@inheritdoc}
56+
* @throws InvalidConfigException
5657
*/
5758
public static function primaryKey()
5859
{
59-
new InvalidConfigException('The primaryKey() method of RestClient ActiveRecord has to be implemented by child classes.');
60+
throw new InvalidConfigException('The primaryKey() method of RestClient ActiveRecord has to be implemented by child classes.');
6061
}
6162

6263
/**
@@ -222,4 +223,22 @@ public function unlinkAll($name, $delete = false)
222223
{
223224
throw new NotSupportedException('unlinkAll() is not supported by RestClient, use unlink() instead.');
224225
}
226+
227+
/**
228+
* {@inheritDoc}
229+
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
230+
*/
231+
public function hasOne($class, $link)
232+
{
233+
return parent::hasOne($class, $link);
234+
}
235+
236+
/**
237+
* {@inheritDoc}
238+
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
239+
*/
240+
public function hasMany($class, $link)
241+
{
242+
return parent::hasMany($class, $link);
243+
}
225244
}

tests/RelationTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ protected function setUp()
2222
Yii::$app->log->logger->flush();
2323
}
2424

25-
public function testRelationGet()
25+
public function testRelationGetAll()
2626
{
2727
$fixture = new RestModelFixture();
2828
$fixture->load();
@@ -42,4 +42,24 @@ public function testRelationGet()
4242
$this->assertEquals('GET', $logEntry['method']);
4343
$this->assertStringStartsWith('https://api.site.com/related-rest-models?filter%5Brest_model_id%5D=1', $logEntry['url']);
4444
}
45+
46+
public function testRelationGetOne()
47+
{
48+
$fixture = new RestModelFixture();
49+
$fixture->load();
50+
51+
/* @var $model RestModel */
52+
$model = $fixture->getModel(0);
53+
54+
$this->assertInstanceOf(RestModel::class, $model);
55+
56+
Yii::$app->log->logger->flush();
57+
58+
$related = $model->relatedRest;
59+
60+
$logEntry = $this->parseLogs();
61+
62+
$this->assertEquals('GET', $logEntry['method']);
63+
$this->assertStringStartsWith('https://api.site.com/related-rest-models?filter%5Brest_model_id%5D=1', $logEntry['url']);
64+
}
4565
}

tests/models/RestModel.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* @property string $updated_by
2424
*
2525
* @property-read RelatedRestModel[] $relatedRests
26+
* @property-read RelatedRestModel $relatedRest
2627
*/
2728
class RestModel extends ActiveRecord
2829
{
@@ -42,4 +43,13 @@ public function getRelatedRests()
4243
{
4344
return $this->hasMany(RelatedRestModel::class, ['rest_model_id' => 'id']);
4445
}
46+
47+
/**
48+
* Get related rest
49+
* @return \simialbi\yii2\rest\ActiveQuery|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
50+
*/
51+
public function getRelatedRest()
52+
{
53+
return $this->hasOne(RelatedRestModel::class, ['rest_model_id' => 'id']);
54+
}
4555
}

0 commit comments

Comments
 (0)