Skip to content

Commit c7da7b6

Browse files
authored
feat: add support sub query builder (#403)
* feat: enhance query builder to support InnerQuery type for table names * feat: add InnerQuery support and enhance type hinting in MyQuery classes * feat: enhance join classes to support sub-queries and improve alias handling * test: add SubQuery tests for where, from, and join scenarios
1 parent 62e8aaf commit c7da7b6

18 files changed

+530
-88
lines changed

src/System/Database/MyQuery.php

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace System\Database;
66

7+
use System\Database\MyQuery\InnerQuery;
78
use System\Database\MyQuery\Table;
89

910
/**
@@ -41,24 +42,24 @@ public function __invoke(string $table_name)
4142
/**
4243
* Create builder and set table name.
4344
*
44-
* @param string $table_name Table name
45+
* @param string|InnerQuery $table_name Table name
4546
*
4647
* @return Table
4748
*/
48-
public function table(string $table_name)
49+
public function table($table_name)
4950
{
5051
return new Table($table_name, $this->PDO);
5152
}
5253

5354
/**
5455
* Create Builder using static function.
5556
*
56-
* @param string $table_name Table name
57-
* @param MyPDO $PDO The PDO connection, null give global instance
57+
* @param string|InnerQuery $table_name Table name
58+
* @param MyPDO $PDO The PDO connection, null give global instance
5859
*
5960
* @return Table
6061
*/
61-
public static function from(string $table_name, MyPDO $PDO)
62+
public static function from($table_name, MyPDO $PDO)
6263
{
6364
$conn = new MyQuery($PDO);
6465

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace System\Database\MyQuery;
6+
7+
final class InnerQuery implements \Stringable
8+
{
9+
public function __construct(private ?Select $select = null, private string $table = '')
10+
{
11+
}
12+
13+
public function isSubQuery(): bool
14+
{
15+
return null !== $this->select;
16+
}
17+
18+
public function getAlias(): string
19+
{
20+
return "`{$this->table}`";
21+
}
22+
23+
/** @return Bind[] */
24+
public function getBind(): array
25+
{
26+
return $this->select->getBinds();
27+
}
28+
29+
public function __toString(): string
30+
{
31+
return $this->isSubQuery()
32+
? '(' . trim((string) $this->select) . ') AS ' . $this->getAlias()
33+
: $this->getAlias();
34+
}
35+
}

src/System/Database/MyQuery/Join/AbstractJoin.php

+34-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace System\Database\MyQuery\Join;
66

7+
use System\Database\MyQuery\InnerQuery;
8+
79
abstract class AbstractJoin
810
{
911
/**
@@ -31,6 +33,12 @@ abstract class AbstractJoin
3133
*/
3234
protected $_stringJoin = '';
3335

36+
protected ?InnerQuery $sub_query = null;
37+
38+
final public function __construct()
39+
{
40+
}
41+
3442
/**
3543
* @return self
3644
*/
@@ -49,16 +57,21 @@ public function __toString()
4957
/**
5058
* Instance of class.
5159
*
52-
* @param string $ref_table Name of the table want to join
53-
* @param string $id Main id of the table
54-
* @param string|null $ref_id Id of the table want to join, null mean same with main id
55-
*
56-
* @return AbstractJoin
60+
* @param string|InnerQuery $ref_table Name of the table want to join or sub query
61+
* @param string $id Main id of the table
62+
* @param string|null $ref_id Id of the table want to join, null means same as main id
5763
*/
58-
public static function ref(string $ref_table, string $id, ?string $ref_id = null)
64+
public static function ref($ref_table, string $id, ?string $ref_id = null): AbstractJoin
5965
{
60-
/* @phpstan-ignore-next-line */
61-
return (new static())
66+
$instance = new static();
67+
68+
if ($ref_table instanceof InnerQuery) {
69+
return $instance
70+
->clausa($ref_table)
71+
->compare($id, $ref_id);
72+
}
73+
74+
return $instance
6275
->tableRef($ref_table)
6376
->compare($id, $ref_id);
6477
}
@@ -79,6 +92,14 @@ public function table(string $main_table)
7992
return $this;
8093
}
8194

95+
public function clausa(InnerQuery $select): self
96+
{
97+
$this->sub_query = $select;
98+
$this->_tableName = $select->getAlias();
99+
100+
return $this;
101+
}
102+
82103
/**
83104
* Set table reference.
84105
*
@@ -167,4 +188,9 @@ protected function splitJoin(): string
167188

168189
return implode(' AND ', $on);
169190
}
191+
192+
protected function getAlias(): string
193+
{
194+
return null === $this->sub_query ? $this->_tableName : (string) $this->sub_query;
195+
}
170196
}

src/System/Database/MyQuery/Join/CrossJoin.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ class CrossJoin extends AbstractJoin
1111
*/
1212
protected function joinBuilder(): string
1313
{
14-
return "CROSS JOIN $this->_tableName";
14+
return "CROSS JOIN {$this->getAlias()}";
1515
}
1616
}

src/System/Database/MyQuery/Join/FullJoin.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ protected function joinBuilder(): string
1010
{
1111
$on = $this->splitJoin();
1212

13-
return "FULL OUTER JOIN $this->_tableName ON $on";
13+
return "FULL OUTER JOIN {$this->getAlias()} ON {$on}";
1414
}
1515
}

src/System/Database/MyQuery/Join/InnerJoin.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ protected function joinBuilder(): string
1010
{
1111
$on = $this->splitJoin();
1212

13-
return "INNER JOIN $this->_tableName ON $on";
13+
return "INNER JOIN {$this->getAlias()} ON {$on}";
1414
}
1515
}

src/System/Database/MyQuery/Join/LeftJoin.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ protected function joinBuilder(): string
1010
{
1111
$on = $this->splitJoin();
1212

13-
return "LEFT JOIN $this->_tableName ON $on";
13+
return "LEFT JOIN {$this->getAlias()} ON {$on}";
1414
}
1515
}

src/System/Database/MyQuery/Join/RightJoin.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ protected function joinBuilder(): string
1010
{
1111
$on = $this->splitJoin();
1212

13-
return "RIGHT JOIN $this->_tableName ON $on";
13+
return "RIGHT JOIN {$this->getAlias()} ON {$on}";
1414
}
1515
}

src/System/Database/MyQuery/Query.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ abstract class Query
1717
/** @var string Table Name */
1818
protected $_table = '';
1919

20+
protected ?InnerQuery $_sub_query = null;
21+
2022
/** @var string[] Columns name */
2123
protected $_column = ['*'];
2224

2325
/**
2426
* Binder array(['key', 'val']).
2527
*
26-
* @var Bind[] Binder for PDO bind */
28+
* @var Bind[] Binder for PDO bind
29+
*/
2730
protected $_binds = [];
2831

2932
/** @var int Limit start from */
@@ -89,6 +92,7 @@ abstract class Query
8992
public function reset()
9093
{
9194
$this->_table = '';
95+
$this->_sub_query = null;
9296
$this->_column = ['*'];
9397
$this->_binds = [];
9498
$this->_limit_start = 0;
@@ -172,13 +176,13 @@ protected function splitGrupsFilters(array $group_filters): string
172176
*/
173177
protected function splitFilters(array $filters): string
174178
{
175-
// mengconvert array ke string query
176-
$query = [];
179+
$query = [];
180+
$table_name = null === $this->_sub_query ? "`{$this->_table}`" : $this->_sub_query->getAlias();
177181
foreach ($filters['filters'] as $fieldName => $fieldValue) {
178182
$value = $fieldValue['value'];
179183
$comparation = $fieldValue['comparation'];
180184
if ($value !== '') {
181-
$query[] = "($this->_table.$fieldName $comparation :$fieldName)";
185+
$query[] = "({$table_name}.{$fieldName} {$comparation} :{$fieldName})";
182186
}
183187
}
184188

src/System/Database/MyQuery/Select.php

+23-13
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,31 @@ final class Select extends Fetch
1616
use SubQueryTrait;
1717

1818
/**
19-
* @param string $table_name Table name
20-
* @param string[] $columns_name Selected cloumn
21-
* @param MyPDO $PDO MyPDO class
22-
* @param string[] $options Add costume option (eg: query)
19+
* @param string|InnerQuery $table_name Table name
20+
* @param string[] $columns_name Selected cloumn
21+
* @param MyPDO $PDO MyPDO class
22+
* @param string[] $options Add costume option (eg: query)
2323
*
2424
* @return void
2525
*/
26-
public function __construct(string $table_name, array $columns_name, MyPDO $PDO, ?array $options = null)
26+
public function __construct($table_name, array $columns_name, MyPDO $PDO, ?array $options = null)
2727
{
28-
$this->_table = $table_name;
29-
$this->_column = $columns_name;
30-
$this->PDO = $PDO;
28+
$this->_sub_query = $table_name instanceof InnerQuery ? $table_name : new InnerQuery(table: $table_name);
29+
$this->_column = $columns_name;
30+
$this->PDO = $PDO;
31+
32+
// inherit bind from sub query
33+
if ($table_name instanceof InnerQuery) {
34+
$this->_binds = $table_name->getBind();
35+
}
3136

3237
// defaul query
3338
if (count($this->_column) > 1) {
3439
$this->_column = array_map(fn ($e) => "`$e`", $this->_column);
3540
}
3641

3742
$column = implode(', ', $columns_name);
38-
$this->_query = $options['query'] ?? "SELECT $column FROM `$this->_table`";
43+
$this->_query = $options['query'] ?? "SELECT {$column} FROM `{ $this->_sub_query}`";
3944
}
4045

4146
public function __toString()
@@ -67,9 +72,14 @@ public static function from(string $table_name, array $column_name, MyPDO $PDO)
6772
public function join(AbstractJoin $ref_table): self
6873
{
6974
// overide master table
70-
$ref_table->table($this->_table);
75+
$ref_table->table($this->_sub_query->getAlias());
7176

7277
$this->_join[] = $ref_table->stringJoin();
78+
$binds = (fn () => $this->{'sub_query'})->call($ref_table);
79+
80+
if (null !== $binds) {
81+
$this->_binds = array_merge($this->_binds, $binds->getBind());
82+
}
7383

7484
return $this;
7585
}
@@ -161,8 +171,8 @@ public function limitOffset(int $limit, int $offset): self
161171
public function order(string $column_name, int $order_using = MyQuery::ORDER_ASC, ?string $belong_to = null)
162172
{
163173
$order = 0 === $order_using ? 'ASC' : 'DESC';
164-
$belong_to ??= $this->_table;
165-
$this->_sort_order = "ORDER BY `$belong_to`.`$column_name` $order";
174+
$belong_to ??= null === $this->_sub_query ? "`{$this->_table}`" : $this->_sub_query->getAlias();
175+
$this->_sort_order = "ORDER BY $belong_to.`$column_name` $order";
166176

167177
return $this;
168178
}
@@ -183,7 +193,7 @@ protected function builder(): string
183193

184194
$condition = implode(' ', array_filter($build, fn ($item) => $item !== ''));
185195

186-
return $this->_query = "SELECT $column FROM `$this->_table` $condition";
196+
return $this->_query = "SELECT {$column} FROM {$this->_sub_query} {$condition}";
187197
}
188198

189199
/**

src/System/Database/MyQuery/Table.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ class Table
1818
/**
1919
* Table name.
2020
*
21-
* @var string
21+
* @var string|InnerQuery
2222
*/
2323
protected $table_name;
2424

25-
public function __construct(string $table_name, MyPDO $PDO)
25+
/**
26+
* @param string|InnerQuery $table_name Table name
27+
*/
28+
public function __construct($table_name, MyPDO $PDO)
2629
{
2730
$this->table_name = $table_name;
2831
$this->PDO = $PDO;

src/System/Database/MyQuery/Traits/ConditionTrait.php

+6-6
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ public function where(string $where_condition, array $binder = [])
7575
*/
7676
public function between(string $column_name, $value_1, $value_2)
7777
{
78+
$table_name = null === $this->_sub_query ? "`{$this->_table}`" : $this->_sub_query->getAlias();
79+
7880
$this->where(
79-
"(`$this->_table`.`$column_name` BETWEEN :b_start AND :b_end)",
80-
[
81-
// [':b_start', $value_1],
82-
// [':b_end', $value_2],
83-
]
81+
"({$table_name}.`{$column_name}` BETWEEN :b_start AND :b_end)",
82+
[]
8483
);
8584

8685
$this->_binds[] = Bind::set('b_start', $value_1);
@@ -106,9 +105,10 @@ public function in(string $column_name, $value)
106105
$binder[] = [":in_$key", $bind];
107106
}
108107
$bindString = implode(', ', $binds);
108+
$table_name = null === $this->_sub_query ? "`{$this->_table}`" : $this->_sub_query->getAlias();
109109

110110
$this->where(
111-
"(`$this->_table`.`$column_name` IN ($bindString))",
111+
"({$table_name}.`{$column_name}` IN ({$bindString}))",
112112
$binder
113113
);
114114

src/System/Database/MyQuery/Where.php

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class Where
1515
/** @var string Table Name */
1616
private $_table;
1717

18+
/** This property use for helper phpstan (auto skip) */
19+
private ?InnerQuery $_sub_query = null;
20+
1821
/**
1922
* Binder array(['key', 'val']).
2023
*

0 commit comments

Comments
 (0)