mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-14 06:11:35 +08:00
Added Alias Syntax for joinWith()
Add alias syntax to joinWith(), e.g. joinWith('author a'). No need to know the table name for defining an alias for the relation. fixes #2377, alternative to #8788, which allows later implementation of getting alias and column name ambiguation. depends on #10813 to be merged first.
This commit is contained in:
@ -1089,7 +1089,28 @@ Note that this differs from our earlier example which only brings back customers
|
||||
> Info: When [[yii\db\ActiveQuery]] is specified with a condition via [[yii\db\ActiveQuery::onCondition()|onCondition()]],
|
||||
the condition will be put in the `ON` part if the query involves a JOIN query. If the query does not involve
|
||||
JOIN, the on-condition will be automatically appended to the `WHERE` part of the query.
|
||||
Thus it may only contain conditions including columns of the related table.
|
||||
|
||||
#### Relation table aliases <span id="relation-table-aliases"></span>
|
||||
|
||||
As noted before, when using JOIN in a query, we need to disambiguate column names. Therefor often an alias is
|
||||
defined for a table. Setting an alias for the relational query would be possible by customizing the relation query in the following way:
|
||||
|
||||
```php
|
||||
$query->joinWith([
|
||||
'orders' => function ($q) {
|
||||
$q->from(['o' => Order::tableName()]);
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
This however looks very complicated and involves either hardcoding the related objects table name or calling `Order::tableName()`.
|
||||
Since version 2.0.7, Yii provides a shortcut for this. You may now define and use the alias for the relation table like the following:
|
||||
|
||||
```php
|
||||
// join the orders relation and sort the result by orders.id
|
||||
$query->joinWith(['orders o'])->orderBy('o.id');
|
||||
```
|
||||
|
||||
### Inverse Relations <span id="inverse-relations"></span>
|
||||
|
||||
|
@ -63,6 +63,7 @@ Yii Framework 2 Change Log
|
||||
- Bug #10760: `yii\widgets\DetailView::normalizeAttributes()` fixed for arrayable models (boehsermoe)
|
||||
- Bug: Fixed generation of canonical URLs for `ViewAction` pages (samdark)
|
||||
- Bug: Fixed `mb_*` functions calls to use `UTF-8` or `Yii::$app->charset` (silverfire)
|
||||
- Enh #2377: Allow specifying a table alias when joining relations via `joinWith()` (cebe, nainoon)
|
||||
- Enh #3506: Added `yii\validators\IpValidator` to perform validation of IP addresses and subnets (SilverFire, samdark)
|
||||
- Enh #4972: Added `yii\db\ActiveQuery::alias()` to allow specifying a table alias for the model table without having to know the name (cebe, stepanselyuk)
|
||||
- Enh #5146: Added `yii\i18n\Formatter::asDuration()` method (nineinchnick, SilverFire)
|
||||
|
@ -355,11 +355,19 @@ class ActiveQuery extends Query implements ActiveQueryInterface
|
||||
* This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
|
||||
* for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
|
||||
*
|
||||
* @param string|array $with the relations to be joined. Each array element represents a single relation.
|
||||
* The array keys are relation names, and the array values are the corresponding anonymous functions that
|
||||
* can be used to modify the relation queries on-the-fly. 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 example,
|
||||
* @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.
|
||||
*
|
||||
* The relation name may optionally contain an alias for the relation table (e.g. `books b`).
|
||||
*
|
||||
* 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"
|
||||
@ -370,8 +378,12 @@ class ActiveQuery extends Query implements ActiveQueryInterface
|
||||
* $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'], true, 'INNER JOIN')->where(['b.category' => 'Science fiction'])->all();
|
||||
* ```
|
||||
*
|
||||
* The alias syntax is available since version 2.0.7.
|
||||
*
|
||||
* @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`.
|
||||
* When this is a boolean, it applies to all relations specified in `$with`. Use an array
|
||||
* to explicitly list which relations in `$with` need to be eagerly loaded. Defaults to `true`.
|
||||
@ -382,8 +394,33 @@ class ActiveQuery extends Query implements ActiveQueryInterface
|
||||
*/
|
||||
public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
|
||||
{
|
||||
$this->joinWith[] = [(array) $with, $eagerLoading, $joinType];
|
||||
$relations = [];
|
||||
foreach((array) $with as $name => $callback) {
|
||||
if (is_int($name)) {
|
||||
$name = $callback;
|
||||
$callback = null;
|
||||
}
|
||||
|
||||
if (preg_match('/^(.*?)(?:\s+AS\s+|\s+)(\w+)$/i', $name, $matches)) {
|
||||
// relation is defined with an alias, adjust callback to apply alias
|
||||
list(, $relation, $alias) = $matches;
|
||||
$name = $relation;
|
||||
$callback = function($query) use ($callback, $alias) {
|
||||
/** @var $query ActiveQuery */
|
||||
$query->alias($alias);
|
||||
if ($callback !== null) {
|
||||
call_user_func($callback, $query);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if ($callback === null) {
|
||||
$relations[] = $name;
|
||||
} else {
|
||||
$relations[$name] = $callback;
|
||||
}
|
||||
}
|
||||
$this->joinWith[] = [$relations, $eagerLoading, $joinType];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -551,6 +551,114 @@ class ActiveRecordTest extends DatabaseTestCase
|
||||
])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the alias syntax for joinWith: 'alias' => 'relation'
|
||||
*/
|
||||
public function testJoinWithAlias()
|
||||
{
|
||||
// left join and eager loading
|
||||
/** @var ActiveQuery $query */
|
||||
$query = Order::find()->joinWith(['customer c']);
|
||||
$orders = $query->orderBy($query->applyRelationAlias('customer', 'id') /* c.id */ . ' DESC, order.id')->all();
|
||||
$this->assertEquals(3, count($orders));
|
||||
$this->assertEquals(2, $orders[0]->id);
|
||||
$this->assertEquals(3, $orders[1]->id);
|
||||
$this->assertEquals(1, $orders[2]->id);
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('customer'));
|
||||
$this->assertTrue($orders[1]->isRelationPopulated('customer'));
|
||||
$this->assertTrue($orders[2]->isRelationPopulated('customer'));
|
||||
|
||||
// inner join filtering and eager loading
|
||||
$orders = Order::find()->innerJoinWith(['customer c'])->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
|
||||
$this->assertEquals(2, count($orders));
|
||||
$this->assertEquals(2, $orders[0]->id);
|
||||
$this->assertEquals(3, $orders[1]->id);
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('customer'));
|
||||
$this->assertTrue($orders[1]->isRelationPopulated('customer'));
|
||||
|
||||
// inner join filtering without eager loading
|
||||
$orders = Order::find()->innerJoinWith(['customer c'], false)->where('{{c}}.[[id]]=2')->orderBy('order.id')->all();
|
||||
$this->assertEquals(2, count($orders));
|
||||
$this->assertEquals(2, $orders[0]->id);
|
||||
$this->assertEquals(3, $orders[1]->id);
|
||||
$this->assertFalse($orders[0]->isRelationPopulated('customer'));
|
||||
$this->assertFalse($orders[1]->isRelationPopulated('customer'));
|
||||
|
||||
// join with via-relation
|
||||
$query = Order::find()->innerJoinWith(['books b']);
|
||||
$orders = $query->where([$query->getRelationAlias('books') . '.name' => 'Yii 1.1 Application Development Cookbook'])->orderBy($query->applyAlias('order', 'id'))->all();
|
||||
$this->assertEquals(2, count($orders));
|
||||
$this->assertEquals(1, $orders[0]->id);
|
||||
$this->assertEquals(3, $orders[1]->id);
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('books'));
|
||||
$this->assertTrue($orders[1]->isRelationPopulated('books'));
|
||||
$this->assertEquals(2, count($orders[0]->books));
|
||||
$this->assertEquals(1, count($orders[1]->books));
|
||||
|
||||
|
||||
$orders = Order::find()->innerJoinWith([
|
||||
'items i' => function ($q) {
|
||||
/** @var $q ActiveQuery */
|
||||
$q->orderBy($q->applyAlias('item', 'id'));
|
||||
},
|
||||
'items.category c' => function ($q) {
|
||||
/** @var $q ActiveQuery */
|
||||
$q->where('{{c}}.[[id]] = 2');
|
||||
},
|
||||
])->orderBy('i.id')->all();
|
||||
|
||||
$this->assertEquals(1, count($orders));
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('items'));
|
||||
$this->assertEquals(2, $orders[0]->id);
|
||||
$this->assertEquals(3, count($orders[0]->items));
|
||||
$this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
|
||||
$this->assertEquals(2, $orders[0]->items[0]->category->id);
|
||||
|
||||
// join with ON condition
|
||||
$orders = Order::find()->joinWith(['books2 b'])->orderBy('order.id')->all();
|
||||
$this->assertEquals(3, count($orders));
|
||||
$this->assertEquals(1, $orders[0]->id);
|
||||
$this->assertEquals(2, $orders[1]->id);
|
||||
$this->assertEquals(3, $orders[2]->id);
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('books2'));
|
||||
$this->assertTrue($orders[1]->isRelationPopulated('books2'));
|
||||
$this->assertTrue($orders[2]->isRelationPopulated('books2'));
|
||||
$this->assertEquals(2, count($orders[0]->books2));
|
||||
$this->assertEquals(0, count($orders[1]->books2));
|
||||
$this->assertEquals(1, count($orders[2]->books2));
|
||||
|
||||
// join with count and query
|
||||
/** @var $query ActiveQuery */
|
||||
$query = Order::find()->joinWith(['customer c']);
|
||||
$count = $query->count('c.id');
|
||||
$this->assertEquals(3, $count);
|
||||
$orders = $query->all();
|
||||
$this->assertEquals(3, count($orders));
|
||||
|
||||
// relational query
|
||||
$order = Order::findOne(1);
|
||||
$customer = $order->getCustomer()->innerJoinWith(['orders o'], false)->where(['o.id' => 1])->one();
|
||||
$this->assertNotNull($customer);
|
||||
$this->assertEquals(1, $customer->id);
|
||||
|
||||
// join with sub-relation called inside Closure
|
||||
$orders = Order::find()->joinWith([
|
||||
'items' => function ($q) {
|
||||
/** @var $q ActiveQuery */
|
||||
$q->orderBy('item.id');
|
||||
$q->joinWith(['category c']);
|
||||
$q->where('{{c}}.[[id]] = 2');
|
||||
},
|
||||
])->orderBy('order.id')->all();
|
||||
$this->assertEquals(1, count($orders));
|
||||
$this->assertTrue($orders[0]->isRelationPopulated('items'));
|
||||
$this->assertEquals(2, $orders[0]->id);
|
||||
$this->assertEquals(3, count($orders[0]->items));
|
||||
$this->assertTrue($orders[0]->items[0]->isRelationPopulated('category'));
|
||||
$this->assertEquals(2, $orders[0]->items[0]->category->id);
|
||||
|
||||
}
|
||||
|
||||
public function testAlias()
|
||||
{
|
||||
$query = Order::find();
|
||||
|
Reference in New Issue
Block a user