Cyril CHAPON
Cyril CHAPON

Reputation: 3747

symfony api-platform depth

So far we've been struggling with Symfony, Doctrine, and Serializer depth.

I'd like to be able to provide just one-level-depth JSON REST API with Symfony, allowing me to manage my "foreign key" and relation logic directly from the view.

GET /people/1

{
  id:1,
  name:"theonewhoknocks",
  friends: [3, 12, 25]
}

Using FosRESTBundle, we've been strugling at succeeding on that. (we've seen "depth" anotations and "groups" views for models, but none of this fit our need).

The question is simple, before we make a choice for our future API, we have to know:

is api-platform able to provide a dead simple one level (with apparent foreign keys) REST API ?

Upvotes: 4

Views: 7569

Answers (3)

user1032531
user1032531

Reputation: 26331

As @leberknecht indicated, you might not get the results you are looking for using MaxDepth.

This script:

#[ApiResource(
    normalizationContext: ['enable_max_depth'=>true],
)]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 180)]
    private ?string $someProperty = null;

    #[ORM\ManyToOne(targetEntity: UserClass::class)]
    #[SymfonyMaxDepth(1)]
    private ?User $createdBy = null;
}

will return:

{
    "id": 123,
    "someProperty": "objectProperty",
    "createdBy": {
        "id": 20,
        "someProperty": "parentProperty",
        "createdBy": "users/5"
    }
}

and this script:

#[ApiResource]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 180)]
    private ?string $someProperty = null;

    #[ORM\ManyToOne(targetEntity: UserClass::class)]
    #[ApiProperty(readableLink: false, writableLink: false)]
    private ?User $createdBy = null;
}

will return

{
    "id": 123,
    "someProperty": "objectProperty",
    "createdBy": "users/20"
}

Upvotes: 3

leberknecht
leberknecht

Reputation: 1736

Be aware that the MaxDepth on the symfony serializer might not behave as expected, see https://github.com/symfony/symfony/issues/33466

The annotation basically says "from here on, render max N instances of THE SAME CLASS into the graph"

So given a pseudo structure like

Class A:
    @MaxDepth(1)
    Class B:
        Class C:
            Class D:

would render the whole thing A.B.C.D, while

Class A:
    @MaxDepth(1)
    Class B:
        Class B:
            Class B:

would only render A.B.B

Which is quite different from what e.g. JMS serializer is doing, where MaxDepth really means "From here on, max N steps into the relation graph".

Bad thing is that JMS serializer is not supported by api-platform: https://github.com/api-platform/api-platform/issues/753

So the answer to your question, atm, is: no. :/

Upvotes: 3

Nicolas Janel
Nicolas Janel

Reputation: 3075

API Platform can handle that using the Serializer Symfony bundle and its annotation set.

To define what will be returned by an operation we use a normalizationContext which define group(s) of property to include in result of an api operation. Property to include have then this group name linked to @Groups serializer annotation

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
 * @ORM\Entity()
 * @ApiResource(normalizationContext={"groups"={"read"}}
 */
class Book {
    /**
     * @ORM\Column()
     * @Groups({"read"})
     */
    private $title;

    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="books")
     * @Groups({"read"})
     */
    private $author;

    /**
     * Will not be included in result
     */
    private $secret_comment;

}

If a relation column is in a Group as $author here, properties defined in a group in the child class will be included in the result

/**
 * @ORM\Entity()
 * @ApiResource(normalizationContext={"groups"={"read"}})
 */
class User {
    /**
     * @ORM\Column()
     * @Groups({"read"})
     */
    private $username;
}

In order to avoid cyclic recursion you can specify the max depth of child relation joins with annotation @MaxDepth(n) where n is the max depth (1 in your case). This annotation has to be enabled with enable_max_depth property in serializer context of the @ApiResource annotation

/**
 * @ApiPlatform(normalizationContext={"groups"={"read"}, "enable_max_depth"=true})
 */
class Book {
    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="books")
     * @Groups({"read"})
     * @MaxDepth(1)
     */
    private $author;
}

Please note that API Platform is in this case an agregation of existing bundles and features. Refer to the main bundles for detailed informations (here the Symfony Serializer bundle)

Upvotes: 4

Related Questions