Skip to content

Commit 7443262

Browse files
authored
feat: improve handling of empty/missing relations during insert/update (#23)
1 parent 98b8dd1 commit 7443262

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ parameters:
3030
count: 1
3131
path: tests/EntityTest.php
3232

33+
-
34+
message: '#^Parameter \#1 \$row of method Tests\\Support\\Models\\UserModel\:\:insert\(\) expects array\<int\|string, bool\|float\|int\|object\|string\|null\>\|object\|null, array\<string, array\|string\> given\.$#'
35+
identifier: argument.type
36+
count: 1
37+
path: tests/EntityTest.php
38+
3339
-
3440
message: '#^Cannot access property \$country on array\|bool\|float\|int\|object\|string\.$#'
3541
identifier: property.nonObject

src/Relation.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Relation
2727
/**
2828
* @var list<mixed>
2929
*/
30-
private array|Entity $data = [];
30+
private array|Entity|null $data = [];
3131

3232
public function __construct(
3333
public readonly RelationTypes $type,
@@ -61,7 +61,7 @@ public function applyConditions(): static
6161
/**
6262
* @param list<mixed> $data
6363
*/
64-
public function setData(array|Entity $data): static
64+
public function setData(array|Entity|null $data): static
6565
{
6666
$this->data = $data;
6767

@@ -277,7 +277,7 @@ public function filterResult(array|Entity|null $row, string $returnType): array|
277277

278278
public function filterResults(array|Entity $results, string $returnType): array|Entity
279279
{
280-
if ($this->type !== RelationTypes::belongsToMany) {
280+
if ($results === [] || $this->type !== RelationTypes::belongsToMany) {
281281
return $results;
282282
}
283283

src/Traits/HasRelations.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,14 @@ protected function relationsAfterInsert(array $eventData): array
302302
}
303303

304304
foreach ($this->relations as $relationObject) {
305-
foreach ($relationObject->getData() as $row) {
305+
$data = $relationObject->getData();
306+
307+
// Skip if data is null or empty - nothing to insert
308+
if ($data === [null] || $data === []) {
309+
continue;
310+
}
311+
312+
foreach ($data as $row) {
306313
$row = $this->transformDataToArray($row, 'insert');
307314
$result = $relationObject->applyWith()->model->insert(array_merge($row, [
308315
$relationObject->foreignKey => $eventData[$this->primaryKey],
@@ -349,7 +356,14 @@ protected function relationsAfterUpdate(array $eventData): array
349356
}
350357

351358
foreach ($this->relations as $relationObject) {
352-
foreach ($relationObject->getData() as $row) {
359+
$data = $relationObject->getData();
360+
361+
// Skip if data is null or empty - nothing to update
362+
if ($data === [null] || $data === []) {
363+
continue;
364+
}
365+
366+
foreach ($data as $row) {
353367
$row = $this->transformDataToArray($row, 'insert');
354368

355369
foreach ($eventData[$this->primaryKey] as $id) {
@@ -432,11 +446,9 @@ protected function getDataForRelationById(int|string $id, Relation $relation, st
432446

433447
$relation->applyWith()->applyRelation($id, $this->primaryKey)->applyConditions();
434448

435-
$results = in_array($relation->type, [RelationTypes::hasOne, RelationTypes::belongsTo], true) ?
436-
$relation->model->first() :
437-
$relation->model->findAll();
438-
439-
return $relation->filterResults($results, $this->tempReturnType);
449+
return in_array($relation->type, [RelationTypes::hasOne, RelationTypes::belongsTo], true) ?
450+
$relation->filterResult($relation->model->first(), $this->tempReturnType) :
451+
$relation->filterResults($relation->model->findAll(), $this->tempReturnType);
440452
}
441453

442454
/**

tests/EntityTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,46 @@ public function testRelationQueriedOnlyOnce()
102102

103103
$this->assertSame($queryCount + 1, $this->queryCount);
104104
}
105+
106+
public function testSaveNullRelationOneToOneSkipsInsert()
107+
{
108+
$model = model(UserModel::class);
109+
$data = [
110+
'username' => 'Test User',
111+
'company_id' => '2',
112+
'country_id' => '1',
113+
'profile' => null,
114+
];
115+
116+
$id = $model->with('profile')->insert($data);
117+
118+
$this->assertIsNumeric($id);
119+
120+
// Verify user was created but profile was not
121+
$user = $model->with('profile')->find($id);
122+
$this->assertInstanceOf(User::class, $user);
123+
$this->assertSame('Test User', $user->username);
124+
$this->assertNull($user->profile);
125+
}
126+
127+
public function testSaveEmptyArrayRelationOneToManySkipsInsert()
128+
{
129+
$model = model(UserModel::class);
130+
$data = [
131+
'username' => 'Test User',
132+
'company_id' => '2',
133+
'country_id' => '1',
134+
'posts' => [],
135+
];
136+
137+
$id = $model->with('posts')->insert($data);
138+
139+
$this->assertIsNumeric($id);
140+
141+
// Verify user was created but no posts were created
142+
$user = $model->with('posts')->find($id);
143+
$this->assertInstanceOf(User::class, $user);
144+
$this->assertSame('Test User', $user->username);
145+
$this->assertSame([], $user->posts);
146+
}
105147
}

0 commit comments

Comments
 (0)