Creating Entities with the Schema Generator [API Platform]


In the previous video we covered one way of generating an entity for use in your API Platform project. I'd say that approach would be fairly natural to you if you have any experience of Symfony 2, 3, or 4.

Another way might be to use the Symfony Maker bundle.

Manually defining an entity using either of these methods is possibly good enough. But the API Platform has a dedicated Schema Generator of its own to help us document our code in a way that works best with the API Platform setup.

I'd strongly recommend you check out the link above as there's only three short sections to read, and here we are only focusing on the 'how to', rather than the 'why'.

Technically the most specific match for what our data represents is a MusicAlbum. I haven't added enough properties to our basic Album representation to fully meet the Schema. Feel free to add more data, but it's not necessary to complete this tutorial.

According to the API Platform docs, to generate an MusicAlbum data model, we may use something like this:

# api/config/schema.yaml

types:
    # Parent class of everything
    Thing:
        properties:
            name: ~
    CreativeWork:
        properties:
            datePublished: ~
    MusicPlaylist:
        properties:
            numTracks: ~
    MusicAlbum: ~

I've had problems with this. Judging by what the documentation displays as expected output, here is a version that works for me:

# api/config/schema.yaml

types:
    MusicAlbum:
        properties:
            name: ~
            datePublished: ~
            numTracks: ~

In order to generate this new MusicAlbum entity, I can run the command:

docker-compose exec php php vendor/bin/schema generate-types src config/schema.yaml

Which is a little long winded. The first part is all Docker, so removing that we see the more natural command of:

vendor/bin/schema generate-types src config/schema.yaml

And a new MusicAlbum is created:

<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * A collection of music tracks.
 *
 * @see http://schema.org/MusicAlbum Documentation on Schema.org
 *
 * @ORM\Entity
 * @ApiResource(iri="http://schema.org/MusicAlbum")
 */
class MusicAlbum
{
    /**
     * @var int|null
     *
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string|null the name of the item
     *
     * @ORM\Column(type="text", nullable=true)
     * @ApiProperty(iri="http://schema.org/name")
     */
    private $name;

    /**
     * @var \DateTimeInterface|null date of first broadcast/publication
     *
     * @ORM\Column(type="date", nullable=true)
     * @ApiProperty(iri="http://schema.org/datePublished")
     * @Assert\Date
     */
    private $datePublished;

    /**
     * @var int|null the number of tracks in this album or playlist
     *
     * @ORM\Column(type="integer", nullable=true)
     * @ApiProperty(iri="http://schema.org/numTracks")
     */
    private $numTrack;

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

    public function setName(?string $name): void
    {
        $this->name = $name;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setDatePublished(?\DateTimeInterface $datePublished): void
    {
        $this->datePublished = $datePublished;
    }

    public function getDatePublished(): ?\DateTimeInterface
    {
        return $this->datePublished;
    }

    public function setNumTrack(?int $numTrack): void
    {
        $this->numTrack = $numTrack;
    }

    public function getNumTrack(): ?int
    {
        return $this->numTrack;
    }
}

Please do watch the video for a further probe into what we get in terms of generated output.

Immediately we hit on some problems here for our implementation.

The primary of which is that the resource, and property names no longer match up with our expectations.

Also some of the values are nullable, which is unwanted. And we're missing an assertion that our Album must have at least one track.

My approach is somewhat of a hybrid. I take the generated ApiProperty annotations and apply them to my own custom Album entity:

<?php

// api/src/Entity/Album.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity()
 * @ApiResource(iri="http://schema.org/MusicAlbum")
 */
class Album
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @Assert\NotBlank()
     * @ORM\column(type="string")
     * @ApiProperty(iri="http://schema.org/name")
     */
    private $title;

    /**
     * @var \DateTime|null
     * @ORM\column(type="datetime")
     * @ApiProperty(iri="http://schema.org/datePublished")
     */
    private $releaseDate;

    /**
     * @Assert\GreaterThan(0)
     * @ORM\column(type="integer")
     * @ApiProperty(iri="http://schema.org/numTracks")
     */
    private $trackCount;

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

    /**
     * @return string|null
     */
    public function getTitle(): ?string
    {
        return $this->title;
    }

    /**
     * @param string $title
     *
     * @return Album
     */
    public function setTitle($title): Album
    {
        $this->title = $title;

        return $this;
    }

    /**
     * @return \DateTime|null
     */
    public function getReleaseDate(): ?\DateTime
    {
        return $this->releaseDate;
    }

    /**
     * @param \DateTime $releaseDate
     *
     * @return Album
     */
    public function setReleaseDate($releaseDate): Album
    {
        $this->releaseDate = $releaseDate;

        return $this;
    }

    /**
     * @return int|null
     */
    public function getTrackCount(): ?int
    {
        return $this->trackCount;
    }

    /**
     * @param int $trackCount
     *
     * @return Album
     */
    public function setTrackCount($trackCount): Album
    {
        $this->trackCount = $trackCount;

        return $this;
    }
}

For me and my needs, this is currently the best compromise.

However you generate an entity is your decision. The important part is your entity have the @ApiResource annotation on the class itself.

At this stage I am assuming you have completed, or are about to complete the final step from the previous video whereby we update our database schema, and ensure a new set of routes are available for use.

Episodes

# Title Duration
1 What will our JSON API actually do? 08:46
2 What needs to be in our Database for our Tests to work? 12:32
3 Cleaning up after each Test Run 02:40
4 Docker makes for Easy Databases 09:01
5 Healthcheck [Raw Symfony 4] 07:53
6 Send in JSON data using POST [Raw Symfony 4] 05:33
7 Keep your data nice and tidy using Symfony's Form [Raw Symfony 4] 10:48
8 Validating incoming JSON [Raw Symfony 4] 08:26
9 Nicer error messages [Raw Symfony 4] 06:23
10 GET'ting data from our Symfony 4 API [Raw Symfony 4] 08:11
11 GET'ting a collection of Albums [Raw Symfony 4] 01:50
12 Update existing Albums with PUT [Raw Symfony 4] 05:00
13 Upsetting Purists with PATCH [Raw Symfony 4] 02:39
14 Hitting DELETE [Raw Symfony 4] 02:11
15 How to open your API to the outside world with CORS [Raw Symfony 4] 07:48
16 Getting Setup with Symfony 4 and FOSRESTBundle [FOSRESTBundle] 09:11
17 Healthcheck [FOSRESTBundle] 06:14
18 Handling POST requests [FOSRESTBundle] 08:31
19 Saving POST data to the database [FOSRESTBundle] 09:44
20 Work with XML, or JSON, or Both [FOSRESTBundle] 04:31
21 Going far, then Too Far with the ViewResponseListener [FOSRESTBundle] 03:19
22 GET'ting data from your Symfony 4 API [FOSRESTBundle] 05:58
23 GET'ting a Collection of data from your Symfony 4 API [FOSRESTBundle] 01:27
24 Updating with PUT [FOSRESTBundle] 02:58
25 Partially Updating with PATCH [FOSRESTBundle] 02:15
26 DELETE'ing Albums [FOSRESTBundle] 01:27
27 Handling Errors [FOSRESTBundle] 08:58
28 Introducing the API Platform [API Platform] 08:19
29 The Entry Point [API Platform] 04:30
30 The Context [API Platform] 05:52
31 Healthcheck - Custom Endpoint [API Platform] 05:17
32 Starting with POST [API Platform] 07:08
33 Creating Entities with the Schema Generator [API Platform] 07:38
34 Defining A Custom POST Route [API Platform] 07:31
35 Finishing POST [API Platform] 06:29
36 GET'ting One Resource [API Platform] 02:50
37 GET'ting Multiple Resources [API Platform] 02:59
38 PUT to Update Existing Data [API Platform] 02:19
39 DELETE to Remove Data [API Platform] 01:15
40 No One Likes Errors [API Platform] 03:28