Skip to content

Commit 4a99a5f

Browse files
authored
fix(laravel): persist HasMany and MorphMany relationships (#7208)
fixes #7206
1 parent b213164 commit 4a99a5f

File tree

5 files changed

+132
-1
lines changed

5 files changed

+132
-1
lines changed

src/Laravel/Eloquent/State/PersistProcessor.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Metadata\HttpOperation;
1919
use ApiPlatform\Metadata\Operation;
2020
use ApiPlatform\State\ProcessorInterface;
21+
use Illuminate\Database\Eloquent\Collection;
2122
use Illuminate\Database\Eloquent\Relations\BelongsTo;
2223
use Illuminate\Database\Eloquent\Relations\HasMany;
2324
use Illuminate\Database\Eloquent\Relations\MorphMany;
@@ -62,7 +63,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
6263
if (HasMany::class === $relation['type'] || MorphMany::class === $relation['type']) {
6364
$rel = $data->{$relation['name']};
6465

65-
if (!\is_array($rel)) {
66+
if (!\is_array($rel) && !$rel instanceof Collection) {
6667
throw new RuntimeException('To-Many relationship is not a collection.');
6768
}
6869

src/Laravel/Tests/EloquentTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,19 @@ public function testRelationIsHandledOnCreateWithNestedDataToMany(): void
523523
],
524524
]);
525525
}
526+
527+
public function testPostWithEmptyMorphMany(): void
528+
{
529+
$response = $this->postJson('/api/post_with_morph_manies', [
530+
'title' => 'My first post',
531+
'content' => 'This is the content of my first post.',
532+
'comments' => [['content' => 'hello']],
533+
], ['accept' => 'application/ld+json', 'content-type' => 'application/ld+json']);
534+
$response->assertStatus(201);
535+
$response->assertJson([
536+
'title' => 'My first post',
537+
'content' => 'This is the content of my first post.',
538+
'comments' => [['content' => 'hello']],
539+
]);
540+
}
526541
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\App\Models;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\NotExposed;
18+
use Illuminate\Database\Eloquent\Model;
19+
use Illuminate\Database\Eloquent\Relations\MorphTo;
20+
use Symfony\Component\Serializer\Attribute\Groups;
21+
22+
#[NotExposed]
23+
#[ApiProperty(serialize: new Groups(['comments']), property: 'content')]
24+
class CommentMorph extends Model
25+
{
26+
protected $table = 'comments_morph';
27+
protected $fillable = ['content'];
28+
29+
public function commentable(): MorphTo
30+
{
31+
return $this->morphTo();
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\App\Models;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Post;
19+
use Illuminate\Database\Eloquent\Model;
20+
use Illuminate\Database\Eloquent\Relations\MorphMany;
21+
use Symfony\Component\Serializer\Attribute\Groups;
22+
23+
#[ApiResource(operations: [
24+
new Post(
25+
denormalizationContext: ['groups' => ['comments']],
26+
normalizationContext: ['groups' => ['comments']],
27+
),
28+
])]
29+
#[ApiProperty(serialize: new Groups(['comments']), property: 'title')]
30+
#[ApiProperty(serialize: new Groups(['comments']), property: 'content')]
31+
#[ApiProperty(serialize: new Groups(['comments']), property: 'comments')]
32+
class PostWithMorphMany extends Model
33+
{
34+
protected $table = 'posts_with_morph_many';
35+
protected $fillable = ['title', 'content'];
36+
37+
public function comments(): MorphMany
38+
{
39+
return $this->morphMany(CommentMorph::class, 'commentable');
40+
}
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
use Illuminate\Database\Migrations\Migration;
15+
use Illuminate\Database\Schema\Blueprint;
16+
use Illuminate\Support\Facades\Schema;
17+
18+
return new class extends Migration {
19+
public function up(): void
20+
{
21+
Schema::create('posts_with_morph_many', function (Blueprint $table): void {
22+
$table->id();
23+
$table->string('title');
24+
$table->text('content');
25+
$table->timestamps();
26+
});
27+
28+
Schema::create('comments_morph', function (Blueprint $table): void {
29+
$table->id();
30+
$table->text('content');
31+
$table->morphs('commentable');
32+
$table->timestamps();
33+
});
34+
}
35+
36+
public function down(): void
37+
{
38+
Schema::dropIfExists('posts_with_morph_many');
39+
Schema::dropIfExists('comments_morph');
40+
}
41+
};

0 commit comments

Comments
 (0)