API Platform Con 2021: join us in Lille (France) or online on September 10 for our 1st international conference.
API Platform Con 2021: join us in Lille (France) or online on September 10 for our 1st international conference.
API Platform Con 2021: join us in Lille (France) or online on September 10 for our 1st international conference.

Subresources

A subresource is a collection or an item that belongs to another resource. API Platform makes it easy to create such operations.

Subresources screencast
Watch the Subresources screencast

The starting point of a subresource must be a relation on an existing resource. For example, let's create two entities (Question, Answer) and set up a subresource so that /question/42/answer gives us the answer to the question 42:

<?php
// api/src/Entity/Answer.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
#[ApiResource]
class Answer
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column
     */
    public $content;

    /**
     * @ORM\OneToOne(targetEntity="Question", mappedBy="answer")
     */
    public $question;

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

    // ...
}

// api/src/Entity/Question.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
#[ApiResource]
class Question
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column
     */
    public $content;

    /**
     * @ORM\OneToOne(targetEntity="Answer", inversedBy="question")
     * @ORM\JoinColumn(referencedColumnName="id", unique=true)
     */
    #[ApiSubresource]
    public $answer;

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

    // ...
}

Note that all we had to do is to set up #[ApiSubresource] on the Question::answer relation. Because the answer is a to-one relation, we know that this subresource is an item. Therefore the response will look like this:

{
  "@context": "/contexts/Answer",
  "@id": "/answers/42",
  "@type": "Answer",
  "id": 42,
  "content": "Life, the Universe, and Everything",
  "question": "/questions/42"
}

If you put the subresource on a relation that is to-many, you will retrieve a collection.

Last but not least, subresources can be nested, such that /questions/42/answer/comments will get the collection of comments for the answer to question 42.

Note: only for GET operations are supported at the moment

Using Serialization Groups

You may want custom groups on subresources, you can set normalization_context or denormalization_context on that operation. To do so, add a subresourceOperations node. For example:

<?php
// api/src/Entity/Answer.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;

 #[ApiResource(
    subresourceOperations: [
        'api_questions_answer_get_subresource' => [
            'method' => 'GET',
            'normalization_context' => [
                'groups' => ['foobar'],
            ],
        ],
    ],
)]
class Answer
{
    // ...
}

In the previous examples, the method attribute is mandatory, because the operation name doesn't match a supported HTTP method.

Note that the operation name, here api_questions_answer_get_subresource, is the important keyword. It'll be automatically set to $resources_$subresource(s)_get_subresource. To find the correct operation name you may use bin/console debug:router.

Using Custom Paths

You can control the path of subresources with the path option of the subresourceOperations parameter:

<?php
// api/src/Entity/Question.php

#[ApiResource(
    subresourceOperations: [
        'api_questions_answer_get_subresource' => [
            'method' => 'GET',
            'path' => '/questions/{id}/all-answers',
        ],
    ],
)]
class Question
{
}

Access Control of Subresources

The subresourceOperations attribute also allows you to add an access control on each path with the attribute security.

<?php
// api/src/Entity/Answer.php

 #[ApiResource(
    subresourceOperations: [
        'api_questions_answer_get_subresource' => [
            'security' => "is_granted('ROLE_AUTHENTICATED')",
        ],
    ],
 )]
 class Answer
 {
 }

Limiting Depth

You can control depth of subresources with the parameter maxDepth. For example, if the Answer entity also has a subresource such as commentsand you don't want the route api/questions/{id}/answers/{id}/comments to be generated. You can do this by adding the parameter maxDepth in the ApiSubresource annotation or YAML/XML file configuration.

<?php
// api/src/Entity/Question.php

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;

#[ApiResource]
class Question
{
    #[ApiSubresource(
        maxDepth: 1,
    )]
    public $answer;

    // ...
}