使用相关/嵌入式实体

Original Github Issue: https://github.com/zfcampus/zf-apigility-doctrine/issues/215

In a Code connected API with three entities with their respective services: Venue, Product and Location.

Venue has a ManyToOne relation with Location

   /**
     * @ORM\ManyToOne(targetEntity="Db\Entity\Location", cascade={"persist"})
     * @ORM\JoinColumn(name="location_id", referencedColumnName="id")
     *
     * @var Db\Entity\Location $location
     */
    protected $location;
    public function getLocation() {...}
    public function setLocation($location) {...}

Venue has ManyToMany relation with Product

/**
 * @ORM\ManyToMany(targetEntity="Db\Entity\Product", inversedBy="venues", cascade={"persist"})
 * @ORM\JoinTable(name="products_venues")
 **/
protected $products;
public function getProducts() {...}
public function setProducts($products) {...}
public function addProduct(Product $product)
{
    $product->addVenue($this);
    $this->products[] = $product;

    return $this;
}

And Product has a ManyToMany relation with Venue

/**
 * @ORM\ManyToMany(targetEntity="Db\Entity\Venue", mappedBy="products", cascade={"persist"})
 **/
protected $venues;
public function getVenues() {...}
public function setVenues($venues) {...}
public function addVenues($venue)
{
    $this->venues[] = $venue;

    return $this;
}

Now, I have two issues here: 1) I can create a new venue and the Location information like this:

POST /venues
{
  .
  .
  .
  "location":{
      "address":"123 Fake Street",
      "city":"cityTest",
      "country":"CountryTest",
      "postalCode":"12345"
  }
}

And a new Venue is created with a new Location registry, and see both properly linked when I GET the venue just created. When I get the Venue Id just created, I see the location object with its id among the _embedded ones, but what if I need to edit that address and link the Venue 1 with the location 2 instead of location 1 that was originally created?

Secondly, I couldn't reproduce this process with the products creating and linking the product with the venue in the same POST call where this last one is being created. Nether figured how to add an existing product to the Products collection of a particular Venue. So, which way is the more appropriate? ("1 call creates and link them all" vs "multiple creation calls + Patch calls to link them together") and what do I need to make them happen in Apigility + Doctrine?

Update Jul 20th:

Woo!! This was interesting finding. In order to update the location of a specific venue I had to add a new "location" field in the Venues service. With this, I could post a location object as an extra attribute together with the ones that /Venues requires. Or specify the id by adding location: {"id" : 123} (this also works with PATCH, which is what I've been looking for. Great!). But I realize that there is no actual validation that checks that the location with the specified Id actually exists. If none is found, then the new venue will be linked to a "null" location. :frowning:

So, after that, I saw the Apigility Doctrine validators that are included. :smile: ZF\Apigility\Doctrine\Server\Validator\ObjectExists validates that the object I'm sending actually exist in the DB. Yeeyyy!! All I had to do is add it as a validator for "location" field and specify two options based on https://github.com/doctrine/DoctrineModule/blob/master/docs/validator.md entity_class = Db\Entity\Location so the validator knows what Entity I'm trying to validate, and fields = id, which is like a validation criteria.

Now when I try to post a venue, I need to specify the location with "location": {"id":123}. This is also working with PATCH. I get a great error message when the location is not found with the specified Id, which is great. And a 500 one if no Id is provided (One problem at time, I guess...).

Now, I keep trying to find how to add an object to an entity collection. (like, adding products to a specific venue). I tried to add the "products" field to the venues service, but didn't work. I keep getting the venue without any product in the collection. This is how I'm trying to PATCH:

PATCH /venues/1
{
...
"products":[
  {"id": 5}
]
...
}

Any ideas on this?

Generally is a good idea to have atomic CRUD operations in a RESTful API.

In your case this means that it is better to CRUD your Location entity via a separate API resource called POST|GET|DELETE|PUT /location for example.

Then the API client would first have to retrieve or create a location and only after that post the venues.

Example:

Step 1. Create or retrieve the location.

POST /location
  {
    "address":"123 Fake Street",
    "city":"cityTest",
    "country":"CountryTest",
    "postalCode":"12345"
  }

or

GET /location/{somefilter}

Step 2. Create the venues

POST /venues
  {
    ...
    "location": 2 //the location id
    ...
  }