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

Duplicate ressources in ManyToMany relations #799

Closed
guilhemVB opened this issue Aug 7, 2018 · 9 comments
Closed

Duplicate ressources in ManyToMany relations #799

guilhemVB opened this issue Aug 7, 2018 · 9 comments
Labels

Comments

@guilhemVB
Copy link

guilhemVB commented Aug 7, 2018

Hi,

I've a problem with serialization groups and ManyToMany relations in sub ressoucres.
I've a Entity "Order" with one attribute CustomerBy (ManyToOne to entity "Customer") and one attribute CustomerFor (ManyToOne to entity "Customer" too).
Customer have an attribute addresses (ManyToMany to entity Address)

When Order have the same Customer in "CustomerBy " and "CustomerFor", all the addresses are duplicated:

Order:

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert; 

/**
 * @ApiResource(
 *     attributes={"normalization_context"={"groups"={"order_read", "customer_read", "address_read"}}}
 * )
 * @ORM\Entity
 * @ORM\Table(name="`order`")
 */
class Order
{

    /**
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     * @Groups({"order_read"})
     */
    private $id;
    
    /**
     * @ORM\ManyToOne(targetEntity="Customer")
     * @ORM\JoinColumn(nullable=false)
     * @Groups({"order_read"})
     */
    public $customerBy;
    
    /**
     * @ORM\ManyToOne(targetEntity="Customer")
     * @ORM\JoinColumn(nullable=false)
     * @Assert\NotNull()
     * @Groups({"order_read"})
     */
    public $customerFor;

    public function getId()
    {
        return $this->id;
    }
}

Customer:

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ApiResource(
 *     attributes={"normalization_context"={"groups"={"customer_read"}}}
 * )
 * @ORM\Entity
 */
class Customer
{

    /**
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     * @Groups({"customer_read"})
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     * @Groups({"customer_read"})
     */
    public $name;
    
    /**
     * @ORM\ManyToMany(targetEntity="Address")
     * @ORM\JoinColumn(nullable=false)
     * @Groups({"customer_read"})
     */
    public $addresses;

    public function __construct()
    {
        $this->addresses = new ArrayCollection();
    }

    public function getId()
    {
        return $this->id;
    }
}

Address:

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ApiResource(
 *     attributes={"normalization_context"={"groups"={"address_read"}}}
 * )
 * @ORM\Entity
 */
class Address
{

    /**
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     * @Groups({"address_read"})
     */
    private $id;
    
    /**
     * @ORM\Column(type="string")
     * @Groups({"address_read"})
     */
    public $name;


    public function getId()
    {
        return $this->id;
    }
}

When I send a GET request to /api/orders/1 I've the following result:

{
  "id": 1,
  "customerBy": {
    "id": 1,
    "name": "Customer 1",
    "addresses": [
      {
        "id": 1,
        "name": "Address 1"
      },
      {
        "id": 1,
        "name": "Address 1"
      },
      {
        "id": 2,
        "name": "Address 2"
      },
      {
        "id": 2,
        "name": "Address 2"
      }
    ]
  },
  "customerFor": {
    "id": 1,
    "name": "Customer 1",
    "addresses": [
      {
        "id": 1,
        "name": "Address 1"
      },
      {
        "id": 1,
        "name": "Address 1"
      },
      {
        "id": 2,
        "name": "Address 2"
      },
      {
        "id": 2,
        "name": "Address 2"
      }
    ]
  }
}

You can see that all "addresses" are duplicated :/

Did I miss something or is it a bug ?

My Api-platform version:

"name": "api-platform/core",
"version": "v2.3.0"

Thanks

@ilicmsreten
Copy link

@guilhemVB I open PR in api-platform/core

@soyuka
Copy link
Member

soyuka commented Sep 4, 2018

todo before closing: Add a behat test in api-platform/core for this particular behavior

@ilicmsreten
Copy link

@soyuka I open PR

@errogaht
Copy link

Hi guys! I got the same issue, do you have an idea how we can fix it?

@soyuka
Copy link
Member

soyuka commented Oct 18, 2018

solution is to simply disable the eager loading feature or build your own query. I'm really not sure how we could fix this otherwise...

For the #799 example:

/**
 * @ApiResource(
 *     attributes={"normalization_context"={"groups"={"order_read", "customer_read", "address_read"}}, "force_eager"=false}
 * )
 * @ORM\Entity
 * @ORM\Table(name="`order`")
 */
class Order
{
}

@soyuka soyuka closed this as completed Oct 18, 2018
@soyuka soyuka reopened this Oct 18, 2018
@errogaht
Copy link

@soyuka, if I'll set "force_eager"=false is there could be some side-effects? eg. filters will not work correctly or pagination or something else?

@soyuka
Copy link
Member

soyuka commented Oct 18, 2018

Everything will work, this just skips the magic in building queries based on what you want to retrieve. Basically EagerLoading in api platform builds the sql query with JOINs where it can. By disabling it you'll keep the default doctrine behavior which, in this case, is more appropriate (lazy loading).

@errogaht
Copy link

@soyuka , thanks, I'll disable force_eager system-wide in api-platform configuration. should be fine I believe.

@soyuka
Copy link
Member

soyuka commented Oct 18, 2018

You can also disable it per-operation or per-resource wise see https://api-platform.com/docs/core/performance/#force-eager

@soyuka soyuka added the wontfix label Apr 6, 2019
@soyuka soyuka closed this as completed Apr 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants