0
votes

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"
      }
2
Consider removing goals-read from the denormalization_context groups, as denormalization groups are used to specify incoming payloads, not outgoing ones. Did you try to use configure the context(s) without using the attribute property? E.g @ApiResource(normalizationContext={"groups"={"goals-read"}}). Or by configuring the normalization_context under the collectionOperations GET operation instead? - Jeroen van der Laan

2 Answers

1
votes

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;
}
0
votes

The thing is you use wrong construction in class Goals:

* @ORM\JoinColumns({
*   @ORM\JoinColumn(name="processus_id", referencedColumnName="id")
* })

The annotation @JoinColumns can be used only for @ManyToMany relationship, inside of @JoinTable annotation.

You can take a look on related Doctrine documentation

So, in your case you have to use:

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

And yes, I've added different serialization group goals-write for denormalization context, as @Jeroen van der Laan suggested to you in his comment.

Hope I can help you.