Skip to content

Commit e151ff4

Browse files
committed
Start working on a custom query component
1 parent ea5125a commit e151ff4

File tree

11 files changed

+369
-32
lines changed

11 files changed

+369
-32
lines changed

src/Connection/Connection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ public function select(array $fields, string $table, array $where, array $orderB
1919
public function update(string $table, array $values, array $where): \PDOStatement;
2020
public function delete(string $table, array $where): \PDOStatement;
2121
public function query(string $sql, array $parameters = []): \PDOStatement;
22+
public function formatTable(string $table, string $alias = ''): string;
23+
public function formatFields(array $fields, string $table = '', string $prefix = ''): string;
2224
}

src/Connection/MySqlConnection.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,42 @@ public function delete(string $table, array $where): \PDOStatement
9898
]), $parameters);
9999
}
100100

101-
private function formatFields(array $fields): string
101+
public function formatFields(array $fields, string $table = '', string $prefix = ''): string
102102
{
103103
if (!$fields) {
104104
throw new \InvalidArgumentException('No fields provided for the query');
105105
}
106106

107-
return implode(', ', array_map(function (string $field) {
108-
return $this->escapeIdentifier($field);
107+
$format = '%2$s';
108+
109+
if ($table !== '') {
110+
$format = '%1$s.' . $format;
111+
}
112+
113+
if ($prefix !== '') {
114+
$format .= ' AS %3$s';
115+
}
116+
117+
return implode(', ', array_map(function (string $field) use ($format, $table, $prefix) {
118+
return sprintf(
119+
$format,
120+
$this->escapeIdentifier($table),
121+
$this->escapeIdentifier($field),
122+
$this->escapeIdentifier($prefix . $field)
123+
);
109124
}, $fields));
110125
}
111126

112-
private function formatTable(string $table): string
127+
public function formatTable(string $table, string $alias = ''): string
113128
{
114129
if ($table === '') {
115130
throw new \InvalidArgumentException('No table provided for the query');
116131
}
117132

133+
if ($alias !== '') {
134+
return sprintf('%s AS %s', $this->escapeIdentifier($table), $this->escapeIdentifier($alias));
135+
}
136+
118137
return $this->escapeIdentifier($table);
119138
}
120139

src/Query.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
namespace Simply\Database;
4+
5+
use Simply\Database\Connection\Connection;
6+
7+
/**
8+
* Query.
9+
* @author Riikka Kalliomäki <[email protected]>
10+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
11+
* @license http://opensource.org/licenses/mit-license.php MIT License
12+
*/
13+
class Query
14+
{
15+
/** @var Connection */
16+
private $connection;
17+
private $sql;
18+
19+
/** @var Schema[] */
20+
private $schemas;
21+
private $parameters;
22+
23+
public function __construct(Connection $connection, string $sql)
24+
{
25+
$this->connection = $connection;
26+
$this->sql = $sql;
27+
$this->schemas = [];
28+
$this->parameters = [];
29+
}
30+
31+
public function withSchema(Schema $schema, string $alias = ''): Query
32+
{
33+
$query = clone $this;
34+
$query->schemas[$this->formatAlias($alias)] = $schema;
35+
36+
return $query;
37+
}
38+
39+
public function withParameters(array $parameters): Query
40+
{
41+
$query = clone $this;
42+
43+
foreach ($parameters as $key => $parameter) {
44+
if (\is_int($key)) {
45+
$query->parameters[] = $parameter;
46+
continue;
47+
}
48+
49+
$query->parameters[$key] = $parameter;
50+
}
51+
52+
return $query;
53+
}
54+
55+
public function withoutSchemas(): Query
56+
{
57+
$query = clone $this;
58+
$query->schemas = [];
59+
60+
return $query;
61+
}
62+
63+
public function withoutParameters(): Query
64+
{
65+
$query = clone $this;
66+
$query->parameters = [];
67+
68+
return $query;
69+
}
70+
71+
public function fetchResult(): \PDOStatement
72+
{
73+
$placeholders = [];
74+
75+
foreach ($this->schemas as $alias => $schema) {
76+
if ($alias === '') {
77+
$placeholders['{table}'] = $this->connection->formatTable($schema->getTable());
78+
$placeholders['{fields}'] = $this->connection->formatFields($schema->getFields());
79+
80+
continue;
81+
}
82+
83+
$placeholders["{{$alias}.table}"] = $this->connection->formatTable($schema->getTable(), $alias);
84+
$placeholders["{{$alias}.fields}"] = $this->connection->formatFields(
85+
$schema->getFields(),
86+
$alias,
87+
$this->formatPrefix($alias)
88+
);
89+
}
90+
91+
$query = $this->connection->query(strtr($this->sql, $placeholders), $this->parameters);
92+
$query->setFetchMode(\PDO::FETCH_ASSOC);
93+
94+
return $query;
95+
}
96+
97+
public function fetchRows(): array
98+
{
99+
return iterator_to_array($this->generateRows());
100+
}
101+
102+
public function fetchModels(string $alias = '', array $relationships = []): array
103+
{
104+
return iterator_to_array($this->generateModels($alias, $relationships));
105+
}
106+
107+
public function fetchCallback(callable $callback): array
108+
{
109+
return iterator_to_array($this->generateCallback($callback));
110+
}
111+
112+
public function generateRows(): \Generator
113+
{
114+
foreach ($this->fetchResult() as $row) {
115+
yield $row;
116+
}
117+
}
118+
119+
public function generateModels(string $alias = '', array $relationships = []): \Generator
120+
{
121+
$alias = $this->formatAlias($alias);
122+
123+
if (!isset($this->schemas[$alias])) {
124+
if ($alias !== '' || \count($this->schemas) !== 1) {
125+
throw new \InvalidArgumentException('No schema selected for generating database models');
126+
}
127+
128+
$alias = array_keys($this->schemas)[0];
129+
}
130+
131+
$schema = $this->schemas[$alias];
132+
$prefix = $this->formatPrefix($alias);
133+
$modelRelationships = [];
134+
135+
foreach ($relationships as $key => $name) {
136+
$modelRelationships[$this->formatPrefix($key)] = $name;
137+
}
138+
139+
foreach ($this->fetchResult() as $row) {
140+
yield $schema->createModel($row, $prefix, $modelRelationships);
141+
}
142+
}
143+
144+
public function generateCallback(callable $callback): \Generator
145+
{
146+
foreach ($this->fetchResult() as $row) {
147+
yield $callback($row);
148+
}
149+
}
150+
151+
private function formatAlias(string $alias): string
152+
{
153+
if ($alias === '') {
154+
return $alias;
155+
}
156+
157+
return substr($alias, -1) === '_' ? substr($alias, 0, -1) : $alias;
158+
}
159+
160+
private function formatPrefix(string $prefix): string
161+
{
162+
if ($prefix === '') {
163+
return $prefix;
164+
}
165+
166+
return substr($prefix, -1) === '_' ? $prefix : $prefix . '_';
167+
}
168+
}

src/Record.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ public function getPrimaryKey(): array
4848
return $this->primaryKey;
4949
}
5050

51+
public function isEmpty(): bool
52+
{
53+
foreach ($this->values as $value) {
54+
if ($value !== null) {
55+
return false;
56+
}
57+
}
58+
59+
return true;
60+
}
61+
5162
public function isNew(): bool
5263
{
5364
return $this->state === self::STATE_INSERT;
@@ -255,7 +266,11 @@ public function getAllReferencedRecords(): array
255266
public function setDatabaseValues(array $row)
256267
{
257268
if (array_keys($row) !== array_keys($this->values)) {
258-
throw new \InvalidArgumentException('Invalid set of record database values provided');
269+
if (array_diff_key($row, $this->values) !== [] || \count($row) !== \count($this->values)) {
270+
throw new \InvalidArgumentException('Invalid set of record database values provided');
271+
}
272+
273+
$row = array_replace($this->values, $row);
259274
}
260275

261276
$this->values = $row;

src/Relationship.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,32 @@ private function assignSortedRecords(array $records, array $sorted): void
195195
}
196196
}
197197
}
198+
199+
public function fillSingleRecord(Record $record, Record $referencedRecord): void
200+
{
201+
if (!$this->isUniqueRelationship()) {
202+
throw new \LogicException('Only unique relationships can be filled with single records');
203+
}
204+
205+
if ($referencedRecord->isEmpty()) {
206+
$record->setReferencedRecords($this->getName(), []);
207+
return;
208+
}
209+
210+
$keys = $this->getFields();
211+
$fields = $this->getReferencedFields();
212+
213+
while ($keys) {
214+
if ((string) $record[array_pop($keys)] !== (string) $referencedRecord[array_pop($fields)]) {
215+
throw new \LogicException('Tried to fill a record with a record that is not the referenced record');
216+
}
217+
}
218+
219+
$record->setReferencedRecords($this->getName(), [$referencedRecord]);
220+
$reverse = $this->getReverseRelationship();
221+
222+
if ($reverse->isUniqueRelationship()) {
223+
$referencedRecord->setReferencedRecords($reverse->getName(), [$record]);
224+
}
225+
}
198226
}

src/Repository.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,9 @@ protected function fillRelationships(array $models, array $relationships): void
151151
$filler = new RelationshipFiller($this->connection);
152152
$filler->fill($records, $relationships);
153153
}
154+
155+
protected function query(string $sql): Query
156+
{
157+
return new Query($this->connection, $sql);
158+
}
154159
}

src/Schema.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ abstract class Schema
2525

2626
private $relationshipCache;
2727

28+
private $prefixCache;
29+
2830
private $container;
2931

3032
public function __construct(ContainerInterface $container)
3133
{
3234
$this->relationshipCache = [];
3335
$this->container = $container;
36+
$this->prefixCache = [];
3437
}
3538

3639
public function getTable(): string
@@ -106,7 +109,20 @@ public function getModel(Record $record): Model
106109
return $this->model::createFromDatabaseRecord($record);
107110
}
108111

109-
public function createModel(array $row, string $prefix = ''): Model
112+
public function createModel(array $row, string $prefix = '', array $relationships = []): Model
113+
{
114+
$record = $this->getRecord($this->getPrefixedFields($row, $prefix));
115+
116+
foreach ($relationships as $key => $name) {
117+
$relationship = $this->getRelationship($name);
118+
$schema = $relationship->getReferencedSchema();
119+
$relationship->fillSingleRecord($record, $schema->getRecord($schema->getPrefixedFields($row, $key)));
120+
}
121+
122+
return $record->getModel();
123+
}
124+
125+
private function getPrefixedFields(array $row, string $prefix): array
110126
{
111127
$values = [];
112128

@@ -118,7 +134,7 @@ public function createModel(array $row, string $prefix = ''): Model
118134
}
119135
}
120136

121-
return $this->getRecord($values)->getModel();
137+
return $values;
122138
}
123139

124140
public function createRecord(Model $model = null): Record

0 commit comments

Comments
 (0)