Description
API Platform version(s) affected: 4.0.12
Description
When trying to create, replace or update an embbeded relation using POST, PUT or PATCH Operations, that embedded relation is not modified.
As far as I know, using denormalization we should be able to create, replace or update and embbeded relation using its fields instead of IRI.
How to reproduce
Having a couple of simple Models, Father
and Son
, defined as following:
<?php
namespace App\Models;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Serializer\Annotation\Groups;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Collection;
#[ApiResource(
shortName: 'Father',
normalizationContext: ['groups' => ['father:read']],
denormalizationContext: ['groups' => ['father:write']],
)]
class Father extends Model
{
protected $table = 'fathers';
protected $primaryKey = 'id_father';
protected $fillable = ['name'];
#[ApiProperty(writable: true)]
#[Groups(['father:read','father:write','son:read','son:write'])]
private ?string $name = null;
#[Groups(['father:read','father:write','son:write'])]
private ?Collection $sons = null;
/**
* Sons
*
* @return HasMany
*/
public function sons(): HasMany
{
return $this->hasMany(Son::class, 'father_id', 'id_father');
}
}
and
<?php
namespace App\Models;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Serializer\Annotation\Groups;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[ApiResource(
shortName: 'Son',
normalizationContext: ['groups' => ['son:read']],
denormalizationContext: ['groups' => ['son:write']],
)]
class Son extends Model
{
protected $table = 'sons';
protected $primaryKey = 'id_son';
protected $fillable = ['name'];
#[ApiProperty(writable: true)]
#[Groups(['son:read','son:write','father:read','father:write'])]
private ?string $name = null;
#[Groups(['son:read','son:write','father:write'])]
private ?Father $father = null;
/**
* Father
*
* @return BelongsTo
*/
public function father(): BelongsTo
{
return $this->belongsTo(Father::class, 'father_id', 'id_father');
}
}
Created using following migrations:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('fathers', function (Blueprint $table) {
$table->increments('id_father');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('fathers');
}
};
and
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sons', function (Blueprint $table) {
$table->increments('id_son');
$table->string('name');
$table->unsignedInteger('father_id')->nullable();
$table->timestamps();
$table->foreign('father_id')->references('id_father')->on('fathers');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sons');
}
};
Examples:
FATHER
- I would like to create a father with 2 children.
Using POST on Father
:
{
"name": "new father no1", // new `Father`
"sons": [
{
"name": "new son n1" // new `Son`
},
{
"name": "new son no2" // new `Son`
}
]
}
It is supposed to create 1 new Father
and 2 new Son
but i returns:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sons' in 'field list'
The same if instead of creating 2 new Son
I just create a new father and relate it with 2 existing Son
:
{
"name": "new father no1", // new `Father`
"sons": [
{
"name": "son n1" // existing `Son`
},
{
"name": "son no2" // existing `Son`
}
]
}
The same using IRI:
{
"name": "new father no1", // new `Father`
"sons": [
"/api/sons/3", // existing `Son`
"/api/sons/4" // existing `Son`
]
}
SON
- I would like to create a new
Son
with a newFather
.
Using POST on Son
{
"name": "new son", // new `Son`
"father": {
"name": "new father" // new `Father`
}
}
It is supposed to create 1 new Son
and a new Father
but a new Son
with no Father
is created.
Creating a new Son
related to an existing Father
using IRI:
{
"name": "new son", // new `Son`
"father": "/api/fathers/2" // exsiting `Father`
}
It actually creates the new Son
with the proper existing Father
- I would like to update
Father
's name fromSon
.
Using PATCH on Son
with id = 1:
{
"name": "changing new of son 1", // exsting `Son`
"father": {
"@id": "/api/fathers/2", // related `Father`
"name": "new fathres name"
}
}
The father's name does not change.
I didn't add all the possibilities using POST, PATCH and PUT but I think the problem can be understood with the previous examples.
Possible Solution
On a Symfonycasts course API Platform 3 Part 1: Mythically Good RESTful APIs I've seen something called cascade: ['persist']
here and here not sure how to reproduce it on Laravel if it is needed.
Also, I am not sure if some kind of additional denormalization is needed neither because I didn't see anything related on Documentation, although I am aware Larave'ls one still need some improvements. In that case, could someone explain how to do it?
Thank you