Skip to content

Commit 0b4c263

Browse files
committed
wip
1 parent f4f7600 commit 0b4c263

File tree

10 files changed

+180
-25
lines changed

10 files changed

+180
-25
lines changed

packages/database/src/Builder/ModelInspector.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use ReflectionException;
66
use Tempest\Database\Config\DatabaseConfig;
7+
use Tempest\Database\HasOne;
78
use Tempest\Database\Table;
89
use Tempest\Reflection\ClassReflector;
910
use Tempest\Validation\Exceptions\ValidationException;
@@ -69,7 +70,7 @@ public function getPropertyValues(): array
6970
continue;
7071
}
7172

72-
if ($property->getIterableType()?->isRelation()) {
73+
if ($this->isHasManyRelation($property->getName()) || $this->isHasOneRelation($property->getName())) {
7374
continue;
7475
}
7576

@@ -81,6 +82,44 @@ public function getPropertyValues(): array
8182
return $values;
8283
}
8384

85+
public function isHasManyRelation(string $name): bool
86+
{
87+
if (! $this->isObjectModel()) {
88+
return false;
89+
}
90+
91+
if (! $this->modelClass->hasProperty($name)) {
92+
return false;
93+
}
94+
95+
$property = $this->modelClass->getProperty($name);
96+
97+
if ($property->getIterableType()?->isRelation()) {
98+
return true;
99+
}
100+
101+
return false;
102+
}
103+
104+
public function isHasOneRelation(string $name): bool
105+
{
106+
if (! $this->isObjectModel()) {
107+
return false;
108+
}
109+
110+
if (! $this->modelClass->hasProperty($name)) {
111+
return false;
112+
}
113+
114+
$property = $this->modelClass->getProperty($name);
115+
116+
if ($property->hasAttribute(HasOne::class)) {
117+
return true;
118+
}
119+
120+
return false;
121+
}
122+
84123
public function validate(mixed ...$data): void
85124
{
86125
if (! $this->isObjectModel()) {
@@ -111,4 +150,13 @@ public function validate(mixed ...$data): void
111150
throw new ValidationException($this->modelClass->getName(), $failingRules);
112151
}
113152
}
153+
154+
public function getName(): string
155+
{
156+
if ($this->isObjectModel()) {
157+
return $this->modelClass->getName();
158+
}
159+
160+
return $this->modelClass;
161+
}
114162
}

packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
use Closure;
66
use Tempest\Database\Builder\ModelDefinition;
77
use Tempest\Database\Builder\TableDefinition;
8+
use Tempest\Database\Exceptions\CannotInsertHasManyRelation;
9+
use Tempest\Database\Exceptions\CannotInsertHasOneRelation;
810
use Tempest\Database\Id;
911
use Tempest\Database\Query;
1012
use Tempest\Database\QueryStatements\InsertStatement;
1113
use Tempest\Mapper\SerializerFactory;
1214
use Tempest\Reflection\ClassReflector;
1315
use Tempest\Support\Arr\ImmutableArray;
1416

17+
use function Tempest\Database\model;
18+
1519
final class InsertQueryBuilder implements BuildsQuery
1620
{
1721
private InsertStatement $insert;
@@ -43,12 +47,22 @@ public function execute(mixed ...$bindings): Id
4347

4448
public function build(mixed ...$bindings): Query
4549
{
46-
foreach ($this->resolveEntries() as $entry) {
47-
$this->insert->addEntry($entry);
50+
$definition = model($this->model);
51+
52+
foreach ($this->resolveData() as $data) {
53+
foreach ($data as $key => $value) {
54+
if ($definition->isHasManyRelation($key)) {
55+
throw new CannotInsertHasManyRelation($definition->getName(), $key);
56+
}
57+
58+
if ($definition->isHasOneRelation($key)) {
59+
throw new CannotInsertHasOneRelation($definition->getName(), $key);
60+
}
4861

49-
foreach ($entry as $value) {
5062
$bindings[] = $value;
5163
}
64+
65+
$this->insert->addEntry($data);
5266
}
5367

5468
return new Query(
@@ -64,7 +78,7 @@ public function then(Closure ...$callbacks): self
6478
return $this;
6579
}
6680

67-
private function resolveEntries(): array
81+
private function resolveData(): array
6882
{
6983
$entries = [];
7084

@@ -77,6 +91,8 @@ private function resolveEntries(): array
7791
}
7892

7993
// The rest are model objects
94+
$definition = model($model);
95+
8096
$modelClass = new ClassReflector($model);
8197

8298
$entry = [];
@@ -87,8 +103,8 @@ private function resolveEntries(): array
87103
continue;
88104
}
89105

90-
// HasMany relations are skipped
91-
if ($property->getIterableType()?->isRelation()) {
106+
// HasMany and HasOne relations are skipped
107+
if ($definition->isHasManyRelation($property->getName()) || $definition->isHasOneRelation($property->getName())) {
92108
continue;
93109
}
94110

@@ -111,7 +127,7 @@ private function resolveEntries(): array
111127
};
112128
}
113129

114-
// Check if value needs serialization
130+
// Check if the value needs serialization
115131
$serializer = $this->serializerFactory->forProperty($property);
116132

117133
if ($value !== null && $serializer !== null) {

packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tempest\Database\Builder\QueryBuilders;
44

55
use Tempest\Database\Exceptions\CannotUpdateHasManyRelation;
6+
use Tempest\Database\Exceptions\CannotUpdateHasOneRelation;
67
use Tempest\Database\Id;
78
use Tempest\Database\Query;
89
use Tempest\Database\QueryStatements\UpdateStatement;
@@ -86,7 +87,9 @@ public function build(mixed ...$bindings): Query
8687

8788
private function resolveValues(): ImmutableArray
8889
{
89-
if (! model($this->model)->isObjectModel()) {
90+
$modelDefinition = model($this->model);
91+
92+
if (! $modelDefinition->isObjectModel()) {
9093
return arr($this->values);
9194
}
9295

@@ -97,10 +100,14 @@ private function resolveValues(): ImmutableArray
97100
foreach ($this->values as $column => $value) {
98101
$property = $modelClass->getProperty($column);
99102

100-
if ($property->getIterableType()?->isRelation()) {
103+
if ($modelDefinition->isHasManyRelation($property->getName())) {
101104
throw new CannotUpdateHasManyRelation($modelClass->getName(), $property->getName());
102105
}
103106

107+
if ($modelDefinition->isHasOneRelation($property->getName())) {
108+
throw new CannotUpdateHasOneRelation($modelClass->getName(), $property->getName());
109+
}
110+
104111
if ($property->getType()->isRelation()) {
105112
$column .= '_id';
106113

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Tempest\Database\Exceptions;
4+
5+
use Exception;
6+
7+
final class CannotInsertHasOneRelation extends Exception
8+
{
9+
public function __construct(string $modelName, string $relationName)
10+
{
11+
parent::__construct("Cannot create {$modelName}::\${$relationName} via an insert query. Attach the related has many model manually instead");
12+
}
13+
}

packages/database/src/Exceptions/CannotUpdateHasManyRelation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ final class CannotUpdateHasManyRelation extends Exception
88
{
99
public function __construct(string $modelName, string $relationName)
1010
{
11-
parent::__construct("Cannot update {$modelName}::\${$relationName} via an update query. Attach the related has many model manually instead");
11+
parent::__construct("Cannot update {$modelName}::\${$relationName} via an update query. Update the related model directly instead");
1212
}
1313
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Tempest\Database\Exceptions;
4+
5+
use Exception;
6+
7+
final class CannotUpdateHasOneRelation extends Exception
8+
{
9+
public function __construct(string $modelName, string $relationName)
10+
{
11+
parent::__construct("Cannot update {$modelName}::\${$relationName} via an update query. Update the related model directly instead");
12+
}
13+
}

tests/Fixtures/Modules/Books/Models/Book.php

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

55
namespace Tests\Tempest\Fixtures\Modules\Books\Models;
66

7+
use Tempest\Database\HasOne;
78
use Tempest\Database\IsDatabaseModel;
89
use Tempest\Router\Bindable;
910
use Tempest\Validation\Rules\Length;
@@ -19,4 +20,7 @@ final class Book implements Bindable
1920

2021
/** @var \Tests\Tempest\Fixtures\Modules\Books\Models\Chapter[] */
2122
public array $chapters = [];
23+
24+
#[HasOne]
25+
public ?Isbn $isbn = null;
2226
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures\Modules\Books\Models;
4+
5+
final class Isbn
6+
{
7+
public string $value;
8+
9+
public Book $book;
10+
}

tests/Integration/Database/Builder/InsertQueryBuilderTest.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Tests\Tempest\Integration\Database\Builder;
44

5+
use Tempest\Database\Exceptions\CannotInsertHasManyRelation;
6+
use Tempest\Database\Exceptions\CannotInsertHasOneRelation;
57
use Tempest\Database\Id;
68
use Tempest\Database\Migrations\CreateMigrationsTable;
79
use Tempest\Database\Query;
@@ -150,7 +152,35 @@ public function test_insert_on_model_table_with_existing_relation(): void
150152
$this->assertSame(10, $bookQuery->bindings[1]);
151153
}
152154

153-
public function test_attach_new_has_many_relation_on_update(): void
155+
public function test_inserting_has_many_via_parent_model_throws_exception(): void
156+
{
157+
try {
158+
query(Book::class)
159+
->insert(
160+
title: 'Timeline Taxi',
161+
chapters: ['title' => 'Chapter 01'],
162+
)
163+
->build();
164+
} catch (CannotInsertHasManyRelation $cannotInsertHasManyRelation) {
165+
$this->assertStringContainsString(Book::class . '::$chapters', $cannotInsertHasManyRelation->getMessage());
166+
}
167+
}
168+
169+
public function test_inserting_has_one_via_parent_model_throws_exception(): void
170+
{
171+
try {
172+
query(Book::class)
173+
->insert(
174+
title: 'Timeline Taxi',
175+
isbn: ['value' => '979-8344313764'],
176+
)
177+
->build();
178+
} catch (CannotInsertHasOneRelation $cannotInsertHasOneRelation) {
179+
$this->assertStringContainsString(Book::class . '::$isbn', $cannotInsertHasOneRelation->getMessage());
180+
}
181+
}
182+
183+
public function test_then_method(): void
154184
{
155185
$this->migrate(CreateMigrationsTable::class, CreateBookTable::class, CreateChapterTable::class);
156186

tests/Integration/Database/Builder/UpdateQueryBuilderTest.php

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Tests\Tempest\Integration\Database\Builder;
44

55
use Tempest\Database\Builder\QueryBuilders\UpdateQueryBuilder;
6+
use Tempest\Database\Exceptions\CannotInsertHasManyRelation;
67
use Tempest\Database\Exceptions\CannotUpdateHasManyRelation;
8+
use Tempest\Database\Exceptions\CannotUpdateHasOneRelation;
79
use Tempest\Database\Exceptions\InvalidUpdateStatement;
810
use Tempest\Database\Id;
911
use Tempest\Database\Query;
@@ -199,20 +201,32 @@ public function test_attach_existing_relation_on_update(): void
199201
$this->assertSame([5, 10], $bookQuery->bindings);
200202
}
201203

202-
public function test_attach_new_has_many_relation_on_update(): void
204+
public function test_update_has_many_relation_via_parent_model_throws_exception(): void
203205
{
204-
$this->markTestSkipped('Not implemented yet');
205-
206-
// $book = Book::new(
207-
// id: new Id(10),
208-
// );
209-
// query($book)
210-
// ->update(
211-
// chapters: [
212-
// Chapter::new(title: 'Chapter 01'),
213-
// ],
214-
// )
215-
// ->build();
206+
try {
207+
query(Book::class)
208+
->update(
209+
title: 'Timeline Taxi',
210+
chapters: ['title' => 'Chapter 01'],
211+
)
212+
->build();
213+
} catch (CannotUpdateHasManyRelation $cannotUpdateHasManyRelation) {
214+
$this->assertStringContainsString(Book::class . '::$chapters', $cannotUpdateHasManyRelation->getMessage());
215+
}
216+
}
217+
218+
public function test_update_has_one_relation_via_parent_model_throws_exception(): void
219+
{
220+
try {
221+
query(Book::class)
222+
->update(
223+
title: 'Timeline Taxi',
224+
isbn: ['value' => '979-8344313764'],
225+
)
226+
->build();
227+
} catch (CannotUpdateHasOneRelation $cannotUpdateHasOneRelation) {
228+
$this->assertStringContainsString(Book::class . '::$isbn', $cannotUpdateHasOneRelation->getMessage());
229+
}
216230
}
217231

218232
public function test_update_on_plain_table_with_conditions(): void

0 commit comments

Comments
 (0)