Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
We're currently looking at something called Swagger: an open source API documentation interface. We're going talk more about it soon, but the idea is basically this: if you have an API - built in any language - and you create some configuration that describes that API in a format that Swagger understands, boom! Swagger can render this beautiful interactive documentation for you. Behind the scenes, API Platform is already preparing that configuration for Swagger.
Let's play with it! Open the POST endpoint. It says what this does and shows how the JSON should look to use it. Nice! Click "Try it out"! Let's see what's in my kitchen - some "Half-eaten blue cheese", which is still... probably ok to eat. We'll sell it for $1. What a bargain! And... Execute!
Um... what happened? Scroll down. Woh! It just made a POST
request to /api/cheese_listings
and sent our JSON! Our app responded with a 201 status code and... some weird-looking JSON keys: @context
, @id
and @type
. Then it has the normal data for the new cheese listing: the auto-increment id
, title
, etc. Hey! We already have a working API... and this just proved it!
Close up the POST and open the GET
that returns a collection of cheese listings. Try this one out too: Execute! Yep... there's our one listing... but it's not raw JSON. This extra stuff is called JSON-LD. It's just normal JSON, but with special keys - like @context
- that have a specific meaning. Understanding JSON-LD is an important part of leveraging API Platform - and we'll talk more about it soon.
Anyways, to make things more interesting - go back to the POST endpoint and create a second cheese listing - a giant block of cheddar cheese... for $10. Execute! Same result: 201 status code and id 2.
Try the collection GET
endpoint again. And... alright! Two results, with ids 1 and 2. And if we want to fetch just one cheese listing, we can do that with the other GET
endpoint. As you can see, the id
of the cheese listing that we want to fetch is part of the URL. This time, when we click to try it, cool! It gives us a box for the id. Use "2" and... Execute!
This makes a very simple GET request to /api/cheese_listings/2
, which returns a 200
status code and the familiar JSON format.
Content-Type Negotiation
How cool is this! A full API "CRUD" with no work. Of course, the trick will be to customize this to our exact needs. But sheesh! This is an awesome start.
Let's try to hit our API directly - outside of Swagger - just to make sure this isn't all an elaborate trick. Copy the URL, open a new tab, paste and... hello JSON! Woh! Hello... API doc page again?
It scrolled us down to the documentation for this endpoint and executed it with id 2... which is cool... but what's going on? Do we actually have a working API or not?
Built into API Platform is something called Content-Type negotiation. Conveniently, when you execute an operation, Swagger shows you how you could make that same request using curl at the command line. And it includes one critical piece:
-H "accept: application/ld+json"
That says: make a request with an Accept
header set to application/ld+json
. The request is hinting to API Platform that it should return the data in this JSON-LD format. Whether you realize it or not, your browser also sends this header: as text/html
... cause... it's a browser. That basically tells API Platform:
Hey! I want the CheeseListing with id 2 in HTML format.
API Platform responds by doing its best to do exactly that: it returns the HTML Swagger page with CheeseListing id 2 already showing.
Faking the Content-Type
This isn't a problem for an API client because setting an Accept
header is easy. But... is there some way to kinda... "test" the endpoint in a browser? Totally! You can cheat: add .jsonld
to the end of the URL.
Boom! This is our API endpoint in the JSON-LD format. I called this "cheating" because this little "trick" of adding the extension is really only meant for development. In the real world, you should set the Accept
header instead, like if you were making an AJAX request from JavaScript.
And, check this out: change the extension to .json
. That looks a bit more familiar!
This is a great example of the API Platform philosophy: instead of thinking about routes, controllers and responses, API Platform wants you to think about creating API resources - like CheeseListing
- and then exposing that resource in a variety of different formats, like JSON-LD, normal JSON, XML or exposing it through a GraphQL interface.
Where do These Routes Come From?
Of course, as awesome as that is, if you're like me, you're probably thinking:
This is cool... but how did all these endpoints get magically added to my app?
After all, we don't normally add an annotation to an entity... and suddenly get a bunch of functional pages!
Find your terminal and run:
php bin/console debug:router
Cool! API platform is bringing in several new routes: api_entrypoint
is sort of the "homepage" of our api, which, by the way, can be returned as HTML - like we've been seeing - or as JSON-LD, for a machine-readable "index" of what's included in our API. More on that later. There's also a /api/docs
URL - which, for HTML is the same as going to /api
, another called /api/context
- more on that in a minute - and below, 5 routes for the 5 new endpoints. When we add more resources later, we'll see more routes.
When we installed the API Platform pack, its recipe added a config/routes/api_platform.yaml
file.
api_platform: | |
resource: . | |
type: api_platform | |
prefix: /api |
This is how API Platform magically adds the routes. It's not very interesting, but see that type: api_platform
? That basically says:
Hey! I want API Platform to be able to dynamically add whatever routes it wants.
It does that by finding all the classes marked with @ApiResource
- just one right now - creating 5 new routes for the 5 operations, and prefixing all the URLs with /api
. If you want your API URLs to live at the root of the domain, just change this to prefix:
/.
I hope you're already excited, but there is so much more going on than meets the eye! Next, let's talk about the OpenAPI spec: an industry-standard API description format that gives your API Swagger superpowers... for free. Yes, we need to talk a little bit of theory - but you will not regret it.
23 Comments
Hey Monoranjan,
Oh, haha, it's a tircky Google Chrome plugin that highlights JSON reponses :) It's called JSONView, you can install this extension for *your* browser and your JSON responses will look exactly like Ryan's ;) Btw, we mentioned this earlier in this course, or in previous courses, but I agree, it's easy to miss :) But now you know! Happy coding with nice JSON highlights now ;)
Cheers!
thank you very much for your answer. now it's working successfully
Hi guys,
Hopefully you can give me a suggestion for my issue: I would like to start using a REST api in my existing project, however the problem is that it contains 300+ different entity types. For API-platform it seems not to be a problem, however the Swagger UI interface crashes my browser, just because of all not-yet-filled schemas (and endpoints for 2 entities as test). Do you a way to split the API in sections, documented on separate pages? Or is the project simply too big for Swagger UI?
Hey Thijs-jan V.,
Wow this looks like a BIG project, I'm not sure about how many entities can process default swagger connection, but there is a way to totally customise how it works. Probably it a little complex but it can be a good way. You can read about swagger here https://api-platform.com/do... I like the section about "Overriding the OpenAPI Specification"
PS BTW another tip is to use totally custom swagger it's not too difficult, but very cool to play =)
Cheers!
Hi,
at 8:08 you showed how to change the prefix.
Is it possible to change the prefix for the whole symfony app? so every single route even stuff like "/_profiler/ "
?
I'm running multiple symfony applications parallel and I want each of them to have a distinct prefix so otherwise some features like the profile don't work.
For example I want that "localhost/users"
is the true root of app1 not just localhost
and "localhost/cheeselisting"
be the root of app2. So the path for the profile would be "localhost/user/_profiler"
for the user app.
I have an ingress that decides where to route the request based on the path.
for example: /users/* -> user server == every request that starts with
/users will be passed the the user server<br />
/cheeses/* -> user server == every request that starts with
/cheeses will be passed the the cheeses server
I hope I could explain it understandable ^^
Hey Ecludio,
Check the Kernel::configureRoutes() in your src/Kernel.php. That method imports all the routes into the system, that import() call allows you to set a prefix via 2nd argument, by default it's "/". I believe that's exactly what you need :)
Cheers!
Hi!
Why is my api platform documentation empty?
When i go to the POST option, the body documentation is empty. After i click 'try it out' i only see an empty JSON {}. I had to write the JSON by hand following the example in the video like this:{<br /> "title": "Cheese1",<br /> "description": "description 1",<br /> "price": 1000,<br /> "createdAt": "2021-02-02/22:32:23+00:00Z",<br /> "isPublished": true<br />}
And i get the following error:
<blockquote>"An exception occurred while executing 'INSERT INTO cheese_listing (title, description, price, created_at, is_published) VALUES (?, ?, ?, ?, ?)' with params [null, null, null, null, null]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1048 Column 'title' cannot be null",</blockquote>
Why can't i see the documentation inside the body to send a POST request to create my first item? and why i'm getting 500 status after sending all "null"?
My Entity has the @ApiResource() and the fields are created as in the tutorial. I think i didn't miss any step, the migrations didn't throw any errors either.
Am i missing something?
Thanks!.
Hey Saul,
Hm, difficult to say, it looks like you missed something important from the tutorial. Did you download the course code and started from the start/ directory? Or are you following this course on your own project? Please, also, double-check the namespaces of the annotation you're using in your entities, probably you forgot the namespace or used incorrect one. I'd recommend you literally expand all the lines in the code block for the entity you're interested in and copy/paste it into your project. Does it help? Or you still don't see anything? Btw, are you in dev Symfony mode? Please, also try to clear the cache just in case! Does it help?
I really hope this helps!
Cheers!
Hi,
is there any way to change default scheme in Swagger UI to HTTP instead of HTTPS?
Thanks.
Hey Gets,
Do you run the project via "symfony serve" command? If so, try "symfony serve --no-tls", or probably better "symfony serve --allow-http" to allow both HTTP and HTTPS connections.
I hope this helps!
Cheers!
Hello,
I have a weird unwanted addition called <b>additionalProp1</b> in all my entities docs Example Value<br />{<br /> "@context": "string",<br /> "@id": "string",<br /> "@type": "string",<br /> "id": 0,<br /> "title": "string",<br /> "created_at": "2020-12-15T13:20:04.604Z",<br /> "updated_at": "2020-12-15T13:20:04.605Z",<br /> "additionalProp1": {}<br />}<br />
Have no idea where it coming from. In the Schema tab it is not present<br />content_providers:jsonld-Read{description:ContentProviders@contextstring<br />readOnly: true@idstring<br />readOnly: true@typestring<br />readOnly: trueidinteger<br />readOnly: truetitle*string<br />nullable: truecreated_atstring($date-time)updated_atstring($date-time)}<br />
Hey @isTom
Does it only appear on your docs or it's also a field of your API endpoints?
They only appear on /api/docs. API responses look ok.
Hey, sorry for my late response. If the API endpoints are ok, then I believe you added that extra information to your docs through any of the methods shown in this page https://api-platform.com/do...
Hello Diego,
I cannot identify any additional information in my class and sure there is nothing named <b>additionalProp1</b>
`
/**
- ContentProviders
* - @ORM\Table(name="content_providers",
- indexes={
- @ORM\Index(name="title", columns={"title"}),
- },
- uniqueConstraints={@ORM\UniqueConstraint(columns={"identification"})}
- )
- @UniqueEntity(fields={"identification"}, message="app.unique_value_required")
- @ApiResource(
- collectionOperations={"get", "post"},
- itemOperations={
- "get"={},
- "put"
- },
- shortName="content_providers",
- normalizationContext={"groups"={"content_providers:read"}, "swagger_definition_name"="Read"},
- denormalizationContext={"groups"={"content_providers:write"}, "swagger_definition_name"="Write"},
- )
- @ORM\Entity
*/
class ContentProviders
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned"=true})
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
* @Groups({"content_providers:read"})
*/
private $id;
/**
* ContentProviders in the VodSource.
*
* @var VodSource
*
* @ORM\OneToMany(targetEntity="App\Entity\Main\VodSource", mappedBy="contentProvider")
* @Groups({"content_providers:read"})
**/
protected $vodSource;
/**
* ContentProviders in the VodCreated.
*
* @var VodCreated
*
* @ORM\OneToMany(targetEntity="App\Entity\Main\VodCreated", mappedBy="contentProvider")
**/
protected $vodCreated;
/**
* @var string|null
*
* @ORM\Column(name="title", type="string")
* @Groups({"content_providers:read", "content_providers:write"})
* @Assert\NotBlank()
*/
private $title;
/**
* @var string|null
*
* @ORM\Column(name="identification", type="string")
* @Groups({"content_providers:read", "content_providers:write"})
* @Assert\NotBlank()
*/
private $identification;
/**
* @var \DateTime|null
*
* @ORM\Column(name="created_at", type="datetime", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
* @Groups({"content_providers:read"})
*/
private $createdAt;
/**
* @var \DateTime|null
*
* @ORM\Column(name="updated_at", type="datetime", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
* @Groups({"content_providers:read"})
*/
private $updatedAt;
`
Ha! That's weird. What if you commit everything and then execute git grep additionalProp1
so you can track down where it's coming from
Cheers!
Any way to disable "Try it out" buttons in Swagger UI?
Hey @Mike!
Hmm, I've never tried it! It looks like it's a 2 step process:
1) You disable it in JavaScript (since Swagger is a Js library) https://api-platform.com/do...
2) How do you get custom JS onto the page? By overriding/extending the template that renders Swagger: https://api-platform.com/do...
Let me know if it works!
Cheers!
`api_platform:
resource: .
type: api_platform
prefix: /api
`
in api_plataform.yaml not work for me , I have to put in route.yaml of config
Hey Kasper Smithsonian
I believe you got confused by the file name. This api_platform.yaml
files should live inside config/routes
not inside config/packages
Cheers!
Hi!
Is there any way to redefine swagger operations description (like 'Retrieve foo collection') using an `@ApiResource` configuration?
I tried this
```
* @ApiResource(
* collectionOperations={
* "send": {
* "method": "POST",
* "path": "/email/send",
* "swaggerContext": {
* "summary": "test summary",
* "description": "sends a confirmation code",
* },
* "controller": SendEmailConfirmation::class,
* },
* },
```
but it doesn't work
Hey ddlzz
I haven't done what you are asking but I think you can do it by following this part of the documentation: https://api-platform.com/do...
I hope it helps. Cheers!
"Houston: no signs of life"
Start the conversation!
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.21.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
}
}
i have appearance problem. can you please tell me what the actual problem
when your page show like this
your browser
my browser show like this
my browser
i am something confused. please clear me.