Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Powerful OpenAPI Spec

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.

Earlier, I said that these interactive docs come from an open source library called Swagger UI. And as long as you have some config that describes your API, like what endpoints it has and what fields are used on each endpoint, then you can generate these rich Swagger docs automatically.

Head to https://petstore3.swagger.io. This is really cool: it's is a demo project where Swagger UI is being used on a demo API. And, it has a link to the API configuration file that's powering this!

Hello OpenAPI!

Let's... see what that looks like! Woh! Yea, this JSON file fully describes the API, from basic information about the API itself, all the way down to the different URLs, like updating an existing pet, adding a new pet to the store, the responses... everything. If you have one of these files, then you can get Swagger instantly.

The format of this file is called OpenAPI, which is just a standard for how APIs should be described.

Back over in our docs, we must have that same type of config file, right? We do! Head to /api/docs.json to see our version. Yup! It looks very similar. It has paths, it describes the different operations... everything. The best part is that API Platform reads our code and generates this giant file for us. Then, because we have this giant file, we get Swagger UI.

In fact, if you click on "View Page Source", you can see that this page works by embedding the actual JSON document right into the HTML. Then, there's some Swagger JavaScript that reads that and boots things up.

OpenAPI & Free Tools

This idea of having an OpenAPI specification that describes your API is powerful... because there are an increasing number of tools that can use it. For example, go back to the API Platform documentation and click on "Schema Generator". This is pretty wild: you can use a service called "Stoplight" to design your API. That will give you an OpenAPI specification document... and then you can use the Schema Generator to generate your PHP classes from that. We're not going to use that, but it's a cool idea.

There's also an admin generator built in React - we'll play with this later - and even ways to help generate JavaScript that talks to your API. For example, you can generate a Next.js frontend by having it read from your OpenAPI spec.

The point is, Swagger UI is awesome. But even more awesome is the OpenAPI spec document behind this... which can be used for other stuff.

Models / Schema in OpenAPI

In addition to the endpoints in Swagger, it also has something called "Schemas". These are your models... and there are two - one for JSON-LD and a normal one. We're going to talk about JSON-LD in a minute, but these are basically the same.

If you open one up, wow, this is smart. It knows that our id is an integer, name is a string, coolFactor is an integer, and isPublished is a boolean. All of this info is, once again, coming from this spec document. If we search for isPublished in here... yep! There's the model describing isPublished as type boolean. The best part is that API Platform is generating this by... just looking at our code!

For example, it sees that coolFactor has an integer type:

... lines 1 - 11
class DragonTreasure
{
... lines 14 - 28
private ?int $coolFactor = null;
... lines 30 - 112
}

so it advertises it as an integer in OpenAPI. But it gets even better. Check out the id. It's set as readOnly. How does it know that? Well, id is a private property and there's no setId() method:

... lines 1 - 11
class DragonTreasure
{
... lines 14 - 16
private ?int $id = null;
... lines 18 - 36
public function getId(): ?int
{
return $this->id;
}
... lines 41 - 112
}

And so, it correctly inferred that id must be readOnly.

We can also help API Platform. Find the $value property... there it is... and add a little documentation above this so people know that This is the estimated value of this treasure, in gold coins.

... lines 1 - 11
class DragonTreasure
{
... lines 14 - 24
/**
* The estimated value of this treasure, in gold coins.
*/
#[ORM\Column]
private ?int $value = null;
... lines 30 - 115
}

Head over, refresh... and check out the model down here. For value... it shows up! The point is: if you do a good job writing your PHP code and documenting it, you're going to get rich API documentation thanks to OpenAPI, with zero extra work.

Next: Let's talk about these weird @ fields, like @id, @type, and @context. These come from something called JSON-LD: a powerful addition to JSON that API Platform leverages.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.0.8
        "doctrine/annotations": "^1.0", // 1.14.2
        "doctrine/doctrine-bundle": "^2.8", // 2.8.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.0
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.64.1
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.15.3
        "symfony/asset": "6.2.*", // v6.2.0
        "symfony/console": "6.2.*", // v6.2.3
        "symfony/dotenv": "6.2.*", // v6.2.0
        "symfony/expression-language": "6.2.*", // v6.2.2
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.3
        "symfony/property-access": "6.2.*", // v6.2.3
        "symfony/property-info": "6.2.*", // v6.2.3
        "symfony/runtime": "6.2.*", // v6.2.0
        "symfony/security-bundle": "6.2.*", // v6.2.3
        "symfony/serializer": "6.2.*", // v6.2.3
        "symfony/twig-bundle": "6.2.*", // v6.2.3
        "symfony/ux-react": "^2.6", // v2.6.1
        "symfony/validator": "6.2.*", // v6.2.3
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.0
        "symfony/yaml": "6.2.*" // v6.2.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.2.*", // v6.2.1
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/stopwatch": "6.2.*", // v6.2.0
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.4
        "zenstruck/foundry": "^1.26" // v1.26.0
    }
}