Skip to content

Commit ccd398f

Browse files
authored
fix(database): check explicit relation attributes after singularizing property names (#2090)
1 parent c0d6ebe commit ccd398f

3 files changed

Lines changed: 131 additions & 4 deletions

File tree

packages/database/src/Builder/ModelInspector.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public function getBelongsTo(string $name): ?BelongsTo
196196

197197
$singularizedName = $name->singularizeLastWord();
198198

199-
if (! $singularizedName->equals($name)) {
199+
if (! $singularizedName->equals($name) && $this->reflector->hasProperty($singularizedName)) {
200200
return $this->getBelongsTo($singularizedName);
201201
}
202202

@@ -264,7 +264,7 @@ public function getHasOne(string $name): ?HasOne
264264

265265
$singularizedName = $name->singularizeLastWord();
266266

267-
if (! $singularizedName->equals($name)) {
267+
if (! $singularizedName->equals($name) && $this->reflector->hasProperty($singularizedName)) {
268268
return $this->getHasOne($singularizedName);
269269
}
270270

@@ -343,7 +343,7 @@ public function getHasOneThrough(string $name): ?HasOneThrough
343343

344344
$singularizedName = $name->singularizeLastWord();
345345

346-
if (! $singularizedName->equals($name)) {
346+
if (! $singularizedName->equals($name) && $this->reflector->hasProperty($singularizedName)) {
347347
return $this->getHasOneThrough($singularizedName);
348348
}
349349

packages/intl/src/Pluralizer/InflectorPluralizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private function extractLastWord(string $value, ?string &$prefix = null, ?string
7070
? array_pop(array: $parts)
7171
: '';
7272

73-
if (count(value: $parts) === 0) {
73+
if ($parts === []) {
7474
return null;
7575
}
7676

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Database\ModelInspector;
4+
5+
use PHPUnit\Framework\Attributes\Test;
6+
use Tempest\Database\BelongsTo;
7+
use Tempest\Database\Config\DatabaseDialect;
8+
use Tempest\Database\HasOne;
9+
use Tempest\Database\HasOneThrough;
10+
use Tempest\Database\PrimaryKey;
11+
use Tempest\Database\Table;
12+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
13+
14+
use function Tempest\Database\inspect;
15+
16+
final class LatinPluralRelationTest extends FrameworkIntegrationTestCase
17+
{
18+
#[Test]
19+
public function belongs_to_with_latin_plural_property_name(): void
20+
{
21+
$model = inspect(model: LatinPluralProductModel::class);
22+
$relation = $model->getRelation(name: 'product_metadata');
23+
24+
$this->assertInstanceOf(
25+
expected: BelongsTo::class,
26+
actual: $relation,
27+
);
28+
29+
$this->assertSame(
30+
expected: 'LEFT JOIN metadata ON metadata.id = products.metadata_id',
31+
actual: $relation
32+
->getJoinStatement()
33+
->compile(dialect: DatabaseDialect::SQLITE),
34+
);
35+
}
36+
37+
#[Test]
38+
public function has_one_with_latin_plural_property_name(): void
39+
{
40+
$model = inspect(model: LatinPluralMetadataModel::class);
41+
$relation = $model->getRelation(name: 'extra_data');
42+
43+
$this->assertInstanceOf(
44+
expected: HasOne::class,
45+
actual: $relation,
46+
);
47+
}
48+
49+
#[Test]
50+
public function has_one_through_with_latin_plural_property_name(): void
51+
{
52+
$model = inspect(model: LatinPluralMetadataModel::class);
53+
$relation = $model->getRelation(name: 'product_criteria');
54+
55+
$this->assertInstanceOf(
56+
expected: HasOneThrough::class,
57+
actual: $relation,
58+
);
59+
}
60+
61+
#[Test]
62+
public function latin_plural_belongs_to_uses_fk_in_select_fields(): void
63+
{
64+
$model = inspect(model: LatinPluralProductModel::class);
65+
$fields = $model->getSelectFields()->toArray();
66+
67+
$this->assertContains(
68+
needle: 'metadata_id',
69+
haystack: $fields,
70+
);
71+
$this->assertNotContains(
72+
needle: 'product_metadata',
73+
haystack: $fields,
74+
);
75+
}
76+
}
77+
78+
#[Table(name: 'products')]
79+
final class LatinPluralProductModel
80+
{
81+
public PrimaryKey $id;
82+
83+
#[BelongsTo]
84+
public ?LatinPluralMetadataModel $product_metadata = null;
85+
86+
public string $name;
87+
}
88+
89+
#[Table(name: 'metadata')]
90+
final class LatinPluralMetadataModel
91+
{
92+
public PrimaryKey $id;
93+
94+
public string $value;
95+
96+
#[HasOne]
97+
public ?LatinPluralExtraDataModel $extra_data = null;
98+
99+
#[HasOneThrough(through: LatinPluralIntermediateModel::class)]
100+
public ?LatinPluralCriteriaModel $product_criteria = null;
101+
}
102+
103+
#[Table(name: 'extra_data')]
104+
final class LatinPluralExtraDataModel
105+
{
106+
public PrimaryKey $id;
107+
108+
public string $info;
109+
}
110+
111+
#[Table(name: 'intermediate')]
112+
final class LatinPluralIntermediateModel
113+
{
114+
public PrimaryKey $id;
115+
116+
public LatinPluralMetadataModel $metadata;
117+
118+
public string $link;
119+
}
120+
121+
#[Table(name: 'criteria')]
122+
final class LatinPluralCriteriaModel
123+
{
124+
public PrimaryKey $id;
125+
126+
public string $label;
127+
}

0 commit comments

Comments
 (0)