Search code examples
jsonsymfonynormalizationapi-platform.comsymfony5

API Platform : Groups with nested entities work only when removing @ApiResource


API platform uses by default IRI's to GET nested entities but I'm trying to GET entity normalized with normalization_context and groups. It work but only when i remove @ApiResource from the nested entity and i need it to expose my CRUD services.

Example

/**
 * @ApiResource(
 *       attributes={
 *     "normalization_context"={"groups"={"goals-read"}},
 *     "denormalization_context"={"groups"={"goals-read"}}
 * })
 *
 * )
 *
 * Goals
 * @ApiFilter(OrderFilter::class, properties={"id"}, arguments={"orderParameterName"="order"})
 * @ORM\Table(name="goals", indexes={@ORM\Index(name="IDX_C7241E2FA55629DC", columns={"processus_id"})})
 * @ORM\Entity
 */
class Goals
{
    /**
     * @var int
     * @Groups("goals-read")
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

// some fields ...

    /**
     * @var Processus
     * @ORM\ManyToOne(targetEntity="Processus")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="processus_id", referencedColumnName="id")
     * })
     * @Groups({"goals-read"})
     * @ApiProperty(readableLink=false, writableLink=false)
     */
    private $processus;

    /**
     * @var Issues
     * @ORM\ManyToOne(targetEntity="Issues")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="issues_id", referencedColumnName="id")
     * })
     * @Groups({"goals-read"})
     * @ApiProperty(readableLink=false, writableLink=false)
     */
    private $issue;

Processus Class

/**
 * Processus
 * @ApiResource()
 * @ORM\Table(name="processus", indexes={@ORM\Index(name="IDX_EEEA8C1DC35E566A", columns={"family_id"})})
 * @ORM\Entity
 */
class Processus
{
    /**
     * @var int
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @Groups({"goals-read"})
     */
    private $id;

    /**
     * @var string|null
     * @ORM\Column(name="name", type="string", length=255, nullable=true)
     * @Groups({"goals-read"})
     */
    private $name;

Response body

{
  "@context": "/api/contexts/Goals",
  "@id": "/api/goals",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/api/goals/29",
      "@type": "Goals",
      "id": 29,
      "description": "string",
      "comment": "string",
      "currentState": "string",
      "goalToReach": "string",
      "advancement": "string",
      "indicator": 0,
      "q1": "string",
      "q2": "string",
      "q3": "string",
      "q4": "string",
      "nextYear": "string",
      "nextTwoYear": "string",
      "processus": "/api/processuses/2",
      "issue": "/api/issues/5"
}

when removing @ApiResource()

// JSON Response

...
...
...
 "processus": {
        "@type": "Processus",
        "@id": "_:938",
        "id": 2,
        "name": "string"
      }

Solution

  • Turns out the solution was right under our noses, with the @ApiProperty(readableLink=false, writableLink=false) annotation being the culprit. The documentation regarding this annotation clearly states that this forces referenced entities to be serialized as IRI's (overruling serialization groups). Removing this annotation from the Goals::$processus property will have API Platform use the goals-write serialization group to serialize the referenced Processus entity.

    Here is a working example written in PHP 8 and API Platform 2.6 (as that's what I currently have deployed while writing this, don't think versions here are relevant though):

    Goals

    <?php declare(strict_types = 1);
    
    //..
    
    /**
     * @ORM\Entity
     */
    #[ApiResource(
        normalizationContext: [
            'groups' => [
                'goals-read'
            ]
        ],
    )]
    #[ApiFilter(
        OrderFilter::class,
        properties: ['id'],
        arguments: [
            'orderParameterName' => 'order'
        ]
    )]
    class Goals
    {
        /**
         * @ORM\Id
         * @ORM\GeneratedValue(
         *      strategy="IDENTITY"
         * )
         * @ORM\Column(
         *      type="integer",
         *      nullable=false
         * )
         * @Groups({
         *      "goals-read"
         * })
         */
        private ?int $id = null;
    
        /**
         * @ORM\ManyToOne(
         *      targetEntity="Processus"
         * )
         * @ORM\JoinColumn(
         *      name="processus_id",
         *      referencedColumnName="id"
         * )
         * @Groups({
         *      "goals-read"
         * })
         * NO MORE @ApiProperty ANNOTATION HERE
         */
        private ?Processus $processus = null;
    }
    

    Processus

    <?php declare(strict_types = 1);
    
    //..
    
    /**
     * @ORM\Entity
     */
    #[ApiResource]
    class Processus
    {
        /**
         * @ORM\Id
         * @ORM\GeneratedValue(
         *      strategy="IDENTITY"
         * )
         * @ORM\Column(
         *      type="integer",
         *      nullable=false
         * )
         * @Groups({
         *      "goals-read"
         * })
         */
        private ?int $id = null;
    
        /**
         * @ORM\Column(
         *      name="name",
         *      type="string",
         *      length=255,
         *      nullable=true
         * )
         * @Groups({
         *     "goals-read"
         * })
         */
        private ?string $name = null;
    }