Skip to content

Commit a6a9aac

Browse files
committed
wip
1 parent 9b873b4 commit a6a9aac

File tree

7 files changed

+168
-99
lines changed

7 files changed

+168
-99
lines changed

packages/database/src/Builder/ModelInspector.php

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ public function getPropertyValues(): array
108108
$values = [];
109109

110110
foreach ($this->reflector->getProperties() as $property) {
111+
if ($property->isVirtual()) {
112+
continue;
113+
}
114+
115+
if ($property->hasAttribute(Virtual::class)) {
116+
continue;
117+
}
118+
111119
if (! $property->isInitialized($this->instance)) {
112120
continue;
113121
}
@@ -247,6 +255,81 @@ public function getRelation(string|PropertyReflector $name): ?Relation
247255
return $this->getBelongsTo($name) ?? $this->getHasOne($name) ?? $this->getHasMany($name);
248256
}
249257

258+
/**
259+
* @return \Tempest\Support\Arr\ImmutableArray<array-key, Relation>
260+
*/
261+
public function getRelations(): ImmutableArray
262+
{
263+
if (! $this->isObjectModel()) {
264+
return arr();
265+
}
266+
267+
$relationFields = arr();
268+
269+
foreach ($this->reflector->getPublicProperties() as $property) {
270+
if ($relation = $this->getRelation($property->getName())) {
271+
$relationFields[] = $relation;
272+
}
273+
}
274+
275+
return $relationFields;
276+
}
277+
278+
/**
279+
* @return \Tempest\Support\Arr\ImmutableArray<array-key, PropertyReflector>
280+
*/
281+
public function getValueFields(): ImmutableArray
282+
{
283+
if (! $this->isObjectModel()) {
284+
return arr();
285+
}
286+
287+
$valueFields = arr();
288+
289+
foreach ($this->reflector->getPublicProperties() as $property) {
290+
if ($property->isVirtual()) {
291+
continue;
292+
}
293+
294+
if ($property->hasAttribute(Virtual::class)) {
295+
continue;
296+
}
297+
298+
if ($this->isRelation($property->getName())) {
299+
continue;
300+
}
301+
302+
$valueFields[] = $property;
303+
}
304+
305+
return $valueFields;
306+
}
307+
308+
public function isRelationLoaded(string|PropertyReflector|Relation $relation): bool
309+
{
310+
if (! $this->isObjectModel()) {
311+
return false;
312+
}
313+
314+
if (! $relation instanceof Relation) {
315+
$relation = $this->getRelation($relation);
316+
}
317+
318+
if (! $relation) {
319+
return false;
320+
}
321+
322+
if (! $relation->property->isInitialized($this->instance)) {
323+
return false;
324+
}
325+
326+
if ($relation->property->getValue($this->instance) === null) {
327+
return false;
328+
}
329+
330+
return true;
331+
}
332+
250333
public function getSelectFields(): ImmutableArray
251334
{
252335
if (! $this->isObjectModel()) {
@@ -266,13 +349,21 @@ public function getSelectFields(): ImmutableArray
266349
continue;
267350
}
268351

352+
if ($property->getType()->equals(PrimaryKey::class)) {
353+
continue;
354+
}
355+
269356
if ($relation instanceof BelongsTo) {
270357
$selectFields[] = $relation->getOwnerFieldName();
271358
} else {
272359
$selectFields[] = $property->getName();
273360
}
274361
}
275362

363+
if ($primaryKey = $this->getPrimaryKeyProperty()) {
364+
$selectFields[] = $primaryKey->getName();
365+
}
366+
276367
return $selectFields;
277368
}
278369

@@ -417,11 +508,11 @@ public function getPrimaryKeyProperty(): ?PropertyReflector
417508

418509
return match ($primaryKeys->count()) {
419510
0 => null,
420-
1 => $primaryKeys->first(),
421-
default => throw ModelHadMultiplePrimaryColumns::found(
422-
model: $this->model,
423-
properties: $primaryKeys->map(fn (PropertyReflector $property) => $property->getName())->toArray(),
424-
),
511+
default => $primaryKeys->first(),
512+
// default => throw ModelHadMultiplePrimaryColumns::found(
513+
// model: $this->model,
514+
// properties: $primaryKeys->map(fn (PropertyReflector $property) => $property->getName())->toArray(),
515+
// ),
425516
};
426517
}
427518

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ private function convertObjectToArray(object $object, array $excludeProperties =
149149
continue;
150150
}
151151

152+
if ($property->isVirtual()) {
153+
continue;
154+
}
155+
156+
if ($property->isUninitialized($object)) {
157+
continue;
158+
}
159+
152160
$propertyName = $property->getName();
153161

154162
if (! in_array($propertyName, $excludeProperties, strict: true)) {

packages/database/src/IsDatabaseModel.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ public static function findOrNew(array $find, array $update): self
158158
*
159159
* @param array<string,mixed> $find Properties to search for in the existing model.
160160
* @param array<string,mixed> $update Properties to update or set on the model if it is found or created.
161-
* @return TModel
162161
*/
163162
public static function updateOrCreate(array $find, array $update): self
164163
{
@@ -176,21 +175,29 @@ public function refresh(): self
176175
throw Exceptions\ModelDidNotHavePrimaryColumn::neededForMethod($this, 'refresh');
177176
}
178177

179-
$relations = [];
178+
$loadedRelations = $model->getRelations()
179+
->filter(fn (Relation $relation) => $model->isRelationLoaded($relation));
180180

181-
foreach (new ClassReflector($this)->getPublicProperties() as $property) {
182-
if (! $property->isInitialized($this) || ! $property->getValue($this)) {
183-
continue;
184-
}
181+
$primaryKeyProperty = $model->getPrimaryKeyProperty();
182+
$primaryKeyValue = $primaryKeyProperty->getValue($this);
185183

186-
if (! $model->isRelation($property->getName())) {
187-
continue;
188-
}
184+
$new = self::select()
185+
->with(...$loadedRelations->map(fn (Relation $relation) => $relation->name))
186+
->get($primaryKeyValue);
189187

190-
$relations[] = $property->getName();
188+
foreach ($loadedRelations as $relation) {
189+
$relation->property->setValue(
190+
object: $this,
191+
value: $relation->property->getValue($new),
192+
);
191193
}
192194

193-
$this->load(...$relations);
195+
foreach ($model->getValueFields() as $property) {
196+
$property->setValue(
197+
object: $this,
198+
value: $property->getValue($new),
199+
);
200+
}
194201

195202
return $this;
196203
}
@@ -212,7 +219,7 @@ public function load(string ...$relations): self
212219
$new = self::get($primaryKeyValue, $relations);
213220

214221
foreach (new ClassReflector($new)->getPublicProperties() as $property) {
215-
if ($property->hasAttribute(Virtual::class)) {
222+
if (! in_array($property->getName(), $relations, strict: true)) {
216223
continue;
217224
}
218225

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ final class Book implements Bindable
1919
#[HasLength(min: 1, max: 120)]
2020
public string $title;
2121

22-
public ?Author $author = null;
22+
public Author $author;
2323

2424
/** @var \Tests\Tempest\Fixtures\Modules\Books\Models\Chapter[] */
25-
public array $chapters = [];
25+
public array $chapters;
2626

2727
#[HasOne]
28-
public ?Isbn $isbn = null;
28+
public Isbn $isbn;
2929
}

tests/Integration/Database/Builder/InsertRelationsTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Tests\Tempest\Fixtures\Modules\Books\Models\Isbn;
2323
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
2424

25+
use function Tempest\Database\inspect;
2526
use function Tempest\Database\query;
2627

2728
final class InsertRelationsTest extends FrameworkIntegrationTestCase

tests/Integration/Database/ModelsWithoutIdTest.php

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -145,81 +145,6 @@ public function test_model_with_mixed_id_and_non_id_properties(): void
145145
$this->assertInstanceOf(PrimaryKey::class, $all[0]->id);
146146
$this->assertSame('test', $all[0]->regular_field);
147147
}
148-
149-
public function test_load_works_for_models_with_id(): void
150-
{
151-
$this->migrate(CreateMigrationsTable::class, CreateMixedModelMigration::class);
152-
153-
$mixed = query(MixedModel::class)->create(regular_field: 'test', another_field: 'data');
154-
$result = $mixed->load();
155-
156-
$this->assertSame($mixed, $result);
157-
$this->assertSame('test', $mixed->regular_field);
158-
}
159-
160-
public function test_load_with_relation_works_for_models_with_id(): void
161-
{
162-
$this->migrate(
163-
CreateMigrationsTable::class,
164-
CreateTestUserMigration::class,
165-
CreateTestProfileMigration::class,
166-
);
167-
168-
$user = query(TestUser::class)->create(
169-
name: 'Frieren',
170-
171-
);
172-
173-
query(TestProfile::class)->create(
174-
user: $user,
175-
bio: 'Ancient elf mage who loves magic and collecting spells',
176-
age: 1000,
177-
);
178-
179-
$userWithProfile = $user->load('profile');
180-
181-
$this->assertSame($user, $userWithProfile);
182-
$this->assertSame('Frieren', $user->name);
183-
$this->assertInstanceOf(TestProfile::class, $user->profile);
184-
$this->assertSame('Ancient elf mage who loves magic and collecting spells', $user->profile->bio);
185-
$this->assertSame(1000, $user->profile->age);
186-
}
187-
188-
// this may be a bug, but I'm adding a test just to be sure we don't break the behavior by mistake.
189-
// I believe ->load should just load the specified relations, but it also reloads all properties
190-
public function test_load_method_refreshes_all_properties_not_just_relations(): void
191-
{
192-
$this->migrate(
193-
CreateMigrationsTable::class,
194-
CreateTestUserMigration::class,
195-
CreateTestProfileMigration::class,
196-
);
197-
198-
$user = query(TestUser::class)->create(
199-
name: 'Frieren',
200-
201-
);
202-
203-
query(TestProfile::class)->create(
204-
user: $user,
205-
bio: 'Ancient elf mage',
206-
age: 1000,
207-
);
208-
209-
$userInstance = query(TestUser::class)->findById($user->id);
210-
$userInstance->name = 'Fern';
211-
212-
query(TestUser::class)
213-
->update(email: '[email protected]')
214-
->where('id', $user->id->value)
215-
->execute();
216-
217-
$userInstance->load('profile');
218-
219-
$this->assertSame('Frieren', $userInstance->name); // "Fern" was discarded here
220-
$this->assertSame('[email protected]', $userInstance->email);
221-
$this->assertInstanceOf(TestProfile::class, $userInstance->profile);
222-
}
223148
}
224149

225150
final class LogEntry

tests/Integration/Database/RefreshModelTest.php

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function test_refresh_works_for_models_with_unloaded_relation(): void
3232
// Get user without loading the profile relation
3333
$book = Book::get($book->id);
3434

35-
$this->assertNull($book->author);
35+
$this->assertFalse(isset($book->author));
3636

3737
// Update the user's name in the database
3838
query(Book::class)
@@ -46,15 +46,52 @@ public function test_refresh_works_for_models_with_unloaded_relation(): void
4646
$book->refresh();
4747

4848
$this->assertSame('Timeline Taxi 2', $book->title);
49-
$this->assertNull($book->author); // Relation should still be unloaded
49+
$this->assertFalse(isset($book->author)); // Relation should still be unloaded
5050

5151
// Load the relation
5252
$book->load('author');
5353

54-
$this->assertInstanceOf(Author::class, $book->author);
54+
$this->assertTrue(isset($book->author));
5555

5656
$book->refresh();
5757

58-
$this->assertInstanceOf(Author::class, $book->author);
58+
$this->assertTrue(isset($book->author));
59+
}
60+
61+
public function test_load_method_only_refreshes_relations_and_nothing_else(): void
62+
{
63+
$this->migrate(
64+
CreateMigrationsTable::class,
65+
CreateBookTable::class,
66+
CreateAuthorTable::class,
67+
);
68+
69+
$author = Author::create(
70+
name: 'Brent Roose',
71+
);
72+
73+
$book = Book::create(
74+
title: 'Timeline Taxi',
75+
author: $author,
76+
);
77+
78+
// Get user without loading the profile relation
79+
$book = Book::get($book->id);
80+
81+
$this->assertFalse(isset($book->author));
82+
83+
// Update the user's name in the database
84+
query(Book::class)
85+
->update(
86+
title: 'Timeline Taxi 2',
87+
)
88+
->where('id', $book->id)
89+
->execute();
90+
91+
// Load the relation
92+
$book->load('author');
93+
94+
// The updated value from the database is ignored
95+
$this->assertSame('Timeline Taxi', $book->title);
5996
}
6097
}

0 commit comments

Comments
 (0)