Skip to content

Commit 81b37bd

Browse files
committed
DB: Result - move parsing column values into separate object [WIP: docs]
1 parent e1d8a4b commit 81b37bd

File tree

12 files changed

+137
-93
lines changed

12 files changed

+137
-93
lines changed

docs/db.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -314,15 +314,6 @@ dump($columns); // (array) ['id', 'nick', 'active']
314314

315315
All row data (columns) are automatically parsed to the correct PHP types (detected from the DB column type - but no DB structure is read, PG send type of all columns in the result).
316316

317-
You can get your own data (manually passed, not from DB) parsed to the same type as have the column in the result:
318-
319-
```php
320-
$result = $connection->query('SELECT id, nick, active FROM users');
321-
322-
$data = $result->parseColumnValue('id', '123');
323-
dump($data); // (integer) 123
324-
```
325-
326317
On the result object, we can also check what columns were accessed in our application. You can check this before your request ends, and you can get possible columns that are unnecessary to be selected from the DB:
327318

328319
```php

phpcs-ignores.neon

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ ignoreErrors:
9696
path: src/Db/AsyncQuery.php
9797

9898
-
99-
sniff: Squiz.Scope.MethodScope.Missing
100-
message: Visibility must be declared on method "parseColumnValue"
99+
sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal
100+
message: All classes should be declared using either the "abstract" or "final" keyword.
101101
count: 1
102102
path: src/Db/ColumnValueParser.php
103103

@@ -141,19 +141,13 @@ ignoreErrors:
141141
sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal
142142
message: All classes should be declared using either the "abstract" or "final" keyword.
143143
count: 1
144-
path: src/Db/DummyColumnValueParser.php
145-
146-
-
147-
sniff: SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
148-
message: Unused parameter $column.
149-
count: 1
150-
path: src/Db/DummyColumnValueParser.php
144+
path: src/Db/Events.php
151145

152146
-
153147
sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal
154148
message: All classes should be declared using either the "abstract" or "final" keyword.
155149
count: 1
156-
path: src/Db/Events.php
150+
path: src/Db/Exceptions/ColumnValueParserException.php
157151

158152
-
159153
sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal

src/Db/ColumnValueParser.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,49 @@
22

33
namespace Forrest79\PhPgSql\Db;
44

5-
interface ColumnValueParser
5+
class ColumnValueParser
66
{
7+
private DataTypeParser $dataTypeParser;
78

8-
function parseColumnValue(string $column, mixed $rawValue): mixed;
9+
/** @var array<string, string> */
10+
private array $columnsDataTypes;
11+
12+
/** @var array<string, bool> */
13+
private array $parsedColumns = [];
14+
15+
16+
/**
17+
* @param array<string, string> $columnsDataTypes
18+
*/
19+
public function __construct(DataTypeParser $dataTypeParser, array $columnsDataTypes)
20+
{
21+
$this->dataTypeParser = $dataTypeParser;
22+
$this->columnsDataTypes = $columnsDataTypes;
23+
}
24+
25+
26+
/**
27+
* @throws Exceptions\ColumnValueParserException
28+
*/
29+
public function parseColumnValue(string $column, string|null $rawValue): mixed
30+
{
31+
$value = $this->dataTypeParser->parse(
32+
$this->columnsDataTypes[$column] ?? throw Exceptions\ColumnValueParserException::noColumn($column),
33+
$rawValue,
34+
);
35+
36+
$this->parsedColumns[$column] = true;
37+
38+
return $value;
39+
}
40+
41+
42+
/**
43+
* @return list<string>
44+
*/
45+
public function getParsedColumns(): array
46+
{
47+
return array_keys($this->parsedColumns);
48+
}
949

1050
}

src/Db/DummyColumnValueParser.php

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Forrest79\PhPgSql\Db\Exceptions;
4+
5+
class ColumnValueParserException extends Exception
6+
{
7+
public const int NO_COLUMN = 1;
8+
9+
10+
public static function noColumn(string $column): self
11+
{
12+
return new self(\sprintf('There is no column \'%s\'.', $column), self::NO_COLUMN);
13+
}
14+
15+
}

src/Db/Exceptions/RowException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class RowException extends Exception
66
{
77
public const int NO_COLUMN = 1;
88
public const int NOT_STRING_KEY = 2;
9+
public const int COLUMN_VALUE_PARSER_MISSING = 3;
910

1011

1112
public static function noColumn(string $column): self
@@ -19,4 +20,10 @@ public static function notStringKey(): self
1920
return new self('Requested key must be string.', self::NOT_STRING_KEY);
2021
}
2122

23+
24+
public static function columnValueParserMissing(): self
25+
{
26+
return new self('You must pass the ColumnValueParser, if there are some rawValues.', self::COLUMN_VALUE_PARSER_MISSING);
27+
}
28+
2229
}

src/Db/Result.php

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use PgSql;
66

7-
class Result implements ColumnValueParser, \Countable
7+
class Result implements \Countable
88
{
99
protected PgSql\Result $queryResource;
1010

@@ -28,8 +28,7 @@ class Result implements ColumnValueParser, \Countable
2828
/** @var array<string, string>|null */
2929
private array|null $columnsDataTypes = null;
3030

31-
/** @var array<string, bool> */
32-
private array $parsedColumns = [];
31+
private ColumnValueParser|null $columnValueParser = null;
3332

3433

3534
/**
@@ -126,11 +125,7 @@ public function fetch(): Row|null
126125
return null;
127126
}
128127

129-
// Detecting column data types need valid query result and it can be closed before columns data types
130-
// ...are detected, that's why we're detecting it with the first fetch (before first row is created)
131-
$this->detectColumnDataTypes();
132-
133-
$row = $this->rowFactory->create($this, $data);
128+
$row = $this->rowFactory->create($this->getColumnValueParser(), $data);
134129

135130
if ($this->rowFetchMutator !== null) {
136131
call_user_func($this->rowFetchMutator, $row);
@@ -406,30 +401,31 @@ public function getColumns(): array
406401
}
407402

408403

409-
public function parseColumnValue(string $column, mixed $rawValue): mixed
404+
/**
405+
* @return array<string, bool>|null null = no fetch was called yet
406+
*/
407+
public function getParsedColumns(): array|null
410408
{
411-
\assert(($rawValue === null) || \is_string($rawValue)); // database result all values as string or null
412-
$value = $this->dataTypeParser->parse($this->getColumnType($column), $rawValue);
409+
return $this->columnValueParser === null
410+
? null
411+
: \array_fill_keys($this->columnValueParser->getParsedColumns(), true) + \array_fill_keys($this->getColumns(), false);
412+
}
413413

414-
$this->parsedColumns[$column] = true;
415414

416-
return $value;
415+
private function getColumnValueParser(): ColumnValueParser
416+
{
417+
if ($this->columnValueParser === null) {
418+
$this->columnValueParser = new ColumnValueParser($this->dataTypeParser, $this->getColumnsDataTypes());
419+
}
420+
421+
return $this->columnValueParser;
417422
}
418423

419424

420425
/**
421426
* @return array<string, string>
422427
*/
423428
private function getColumnsDataTypes(): array
424-
{
425-
$this->detectColumnDataTypes();
426-
\assert($this->columnsDataTypes !== null);
427-
428-
return $this->columnsDataTypes;
429-
}
430-
431-
432-
private function detectColumnDataTypes(): void
433429
{
434430
if ($this->columnsDataTypes === null) {
435431
$this->columnsDataTypes = [];
@@ -455,17 +451,8 @@ private function detectColumnDataTypes(): void
455451
$this->columnsDataTypes[$name] = $type;
456452
}
457453
}
458-
}
459-
460454

461-
/**
462-
* @return array<string, bool>|null null = no column was used
463-
*/
464-
public function getParsedColumns(): array|null
465-
{
466-
return $this->parsedColumns === []
467-
? null
468-
: $this->parsedColumns + \array_fill_keys($this->getColumns(), false);
455+
return $this->columnsDataTypes;
469456
}
470457

471458
}

src/Db/Row.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99
class Row implements \ArrayAccess, \IteratorAggregate, \Countable, \JsonSerializable
1010
{
11-
private ColumnValueParser $columnValueParser;
11+
private ColumnValueParser|null $columnValueParser;
1212

1313
/** @var array<string, string|null> */
1414
private array $rawValues;
@@ -20,8 +20,12 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable, \JsonSerializ
2020
/**
2121
* @param array<string, string|null> $rawValues
2222
*/
23-
public function __construct(ColumnValueParser $columnValueParser, array $rawValues)
23+
public function __construct(ColumnValueParser|null $columnValueParser, array $rawValues)
2424
{
25+
if ($columnValueParser === null && $rawValues !== []) {
26+
throw Exceptions\RowException::columnValueParserMissing();
27+
}
28+
2529
$this->columnValueParser = $columnValueParser;
2630
$this->rawValues = $rawValues;
2731

@@ -185,14 +189,15 @@ public function __serialize(): array
185189
*/
186190
public function __unserialize(array $values): void
187191
{
188-
$this->columnValueParser = new DummyColumnValueParser();
192+
$this->columnValueParser = null;
189193
$this->rawValues = [];
190194
$this->values = $values;
191195
}
192196

193197

194198
private function parseValue(string $column): void
195199
{
200+
assert($this->columnValueParser !== null);
196201
$this->values[$column] = $this->columnValueParser->parseColumnValue($column, $this->rawValues[$column]);
197202
unset($this->rawValues[$column]);
198203
}
@@ -226,7 +231,9 @@ public function jsonSerialize(): array
226231
*/
227232
public static function from(array $values): static
228233
{
229-
return new static(new DummyColumnValueParser(), $values);
234+
$row = new static(null, []);
235+
$row->values = $values;
236+
return $row;
230237
}
231238

232239
}

tests/Integration/BasicTest.php

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -266,24 +266,6 @@ public function testResultGetQuery(): void
266266
}
267267

268268

269-
public function testRowFrom(): void
270-
{
271-
$row = Db\Row::from(['column1' => 1, 'column2' => 'text', 'column3' => true, 'column4' => null]);
272-
273-
Tester\Assert::same(1, $row->column1);
274-
Tester\Assert::same('text', $row->column2);
275-
Tester\Assert::same(true, $row->column3);
276-
Tester\Assert::same(null, $row->column4);
277-
278-
Tester\Assert::true(isset($row->column3));
279-
Tester\Assert::false(isset($row->column4));
280-
Tester\Assert::true($row->hasColumn('column4'));
281-
282-
$blankRow = Db\Row::from([]);
283-
Tester\Assert::same([], $blankRow->toArray());
284-
}
285-
286-
287269
public function testRowSerialize(): void
288270
{
289271
$this->connection->query('

tests/Integration/CollectingResultsTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ public function testResultCollector(): void
4949

5050
$resultSelect = $results[2];
5151

52-
// Try also parse some non-existing column
53-
Tester\Assert::exception(static function () use ($resultSelect): void {
54-
$resultSelect->parseColumnValue('non_existing_column', 'someValue');
55-
}, Db\Exceptions\ResultException::class, code: Db\Exceptions\ResultException::NO_COLUMN);
56-
5752
$querySelect = $resultSelect->getQuery();
5853

5954
Tester\Assert::same('SELECT id, name FROM test', $querySelect->sql);

0 commit comments

Comments
 (0)