Skip to content

Commit 1d011c6

Browse files
committed
Nested relations support added
1 parent b4ded24 commit 1d011c6

File tree

5 files changed

+158
-29
lines changed

5 files changed

+158
-29
lines changed

CHANGELOG

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
2014-11-12, v1.0.0
5+
------------------
6+
7+
* Initial Version

README.md

+92-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,104 @@
1-
## Package to convert BelongsTo subqueries into one query with left join
1+
## Package to convert Eloquent BelongsTo subqueries into one query with left join
22

33
### Usage
44

5+
Mark relation you want to convert into left join using `->references($relations)` method or `Model::includes($relations)` method:
6+
57
```php
6-
\Model::with('other')->references('other')->orderBy('other.title', 'asc')->get();
7-
# this will make one sql-query with left join of 'other' relation. result object will be the same.
8+
Model::with('other')->references('other')->orderBy('other.title', 'asc')->get();
9+
# this will make one sql-query with left join of 'other' relation
10+
# result object will be the same object
11+
12+
Model::includes('other', 'another')->where('other.title', '=', 'my title')->get();
13+
# will be the same as Model::with('other', 'another')->references('other', 'another')->…
814

9-
\Model::with('other')->orderBy('field', 'asc')->get();
15+
Model::with('other')->orderBy('field', 'asc')->get();
1016
# this will work with default behaviour (perform 2 sql-queries)
1117
```
1218

19+
### Object Structure
20+
21+
You will get the same object if you will use `includes()` method. For example:
22+
23+
```php
24+
StreetImage::includes('street')->first()
25+
```
26+
27+
will return:
28+
29+
```
30+
object(StreetImage) {
31+
<all street image attributes>
32+
street: object(Street) {
33+
<all street attributes>
34+
}
35+
}
36+
```
37+
38+
Structure will be the same even if you using nested relations:
39+
40+
```php
41+
StreetImage::includes('street.district')->first();
42+
```
43+
44+
will return:
45+
46+
```
47+
object(StreetImage) {
48+
<all street image attributes>
49+
street: object(Street) {
50+
<all street attributes>
51+
district: object(District) {
52+
<all district attributes>
53+
}
54+
}
55+
}
56+
```
57+
58+
### Nested Relations
59+
60+
```php
61+
StreetImage::includes('street.type', 'street.district')->first();
62+
```
63+
64+
will perform a following sql-query (*<…> will be replaced with all table columns*):
65+
66+
```sql
67+
select
68+
`streets`.`<…>` as `_foreign_street.<…>`,
69+
`street_types`.`<…>` as `_foreign_street._foreign_type.<…>`,
70+
`districts`.`<…>` as `_foreign_street._foreign_district.<…>`,
71+
`street_images`.*
72+
from
73+
`street_images`
74+
left join
75+
`streets` on `streets`.`id` = `street_images`.`street_id`
76+
left join
77+
`street_types` on `street_types`.`id` = `streets`.`street_type_id`
78+
left join
79+
`districts` on `districts`.`id` = `streets`.`district_id`
80+
order by `sort` asc
81+
limit 1
82+
```
83+
instead of performing 4 sql-queries by default Eloquent behaviour:
84+
85+
```sql
86+
select `street_images`.* from `street_images` order by `sort` asc limit 1
87+
88+
select `streets`.* from `streets` where `streets`.`id` in (?) order by `title` asc
89+
90+
select * from `street_types` where `street_types`.`id` in (?) order by `title` asc
91+
92+
select `streets`.* from `streets` where `streets`.`id` in (?) order by `title` asc
93+
```
94+
1395
### Installation
1496

15-
1. Use `\SleepingOwl\WithJoin\WithJoinTrait` trait in your eloquent model:
97+
1. Require this package in your composer.json and run composer update (or run `composer require sleeping-owl/with-join:1.x` directly):
98+
99+
"sleeping-owl/with-join": "1.*"
100+
101+
2. Use `\SleepingOwl\WithJoin\WithJoinTrait` trait in every eloquent model you want to use this package features:
16102

17103
```php
18104
class StreetImage extends \Eloquent
@@ -21,12 +107,7 @@
21107
}
22108
```
23109

24-
2. That`s all.
25-
26-
### Todo
27-
28-
- Nested relations support
29-
- Other relation types support
110+
3. That`s all.
30111

31112
## Copyright and License
32113

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sleeping-owl/with-join",
3-
"description": "Package to convert BelongsTo subqueries into one query with left join.",
3+
"description": "Package to convert Eloquent BelongsTo subqueries into one query with left join.",
44
"keywords": ["laravel", "activerecord", "eloquent", "with", "join"],
55
"license": "MIT",
66
"authors": [

src/SleepingOwl/WithJoin/WithJoinEloquentBuilder.php

+38-7
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ public function getModels($columns = ['*'])
1919
{
2020
foreach ($this->eagerLoad as $name => $constraints)
2121
{
22-
if ($this->isNestedRelation($name) || count($this->nestedRelations($name)) > 0)
22+
if ($this->isNestedRelation($name))
2323
{
2424
continue;
2525
}
2626
$relation = $this->getRelation($name);
2727
if ($this->isReferencedInQuery($name) && $this->isRelationSupported($relation))
2828
{
2929
$this->disableEagerLoad($name);
30-
$this->addJoinToQuery($name, $relation);
30+
$this->addJoinToQuery($name, $this->model->getTable(), $relation);
31+
$this->addNestedRelations($name, $relation);
3132
}
3233
}
3334
$this->selectFromQuery($this->model->getTable(), '*');
@@ -43,18 +44,34 @@ protected function isNestedRelation($relation)
4344
return strpos($relation, '.') !== false;
4445
}
4546

47+
/**
48+
* @param $name
49+
*/
50+
protected function addNestedRelations($name, BelongsTo $relation)
51+
{
52+
$nestedRelations = $this->nestedRelations($name);
53+
if (count($nestedRelations) <= 0) return;
54+
55+
$class = $relation->getRelated();
56+
foreach ($nestedRelations as $nestedName => $nestedConstraints)
57+
{
58+
$relation = $class->$nestedName();
59+
$this->addJoinToQuery($nestedName, $class->getTable(), $relation, $name . '._foreign_');
60+
}
61+
}
62+
4663
/**
4764
* @param $name
4865
* @param BelongsTo $relation
4966
*/
50-
protected function addJoinToQuery($name, BelongsTo $relation)
67+
protected function addJoinToQuery($name, $currentTable, BelongsTo $relation, $prefix = '')
5168
{
5269
$foreignTable = $relation->getRelated()->getTable();
5370
$columns = $this->getColumns($foreignTable);
54-
$this->query->leftJoin($foreignTable . ' as ' . $name, $name . '.' . $relation->getOtherKey(), '=', $relation->getForeignKey());
71+
$this->query->leftJoin($foreignTable, $foreignTable . '.' . $relation->getOtherKey(), '=', $currentTable . '.' . $relation->getForeignKey());
5572
foreach ($columns as $column)
5673
{
57-
$this->selectFromQuery($name, $column, '_foreign_' . $name . '.' . $column);
74+
$this->selectFromQuery($foreignTable, $column, '_foreign_' . $prefix . $name . '.' . $column);
5875
};
5976
}
6077

@@ -64,7 +81,18 @@ protected function addJoinToQuery($name, BelongsTo $relation)
6481
*/
6582
protected function isReferencedInQuery($name)
6683
{
67-
return in_array($name, $this->references);
84+
if (in_array($name, $this->references))
85+
{
86+
return true;
87+
}
88+
foreach ($this->references as $reference)
89+
{
90+
if (strpos($reference, $name . '.') === 0)
91+
{
92+
return true;
93+
}
94+
}
95+
return false;
6896
}
6997

7098
/**
@@ -91,7 +119,10 @@ protected function disableEagerLoad($name)
91119
*/
92120
protected function selectFromQuery($table, $column, $as = null)
93121
{
94-
$string = implode('.', [$table, $column]);
122+
$string = implode('.', [
123+
$table,
124+
$column
125+
]);
95126
if ( ! is_null($as))
96127
{
97128
$string .= ' as ' . $as;
+20-10
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
11
<?php namespace SleepingOwl\WithJoin;
22

33
use Illuminate\Database\Eloquent\Relations\BelongsTo;
4+
use Illuminate\Support\Arr;
45

5-
trait WithJoinTrait {
6+
trait WithJoinTrait
7+
{
68

79
/**
810
* @param array $attributes
911
* @return static
1012
*/
1113
public function newFromBuilder($attributes = [])
1214
{
15+
$attributes = (array) $attributes;
1316
$prefix = '_foreign_';
1417
$foreignData = [];
1518
foreach ($attributes as $key => $value)
1619
{
1720
if (strpos($key, $prefix) === 0)
1821
{
19-
unset($attributes->$key);
22+
unset($attributes[$key]);
2023
$key = substr($key, strlen($prefix));
21-
list($table, $key) = explode('.', $key, 2);
22-
if ( ! isset($foreignData[$table]))
23-
{
24-
$foreignData[$table] = [];
25-
}
26-
$foreignData[$table][$key] = $value;
24+
Arr::set($foreignData, $key, $value);
2725
}
2826
}
2927
foreach ($foreignData as $relation => $data)
3028
{
3129
/** @var BelongsTo $relationInstance */
3230
$relationInstance = $this->$relation();
3331
$foreign = $relationInstance->getRelated()->newFromBuilder($data);
34-
$attributes->$relation = $foreign;
32+
$attributes[$relation] = $foreign;
3533
}
3634
return parent::newFromBuilder($attributes);
3735
}
@@ -48,7 +46,7 @@ public function newEloquentBuilder($query)
4846
/**
4947
* Being querying a model with eager loading.
5048
*
51-
* @param array|string $relations
49+
* @param array|string $relations
5250
* @return \Illuminate\Database\Eloquent\Builder|WithJoinEloquentBuilder|static
5351
*/
5452
public static function with($relations)
@@ -57,4 +55,16 @@ public static function with($relations)
5755
return parent::with($relations);
5856
}
5957

58+
/**
59+
* @param array|string $relations
60+
* @return \Illuminate\Database\Eloquent\Builder|WithJoinEloquentBuilder|static
61+
*/
62+
public static function includes($relations)
63+
{
64+
if (is_string($relations)) $relations = func_get_args();
65+
$query = parent::with($relations);
66+
$query->references($relations);
67+
return $query;
68+
}
69+
6070
}

0 commit comments

Comments
 (0)