Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Laravel] POST/PUT/PATCH don't work for embedded relations #6882

Open
llei4 opened this issue Dec 20, 2024 · 0 comments
Open

[Laravel] POST/PUT/PATCH don't work for embedded relations #6882

llei4 opened this issue Dec 20, 2024 · 0 comments

Comments

@llei4
Copy link

llei4 commented Dec 20, 2024

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 new Father.

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 from Son.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant