Skip to content

Commit df35c4a

Browse files
committed
Replace FlatNum with PrestyledAssoc, use aliased SELECT columns
Switch from FETCH_NUM + getColumnMeta() to FETCH_ASSOC with explicit aliased columns (e.g. post.id AS post__id). The hydrator now parses alias keys directly, removing the PDO driver dependency. buildSelectStatement() uses EntityFactory::enumerateFields() to emit per-column aliases, and Composite::COMPOSITION_MARKER for join prefixes.
1 parent 14a84de commit df35c4a

5 files changed

Lines changed: 67 additions & 209 deletions

File tree

src/Hydrators/FlatNum.php

Lines changed: 0 additions & 55 deletions
This file was deleted.

src/Mapper.php

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
use Respect\Data\Collections\Filtered;
1515
use Respect\Data\EntityFactory;
1616
use Respect\Data\Hydrator;
17-
use Respect\Relational\Hydrators\FlatNum;
17+
use Respect\Data\Hydrators\PrestyledAssoc;
1818
use SplObjectStorage;
1919
use Throwable;
2020

2121
use function array_keys;
22-
use function array_merge;
2322
use function array_push;
2423
use function array_values;
2524
use function is_array;
@@ -32,8 +31,6 @@ final class Mapper extends AbstractMapper
3231
{
3332
public readonly Db $db;
3433

35-
private PDOStatement $lastStatement;
36-
3734
/** @var SplObjectStorage<object, true> */
3835
private SplObjectStorage $persisting;
3936

@@ -133,7 +130,7 @@ public function flush(): void
133130

134131
protected function defaultHydrator(Collection $collection): Hydrator
135132
{
136-
return new FlatNum($this->lastStatement);
133+
return new PrestyledAssoc();
137134
}
138135

139136
/** Resolve related entity from relation property or FK field */
@@ -374,48 +371,53 @@ private function buildSelectStatement(Sql $sql, array $collections): Sql
374371
{
375372
$selectTable = [];
376373
foreach ($collections as $tableSpecifier => $c) {
377-
if ($c instanceof Composite) {
378-
foreach ($c->compositions as $composition => $columns) {
379-
foreach ($columns as $col) {
380-
$selectTable[] = $tableSpecifier . '_comp' . $composition . '.' . $col;
381-
}
382-
}
383-
}
384-
385374
if ($c instanceof Filtered) {
386375
$filters = $c->filters;
387376
if ($filters) {
388-
$pkName = $tableSpecifier . '.' .
389-
$this->style->identifier($c->name);
390-
391-
if ($c->identifierOnly) {
392-
$selectColumns = [$pkName];
393-
} else {
394-
$selectColumns = [
395-
$tableSpecifier . '.' .
396-
$this->style->identifier($c->name),
397-
];
377+
$fields = $this->entityFactory->enumerateFields($c->name);
378+
$pk = $this->style->identifier($c->name);
379+
$selectTable[] = self::aliasedColumn($tableSpecifier, $pk, $fields[$pk] ?? $pk);
380+
381+
if (!$c->identifierOnly) {
398382
foreach ($filters as $f) {
399-
$selectColumns[] = $tableSpecifier . '.' . $f;
383+
$selectTable[] = self::aliasedColumn($tableSpecifier, $f, $fields[$f] ?? $f);
400384
}
401385
}
402386

403387
$nextName = $c->next?->name;
404388
if ($nextName !== null) {
405-
$selectColumns[] = $tableSpecifier . '.' .
406-
$this->style->remoteIdentifier($nextName);
389+
$fk = $this->style->remoteIdentifier($nextName);
390+
$selectTable[] = self::aliasedColumn($tableSpecifier, $fk, $fields[$fk] ?? $fk);
407391
}
408-
409-
$selectTable = array_merge($selectTable, $selectColumns);
410392
}
411393
} else {
412-
$selectTable[] = $tableSpecifier . '.*';
394+
foreach ($this->entityFactory->enumerateFields($c->name) as $dbCol => $styledProp) {
395+
$selectTable[] = self::aliasedColumn($tableSpecifier, $dbCol, $styledProp);
396+
}
397+
}
398+
399+
// Composition columns come after entity columns so they override on collision
400+
if (!$c instanceof Composite) {
401+
continue;
402+
}
403+
404+
foreach ($c->compositions as $composition => $columns) {
405+
$compPrefix = $tableSpecifier . Composite::COMPOSITION_MARKER . $composition;
406+
foreach ($columns as $col) {
407+
$selectTable[] = self::aliasedColumn($compPrefix, $col, $col);
408+
}
413409
}
414410
}
415411

416412
return $sql->select(...$selectTable);
417413
}
418414

415+
/** @return array<string, string> Alias array for Sql::select() */
416+
private static function aliasedColumn(string $specifier, string $dbCol, string $prop): array
417+
{
418+
return [$specifier . '__' . $prop => $specifier . '.' . $dbCol];
419+
}
420+
419421
/** @param array<string, Collection> $collections */
420422
private function buildTables(Sql $sql, array $collections): Sql
421423
{
@@ -466,7 +468,7 @@ private function parseCompositions(Sql $sql, Collection $collection, string $ent
466468
}
467469

468470
foreach (array_keys($collection->compositions) as $comp) {
469-
$alias = $entity . '_comp' . $comp;
471+
$alias = $entity . Composite::COMPOSITION_MARKER . $comp;
470472
$sql->innerJoin($comp);
471473
$sql->as($alias);
472474
$sql->on([
@@ -569,9 +571,8 @@ private function parseHydrated(SplObjectStorage $hydrated): object
569571
/** @return SplObjectStorage<object, Collection>|false */
570572
private function fetchHydrated(Collection $collection, PDOStatement $statement): SplObjectStorage|false
571573
{
572-
$this->lastStatement = $statement;
573574
$hydrator = $this->resolveHydrator($collection);
574-
$row = $statement->fetch(PDO::FETCH_NUM);
575+
$row = $statement->fetch(PDO::FETCH_ASSOC);
575576

576577
return $hydrator->hydrate($row, $collection, $this->entityFactory);
577578
}
@@ -586,7 +587,7 @@ private function createStatement(
586587
$query->concat($withExtra);
587588
}
588589

589-
$statement = $this->db->prepare((string) $query, PDO::FETCH_NUM);
590+
$statement = $this->db->prepare((string) $query, PDO::FETCH_ASSOC);
590591
$statement->execute($query->params);
591592

592593
return $statement;

src/Sql.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
/** Fluent SQL builder with shape-based argument detection */
2525
class Sql
2626
{
27-
/** Instructions where assoc array values are raw identifiers, not parameterized */
28-
private const array RAW = ['on', 'select'];
27+
/** Instructions where a single assoc-array arg means raw `key = value` pairs */
28+
private const array RAW_PAIRS = ['on'];
29+
30+
/** Instructions where args are always comma-listed (aliases, not pairs) */
31+
private const array COMMA_ONLY = ['select'];
2932

3033
/**
3134
* Operators that expand an array value into multiple placeholders.
@@ -343,6 +346,10 @@ public function __call(string $name, array $args): static
343346
return $this;
344347
}
345348

349+
if (in_array($name, self::COMMA_ONLY)) {
350+
return $this->commaList(...$args);
351+
}
352+
346353
if (!is_array($args[0])) {
347354
if (count($args) > 1 && is_array($args[1]) && array_is_list($args[1])) {
348355
return $this->namedList((string) $args[0], $args[1]);
@@ -352,7 +359,7 @@ public function __call(string $name, array $args): static
352359
}
353360

354361
if (!array_is_list($args[0])) {
355-
if (in_array($name, self::RAW)) {
362+
if (in_array($name, self::RAW_PAIRS)) {
356363
return $this->rawPairs($args[0]);
357364
}
358365

tests/Hydrators/FlatNumTest.php

Lines changed: 0 additions & 118 deletions
This file was deleted.

tests/SqlTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,29 @@ public function testSelectUsingAliasedColumns(): void
6666
$this->assertEmpty($this->object->params);
6767
}
6868

69+
public function testSelectWithAllAliasedColumns(): void
70+
{
71+
$sql = (string) $this->object->select(
72+
['t__id' => 't.id'],
73+
['t__name' => 't.name'],
74+
)->from('t');
75+
$this->assertEquals(
76+
'SELECT t.id AS t__id, t.name AS t__name FROM t',
77+
$sql,
78+
);
79+
}
80+
81+
public function testSelectWithSingleAliasedColumn(): void
82+
{
83+
$sql = (string) $this->object->select(
84+
['t__id' => 't.id'],
85+
)->from('t');
86+
$this->assertEquals(
87+
'SELECT t.id AS t__id FROM t',
88+
$sql,
89+
);
90+
}
91+
6992
public function testSelectWithAggregateFunctions(): void
7093
{
7194
$sql = (string) $this->object->select('column', 'COUNT(column)', 'SUM(amount)')

0 commit comments

Comments
 (0)