Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Our First ApiResource

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Question: have you ever gone to the store and accidentally bought too much cheese? It's the story of my life. Or maybe you have the opposite problem: you're hosting a big party and you don't have enough cheese! This is our new billion dollar idea: a platform where you can sell that extra chunk of Brie you never finished or buy a truck-load of camembert from someone who go a little too excited at the cheese market. Yep, it's what the world is asking for: a peer-to-peer cheese marketplace. We're calling it: Cheese Whiz.

For the site, maybe we're going to make it an single-page application built in React or Vue... or maybe it'll be a little more traditional: a mixture of HTML pages and JavaScript that makes AJAX requests. And maybe we'll even have a mobile app. It doesn't really matter because all of these options mean that we need to be able to expose our core functionality via an API.

Generating the Entity

But to start: forget about the API and pretend like this is a normal, boring Symfony project. Step 1 is... hmm, probably do create some database entities.

Let's open up our .env file and tweak the DATABASE_URL. My computer uses root with no password... and how about cheese_whiz for the database name.

33 lines .env
... lines 1 - 30
... lines 32 - 33

You can also create a .env.local file and override DATABASE_URL there. Using root and no password is pretty standard, so I like to add this to .env and commit it as the default.

Cool! Next, at your terminal, run

composer require maker:1.11 --dev

to get Symfony's MakerBundle... so we can be lazy and generate our entity. When that finishes, run:

php bin/console make:entity

Call the first entity: CheeseListing, which will represent each "cheese" that's for sale on the site. Hit enter and... oh! It's asking:

Mark this class as an API Platform resource?

MakerBundle asks this because it noticed that API Platform is installed. Say "yes". And before we add any fields, let's go see what that did! In my editor, yep! This created the usual CheeseListing and CheeseListingRepository. Nothing special there. Right now, the only property the entity has is id. So, what did answering "yes" to the API Platform resource question give us? This tiny annotation right here: @ApiResource.

... lines 1 - 7
* @ApiResource()
... line 10
class CheeseListing
... lines 13 - 111

The real question is: what does that activate? We'll see that soon.

But first, let's add some fields. Let's see, each cheese listing probably needs a title, string, 255, not nullable, a description, which will be a big text field, price, which I'll make an integer - this will be the price in cents - so $10 would be 1000, createdAt as a datetime and an isPublished boolean. Ok: good start! Hit enter to finish.

Congratulations! We have a perfectly boring CheeseEntity class: 7 properties with getters and setters.

... lines 1 - 11
class CheeseListing
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
private $id;
* @ORM\Column(type="string", length=255)
private $title;
* @ORM\Column(type="text")
private $description;
* @ORM\Column(type="integer")
private $price;
* @ORM\Column(type="datetime")
private $createdAt;
* @ORM\Column(type="boolean")
private $isPublished;
... lines 45 - 109

Next, generate the migration with:

php bin/console make:migration

Oh! Migrations isn't installed yet! No problem, follow the recommendation:

composer require migrations:2.0.0

But before we try generating it again, I need to make sure my database exists:

php bin/console doctrine:database:create

And now run make:migration:

php bin/console make:migration

Let's go check that out to make sure there aren't any surprises:

CREATE TABLE cheese_listing...

... lines 1 - 12
final class Version20190508193750 extends AbstractMigration
... lines 15 - 19
public function up(Schema $schema) : void
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE cheese_listing (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description LONGTEXT NOT NULL, price INT NOT NULL, created_at DATETIME NOT NULL, is_published TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
... lines 27 - 34

Yea! Looks good! Close that and run:

php bin/console doctrine:migrations:migrate

Say Hello to your API!

Brilliant! At this point, we have a completely traditional Doctrine entity except for this one, @ApiResource() annotation. But that changes everything. This tells API Platform that you want to expose this class as an API.

Check it out: refresh the /api page. Woh! Suddenly this is saying that we have five new endpoints, or "operations"! A GET operation to retrieve a collection of CheeseListings, a POST operation to create a new one, GET to retrieve a single CheeseListing, DELETE to... ya know... delete and PUT to update an existing CheeseListing. That's a full, API-based CRUD!

And this isn't just documentation: these new endpoints already work. Let's check them out next, say hello to something called JSON-LD and learn a bit about how this magic works behind the scenes.

Leave a comment!

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9