Skip to content

Commit 363fb5c

Browse files
committed
Resolve scope names to tables via Style::realName
Mapper used the scope name verbatim as the SQL table and alias. Route every table emission (from/join/insertInto/update/deleteFrom) and the composition check through `Style::realName`, while the alias stays the PHP scope name and an `AS` bridges them whenever they differ — including the root table, which returned before the join-path `AS` logic and so could emit `FROM post_tag` against an undefined `postTag` alias for a multi-word root. DSL calls in the tests become PHP-conventional camelCase (post_category -> postCategory); the test schema, columns and seed data stay snake_case. Requires respect/data with Stylable::realName.
1 parent 4b030d4 commit 363fb5c

2 files changed

Lines changed: 29 additions & 12 deletions

File tree

src/Mapper.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
use function iterator_to_array;
2525

2626
/** Maps objects to database operations */
27-
final class Mapper extends AbstractMapper
27+
class Mapper extends AbstractMapper
2828
{
2929
public readonly Db $db;
3030

@@ -168,7 +168,7 @@ private function rawDelete(
168168
$condition = $this->guessCondition($columns, $scope);
169169

170170
return $this->db
171-
->deleteFrom($scope->name)
171+
->deleteFrom($this->style->realName($scope->name))
172172
->where($condition)
173173
->exec();
174174
}
@@ -179,7 +179,7 @@ private function rawUpdate(array $columns, Scope $scope): bool
179179
$condition = $this->guessCondition($columns, $scope);
180180

181181
return $this->db
182-
->update($scope->name)
182+
->update($this->style->realName($scope->name))
183183
->set($columns)
184184
->where($condition)
185185
->exec();
@@ -192,7 +192,7 @@ private function rawInsert(
192192
object|null $entity = null,
193193
): bool {
194194
$result = $this->db
195-
->insertInto($scope->name, array_keys($columns))
195+
->insertInto($this->style->realName($scope->name), array_keys($columns))
196196
->values(array_values($columns))
197197
->exec();
198198

@@ -362,18 +362,21 @@ private function parseScope(
362362

363363
//No parent scope means it's the first table in the query
364364
if ($parentAlias === null) {
365-
$sql->from($entity);
365+
$sql->from($s->realName($entity));
366+
if ($s->realName($entity) !== $alias) {
367+
$sql->as($alias);
368+
}
366369

367370
return;
368371
}
369372

370373
if ($scope->required) {
371-
$sql->innerJoin($entity);
374+
$sql->innerJoin($s->realName($entity));
372375
} else {
373-
$sql->leftJoin($entity);
376+
$sql->leftJoin($s->realName($entity));
374377
}
375378

376-
if ($alias !== $entity) {
379+
if ($s->realName($entity) !== $alias) {
377380
$sql->as($alias);
378381
}
379382

@@ -396,9 +399,10 @@ private function isCompositionJoin(Scope $scope, string $entity, string $parent)
396399
foreach ($scope->with as $child) {
397400
$connected = $child->name;
398401

402+
$table = $this->style->realName($entity);
399403
if (
400-
$entity === $this->style->composed($parent, $connected)
401-
|| $entity === $this->style->composed($connected, $parent)
404+
$table === $this->style->composed($parent, $connected)
405+
|| $table === $this->style->composed($connected, $parent)
402406
) {
403407
return true;
404408
}

tests/MapperTest.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ public function testNtoN(): void
365365
{
366366
$mapper = $this->mapper;
367367
$comments = $mapper->fetchAll($mapper->comment([
368-
$mapper->post([$mapper->post_category([$mapper->category(filter: 2)])]),
368+
$mapper->post([$mapper->postCategory([$mapper->category(filter: 2)])]),
369369
]));
370370
$comment = current($comments);
371371
$this->assertEquals(1, count($comments));
@@ -379,11 +379,24 @@ public function testNtoN(): void
379379
public function testManyToManyReverse(): void
380380
{
381381
$mapper = $this->mapper;
382-
$cat = $mapper->fetch($mapper->category([$mapper->post_category([$mapper->post(filter: 5)])]));
382+
$cat = $mapper->fetch($mapper->category([$mapper->postCategory([$mapper->post(filter: 5)])]));
383383
$this->assertEquals(2, $cat->id);
384384
$this->assertEquals('Sample Category', $cat->name);
385385
}
386386

387+
public function testFetchMultiWordEntityAsRoot(): void
388+
{
389+
// A multi-word root scope (postCategory → table post_category) must emit
390+
// `FROM post_category AS postCategory`: realName differs from the PHP
391+
// alias, so the root needs its own AS just like joined tables do.
392+
$mapper = $this->mapper;
393+
$junction = $mapper->fetch($mapper->postCategory(filter: 66));
394+
// The fetch succeeding at all proves the alias was emitted: without the
395+
// root AS the query would be `FROM post_category` while SELECT references
396+
// an undefined `postCategory.id`.
397+
$this->assertEquals(66, $junction->id);
398+
}
399+
387400
public function testSimplePersist(): void
388401
{
389402
$mapper = $this->mapper;

0 commit comments

Comments
 (0)