Skip to content

Commit adcccd0

Browse files
committed
ADD new nestMany, linkFirst and linkLast syntax for easier nesting
1 parent db4a68e commit adcccd0

File tree

5 files changed

+147
-47
lines changed

5 files changed

+147
-47
lines changed

README.md

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -438,35 +438,60 @@ For this reason there's a syntax that creates the most lightweight and efficient
438438
way to query nested data, preventing access overhead and reducing processing time.
439439

440440
```php
441-
// Students query
442-
$studentsQuery = Query::selectFrom(
443-
'students',
444-
['student_id', 'group_id', 'first_name', 'last_name']
445-
);
446441

447442
// Groups query
448443
$groupsQuery = Query::selectFrom(
449444
'groups',
450-
['group_id', 'subject', 'teacher']
445+
['group_id', 'teacher_id', 'subject', 'classroom']
451446
);
452447

448+
// NESTING A COLLECTION OF RESULT OBJECTS
449+
// Students query
450+
$studentsQuery = Query::selectFrom(
451+
'students',
452+
['student_id', 'group_id', 'first_name', 'last_name']
453+
);
453454
// Nesting students by each group
454-
$groupsQuery->nest(['Students' => $studentsQuery], function (NestedSelect $nest, RowProxy $row) {
455-
$nest->getSelect()->where('s.group_id', $row->group_id);
456-
});
455+
$groupsQuery
456+
->nestMany('Students', $studentsQuery, $groupRow, Student::class)
457+
->where('s.group_id', $groupRow->group_id);
458+
459+
// NESTING THE FIRST RESULT OBJECT
460+
// Teachers Query
461+
$teachersQuery = Query::selectFrom(
462+
'teachers',
463+
['teacher_id', 'first_name', 'last_name']
464+
);
465+
// Nest-link teacher by each group
466+
$groupsQuery
467+
->linkFirst('Teacher', $teachersQuery, $groupRow, Teacher::class)
468+
->where('t.teacher_id', $groupRow->teacher_id);
457469

458470
$db = DatabaseManager::connect('school');
459471
$result = $db->executeSelect($groupsQuery);
460472
$groups = $result->toArray();
461473
```
462474

475+
> **Old Nest Syntax**
476+
> ```php
477+
> $groupsQuery->nest(['Students' => $studentsQuery], function > (NestedSelect $nest, RowProxy $row) {
478+
> $nest->getSelect()->where('s.group_id', $row->group_id);
479+
> }, NestMode::COLLECTION, Student::class);
480+
> ```
481+
463482
Result would be like this:
464483
```json
465484
[
466485
{
467486
"group_id": 1,
487+
"teacher_id": 3,
468488
"subject": "Programing fundamentals",
469-
"teacher": "Rosemary",
489+
"classroom": "A113",
490+
"Teacher": {
491+
"teacher_id": 3,
492+
"first_name": "Rosemary",
493+
"last_name": "Smith"
494+
},
470495
"Students": [
471496
{
472497
"student_id": 325,
@@ -482,8 +507,14 @@ Result would be like this:
482507
},
483508
{
484509
"group_id": 2,
510+
"teacher_id": 75,
485511
"subject" : "Object Oriented Programming",
486-
"teacher": "Steve",
512+
"classroom": "G7-R5",
513+
"Teacher": {
514+
"teacher_id": 75,
515+
"first_name": "Steve",
516+
"last_name": "Johnson"
517+
},
487518
"Students": [
488519
{
489520
"student_id": 536,

src/Components/Nest.php

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,40 @@
22

33
namespace Francerz\SqlBuilder\Components;
44

5+
use Francerz\SqlBuilder\Expressions\Logical\ConditionList;
56
use Francerz\SqlBuilder\Nesting\NestedSelect;
67
use Francerz\SqlBuilder\Nesting\NestMode;
78
use Francerz\SqlBuilder\Nesting\RowProxy;
9+
use Francerz\SqlBuilder\SelectQuery;
810
use stdClass;
911

1012
class Nest
1113
{
1214
private $alias;
1315
private $nested;
14-
private $callback;
16+
private $where;
1517
private $rowProxy;
1618
private $mode;
1719
private $className;
1820

1921
public function __construct(
2022
string $alias,
21-
callable $callback,
22-
?NestedSelect $nested = null,
23+
NestedSelect $nested,
2324
$mode = NestMode::COLLECTION,
2425
string $className = stdClass::class
2526
) {
27+
$this->rowProxy = new RowProxy();
2628
$this->alias = $alias;
27-
$this->callback = $callback;
2829
$this->nested = $nested;
2930
$this->mode = NestMode::coerce($mode);
3031
$this->className = $className;
3132
}
3233

33-
public function init()
34-
{
35-
$this->rowProxy = new RowProxy();
36-
call_user_func($this->callback, $this->nested, $this->rowProxy);
37-
}
38-
3934
public function getAlias()
4035
{
4136
return $this->alias;
4237
}
4338

44-
public function getCallback()
45-
{
46-
return $this->callback;
47-
}
48-
4939
public function getNested()
5040
{
5141
return $this->nested;
@@ -65,4 +55,10 @@ public function getClassName()
6555
{
6656
return $this->className;
6757
}
58+
59+
public function where()
60+
{
61+
$args = func_get_args();
62+
return call_user_func_array([$this->nested->getSelect(), 'where'], $args);
63+
}
6864
}

src/Nesting/NestMerger.php

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function merge(SelectResult $parents, SelectResult $children, Nest $nest)
2626
$childRow = new RowProxy();
2727
$alias = $nest->getAlias();
2828
$query = $nest->getNested()->getSelect();
29-
$query = $this->placeholdSelect($query, $childRow);
29+
$query = static::placeholdSelect($query, $childRow);
3030
$mode = $nest->getMode();
3131
$className = $nest->getClassName();
3232

@@ -41,8 +41,8 @@ public function merge(SelectResult $parents, SelectResult $children, Nest $nest)
4141
foreach ($subchildren as $child) {
4242
$childRow->setCurrent($child);
4343
if (
44-
$this->mergeConditionList($query->where()) &&
45-
$this->mergeConditionList($query->having())
44+
static::mergeConditionList($query->where()) &&
45+
static::mergeConditionList($query->having())
4646
) {
4747
$childs[] = $child;
4848
if ($mode->is(NestMode::SINGLE_FIRST)) {
@@ -59,39 +59,39 @@ public function merge(SelectResult $parents, SelectResult $children, Nest $nest)
5959
}
6060
}
6161

62-
public function placeholdSelect(SelectQuery $query, RowProxy $rowProxy)
62+
public static function placeholdSelect(SelectQuery $query, RowProxy $rowProxy)
6363
{
6464
$query = clone $query;
65-
$query->setWhere($this->placeholdConditionList($query->where(), $rowProxy));
66-
$query->setHaving($this->placeholdConditionList($query->having(), $rowProxy));
65+
$query->setWhere(static::placeholdConditionList($query->where(), $rowProxy));
66+
$query->setHaving(static::placeholdConditionList($query->having(), $rowProxy));
6767
return $query;
6868
}
6969

70-
public function placeholdConditionList(ConditionList $conditions, RowProxy $rowProxy)
70+
public static function placeholdConditionList(ConditionList $conditions, RowProxy $rowProxy)
7171
{
7272
$newConds = new ConditionList();
7373
foreach ($conditions as $cond) {
7474
$cnd = $cond->getCondition();
7575
if ($cnd instanceof ConditionList) {
76-
$this->placeholdConditionList($cnd, $rowProxy);
76+
static::placeholdConditionList($cnd, $rowProxy);
7777
} elseif ($cnd instanceof OneOperandInterface) {
78-
$op = $this->placeholdOperand($cnd->getOperand(), $rowProxy);
78+
$op = static::placeholdOperand($cnd->getOperand(), $rowProxy);
7979
if (!isset($op)) {
8080
continue;
8181
}
8282
$cnd->setOperand($op);
8383
} elseif ($cnd instanceof TwoOperandsInterface) {
84-
$op1 = $this->placeholdOperand($cnd->getOperand1(), $rowProxy);
85-
$op2 = $this->placeholdOperand($cnd->getOperand2(), $rowProxy);
84+
$op1 = static::placeholdOperand($cnd->getOperand1(), $rowProxy);
85+
$op2 = static::placeholdOperand($cnd->getOperand2(), $rowProxy);
8686
if (!isset($op1, $op2)) {
8787
continue;
8888
}
8989
$cnd->setOperand1($op1);
9090
$cnd->setOperand2($op2);
9191
} elseif ($cnd instanceof ThreeOperandsInterface) {
92-
$op1 = $this->placeholdOperand($cnd->getOperand1(), $rowProxy);
93-
$op2 = $this->placeholdOperand($cnd->getOperand2(), $rowProxy);
94-
$op3 = $this->placeholdOperand($cnd->getOperand3(), $rowProxy);
92+
$op1 = static::placeholdOperand($cnd->getOperand1(), $rowProxy);
93+
$op2 = static::placeholdOperand($cnd->getOperand2(), $rowProxy);
94+
$op3 = static::placeholdOperand($cnd->getOperand3(), $rowProxy);
9595
if (!isset($op1, $op2, $op3)) {
9696
continue;
9797
}
@@ -104,7 +104,7 @@ public function placeholdConditionList(ConditionList $conditions, RowProxy $rowP
104104
return $newConds;
105105
}
106106

107-
public function placeholdOperand($operand, RowProxy $rowProxy)
107+
public static function placeholdOperand($operand, RowProxy $rowProxy)
108108
{
109109
if ($operand instanceof ValueProxy) {
110110
return $operand;
@@ -114,12 +114,12 @@ public function placeholdOperand($operand, RowProxy $rowProxy)
114114
}
115115
}
116116

117-
public function mergeConditionList(ConditionList $conditions): bool
117+
public static function mergeConditionList(ConditionList $conditions): bool
118118
{
119119
$result = true;
120120
foreach ($conditions as $cond) {
121121
$cnd = $cond->getCondition();
122-
$res = $this->handleBooleanResult($cnd);
122+
$res = static::handleBooleanResult($cnd);
123123
if ($cnd instanceof NegatableInterface && $cnd->isNegated()) {
124124
$res = !$res;
125125
}
@@ -135,10 +135,10 @@ public function mergeConditionList(ConditionList $conditions): bool
135135
return $result;
136136
}
137137

138-
public function handleBooleanResult(BooleanResultInterface $bool): bool
138+
public static function handleBooleanResult(BooleanResultInterface $bool): bool
139139
{
140140
if ($bool instanceof ConditionList) {
141-
return $this->mergeConditionList($bool);
141+
return static::mergeConditionList($bool);
142142
} elseif ($bool instanceof NestOperationResolverInterface) {
143143
return $bool->nestResolve();
144144
}

src/Traits/NestableTrait.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Francerz\SqlBuilder\Components\Nest;
66
use Francerz\SqlBuilder\Nesting\NestedSelect;
77
use Francerz\SqlBuilder\Nesting\NestMode;
8+
use Francerz\SqlBuilder\Nesting\RowProxy;
89
use Francerz\SqlBuilder\SelectQuery;
910
use stdClass;
1011

@@ -41,8 +42,8 @@ public function nest($alias, callable $callback, $mode = NestMode::COLLECTION, s
4142
if (!$query instanceof NestedSelect) {
4243
$query = new NestedSelect($query);
4344
}
44-
$nest = new Nest($alias, $callback, $query, $mode, $className);
45-
$nest->init();
45+
$nest = new Nest($alias, $query, $mode, $className);
46+
call_user_func($callback, $query, $nest->getRowProxy());
4647
$this->nests[] = $nest;
4748
return $this;
4849
}
@@ -51,4 +52,28 @@ public function getNests()
5152
{
5253
return $this->nests;
5354
}
55+
56+
public function nestMany(string $alias, SelectQuery $query, ?RowProxy &$row, string $className = stdClass::class)
57+
{
58+
$nest = new Nest($alias, new NestedSelect($query), NestMode::COLLECTION, $className);
59+
$row = $nest->getRowProxy();
60+
$this->nests[] = $nest;
61+
return $nest;
62+
}
63+
64+
public function linkFirst(string $alias, SelectQuery $query, ?RowProxy &$row, string $className = stdClass::class)
65+
{
66+
$nest = new Nest($alias, new NestedSelect($query), NestMode::SINGLE_FIRST, $className);
67+
$row = $nest->getRowProxy();
68+
$this->nests[] = $nest;
69+
return $nest;
70+
}
71+
72+
public function linkLast(string $alias, SelectQuery $query, ?RowProxy &$row, string $className = stdClass::class)
73+
{
74+
$nest = new Nest($alias, new NestedSelect($query), NestMode::SINGLE_LAST, $className);
75+
$row = $nest->getRowProxy();
76+
$this->nests[] = $nest;
77+
return $nest;
78+
}
5479
}

test/SelectNestableTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Francerz\SqlBuilder\Results\SelectResult;
1313
use Francerz\SqlBuilder\SelectQuery;
1414
use PHPUnit\Framework\TestCase;
15+
use stdClass;
1516

1617
class SelectNestableTest extends TestCase
1718
{
@@ -117,4 +118,51 @@ public function testNestable()
117118

118119
$this->assertEquals($groupsNested, $groups);
119120
}
121+
122+
public function getNewGroupsQuery()
123+
{
124+
$query = Query::selectFrom('groups AS g', ['group_id', 'subject', 'teacher']);
125+
$query->where()->in('g.group_id', [3, 7, 11]);
126+
127+
$studentsQuery = Query::selectFrom('students AS s');
128+
$studentsQuery->innerJoin('groups_students AS gs', ['group_id'])
129+
->on('s.student_id', 'gs.student_id');
130+
$query->nestMany('Students', $studentsQuery, $row, stdClass::class)
131+
->where('gs.group_id', $row->group_id);
132+
return $query;
133+
}
134+
135+
public function testNestMany()
136+
{
137+
$nestTranslator = new NestTranslator();
138+
$compiler = new QueryCompiler();
139+
$merger = new NestMerger();
140+
141+
$query = $this->getNewGroupsQuery();
142+
$groups = $this->getGroupsExpectedResult();
143+
$nests = $query->getNests();
144+
$this->assertCount(1, $nests);
145+
146+
$nest = $nests[0];
147+
if (!$nest instanceof Nest) {
148+
return;
149+
}
150+
151+
$nested = $nest->getNested();
152+
$nestSelect = $nested->getSelect();
153+
$nestTranslate = $nestTranslator->translate($nestSelect, $groups);
154+
155+
$nestCompiled = $compiler->compileSelect($nestTranslate);
156+
$expected = 'SELECT s.*, gs.group_id FROM students AS s INNER JOIN groups_students AS gs ON s.student_id = gs.student_id WHERE gs.group_id IN (:v1, :v2, :v3)';
157+
$this->assertEquals($expected, $nestCompiled->getQuery());
158+
$this->assertEquals(['v1' => 3, 'v2' => 7, 'v3' => 11], $nestCompiled->getValues());
159+
160+
$students = $this->getStudentsExpectedResult($nestTranslate);
161+
162+
$merger->merge($groups, $students, $nest);
163+
$groupsNested = $this->getGroupsNestedResult($query);
164+
165+
$this->assertEquals($groupsNested, $groups);
166+
167+
}
120168
}

0 commit comments

Comments
 (0)