WEBVTT

NOTE Created by CaptionSync from Automatic Sync Technologies www.automaticsync.com

00:00:01.086 --> 00:00:02.636 align:middle
With all the new code organization,

00:00:02.746 --> 00:00:07.586 align:middle
let's celebrate by creating another API
endpoint to fetch a single starship.

00:00:08.256 --> 00:00:11.686 align:middle
Start like usual: create a public
function called, how about, get().

00:00:11.686 --> 00:00:14.456 align:middle
I'll include the optional Response return type.

00:00:15.216 --> 00:00:20.546 align:middle
Above this add the #[Route]
with a URL of /api/starships/...

00:00:20.876 --> 00:00:24.696 align:middle
hmm. This time, the last part
of the URL needs to be dynamic:

00:00:24.876 --> 00:00:31.126 align:middle
it should match /api/starships/5
or /api/starships/25.

00:00:31.786 --> 00:00:32.576 align:middle
How can we do that?

00:00:33.016 --> 00:00:35.286 align:middle
How can we make a route match a wildcard?

00:00:35.856 --> 00:00:42.466 align:middle
The answer is by adding {, a name, the }.
The name inside this could be anything.

00:00:42.846 --> 00:00:48.016 align:middle
No matter what, this route will
now match /api/starships/*.

00:00:48.736 --> 00:00:54.796 align:middle
But whatever you name this, you're now allowed
to have an argument with a matching name: $id.

00:00:55.526 --> 00:00:57.476 align:middle
Below, dump this to make sure it's working.

00:00:58.686 --> 00:01:02.756 align:middle
Ok! Zoom over to /api/starships/2 and...

00:01:03.256 --> 00:01:04.656 align:middle
it is working!

00:01:05.146 --> 00:01:07.596 align:middle
In our app, the ID will be an integer.

00:01:08.056 --> 00:01:11.296 align:middle
If I try something that is not
an integers - like /wharf -

00:01:11.556 --> 00:01:13.796 align:middle
the route still matches and
calls our controller.

00:01:14.426 --> 00:01:16.416 align:middle
And, that's almost always okay.

00:01:16.796 --> 00:01:22.636 align:middle
In a real app, if we queried the database with
WHERE ID = 'wharf', it wouldn't cause an error:

00:01:22.836 --> 00:01:24.776 align:middle
it just wouldn't find a matching ship!

00:01:25.346 --> 00:01:29.136 align:middle
And then we could trigger a 404 page,
which I'll show you how to do soon.

00:01:29.786 --> 00:01:32.596 align:middle
But sometimes, we may want
to restrict these values.

00:01:32.966 --> 00:01:37.266 align:middle
We may want to say: Only match this
route if the wildcard is an integer.

00:01:37.996 --> 00:01:42.296 align:middle
To do that, inside the curly
brace, after the name, add a &lt;,

00:01:42.296 --> 00:01:46.756 align:middle
&gt; and inside, a regular expression \d+.

00:01:47.336 --> 00:01:49.846 align:middle
This means: match a digit of any length.

00:01:50.396 --> 00:01:55.516 align:middle
With this setup, if we refresh
the wharf URL, we get a 404 error.

00:01:55.516 --> 00:02:01.026 align:middle
Our route simply wasn't matched - no route
matched - so our controller was never called.

00:02:01.596 --> 00:02:04.616 align:middle
But if we go back to /2, that still works.

00:02:05.196 --> 00:02:08.946 align:middle
And as an added benefit, now
that this only matches digits,

00:02:09.146 --> 00:02:11.466 align:middle
we can add an int type to the argument.

00:02:12.116 --> 00:02:15.416 align:middle
Now, instead of the string
2, we get the integer 2.

00:02:15.976 --> 00:02:19.826 align:middle
These details aren't super important, but
I want you to know what options you have.

00:02:20.546 --> 00:02:22.726 align:middle
One thing that is common with APIs is

00:02:22.726 --> 00:02:27.426 align:middle
to make routes only match a certain
HTTP method, like GET or POST.

00:02:28.096 --> 00:02:33.266 align:middle
For example, if you want to fetch all the
starships, users should make a GET request...

00:02:33.556 --> 00:02:35.606 align:middle
same if you want to fetch a single ship.

00:02:36.426 --> 00:02:40.066 align:middle
If we kept building our API and
created an endpoint that could be used

00:02:40.066 --> 00:02:45.126 align:middle
to create a new Starship, the standard way
to do that would be to use the same URL:

00:02:45.306 --> 00:02:49.106 align:middle
/api/starships but with a POST request.

00:02:49.836 --> 00:02:51.366 align:middle
Right now, this wouldn't work.

00:02:51.786 --> 00:02:56.706 align:middle
Every time the user requested
/api/starships - no matter if they use a GET

00:02:56.706 --> 00:03:00.176 align:middle
or POST request, it would
match this first route.

00:03:00.926 --> 00:03:03.256 align:middle
For that reason, it's common in an API

00:03:03.416 --> 00:03:07.596 align:middle
to add a methods option set
to an array, with GET or POST.

00:03:08.376 --> 00:03:10.876 align:middle
I'll do the same thing down
here: methods: ['GET'].

00:03:12.006 --> 00:03:16.286 align:middle
I can't easily test this in a
browser, but if we made a POST request

00:03:16.286 --> 00:03:20.546 align:middle
to /api/starships/2, it would
not match our route.

00:03:21.156 --> 00:03:22.836 align:middle
But we can see the change in our terminal.

00:03:23.326 --> 00:03:26.856 align:middle
Run: php bin/console debug:router Perfect!

00:03:27.646 --> 00:03:29.496 align:middle
Most routes match any method...

00:03:29.646 --> 00:03:34.296 align:middle
but our two API routes only match if
a GET request is made to that URL.

00:03:34.916 --> 00:03:37.356 align:middle
Ok, I have one more routing trick to show you...

00:03:37.476 --> 00:03:39.056 align:middle
and this is a fun one!

00:03:39.756 --> 00:03:44.236 align:middle
Every route in this controller starts
with the same URL: /api/starships.

00:03:44.766 --> 00:03:47.626 align:middle
Having the full URL in each route is fine.

00:03:48.106 --> 00:03:52.026 align:middle
But if we want, we can automatically
prefix each route's URL.

00:03:52.536 --> 00:03:57.526 align:middle
Above the class, add a #[Route]
attribute with /api/starships.

00:03:58.216 --> 00:04:02.236 align:middle
Unlike when we put this above a
method, this does not create a route.

00:04:02.596 --> 00:04:07.266 align:middle
It just says: every route in this
class should be prefixed with this URL.

00:04:07.826 --> 00:04:10.516 align:middle
So for the first route, remove
the path entirely.

00:04:10.866 --> 00:04:13.536 align:middle
And for the second, we only
need the wildcard part.

00:04:14.116 --> 00:04:15.696 align:middle
Try debug:router again...

00:04:15.826 --> 00:04:19.726 align:middle
and watch these URLs: They don't change!

00:04:20.396 --> 00:04:22.006 align:middle
Okay. Let's finish our endpoint.

00:04:22.226 --> 00:04:24.796 align:middle
We need to find the one ship
that matches this ID.

00:04:25.246 --> 00:04:30.686 align:middle
Normally we'd query the database:
select * from ship where id = this ID.

00:04:31.136 --> 00:04:34.076 align:middle
Our ships are hardcoded right
now, but we can still do something

00:04:34.076 --> 00:04:38.586 align:middle
that will look pretty much exactly like
what it will, once we do have a database.

00:04:38.946 --> 00:04:44.786 align:middle
We already have a service - StarshipRepository
- whose whole job is to fetch starship data.

00:04:45.416 --> 00:04:50.526 align:middle
Let's give it a new superpower: the ability
to fetch a single Starship for an id.

00:04:50.996 --> 00:04:56.406 align:middle
Add public function find() with an int $id
argument that will return a nullable Starship.

00:04:57.056 --> 00:05:00.426 align:middle
So, a Starship if we find
one for this id, else null.

00:05:00.966 --> 00:05:06.226 align:middle
Right now, the easiest way write this logic is
to loop over $this-&gt;findAll() as $starship...

00:05:07.016 --> 00:05:11.566 align:middle
then if $starship-&gt;getId()
=== $id, return $starship.

00:05:12.286 --> 00:05:14.206 align:middle
I'll change my uf to if.

00:05:14.646 --> 00:05:15.256 align:middle
Much better.

00:05:15.846 --> 00:05:18.756 align:middle
And if we didn't find anything,
at the bottom, return null.

00:05:19.626 --> 00:05:22.566 align:middle
Thanks to this, our controller is so simple.

00:05:23.006 --> 00:05:25.816 align:middle
First, autowire the repository
by adding an argument:

00:05:26.126 --> 00:05:29.226 align:middle
StarshipRepository and just call it $repository.

00:05:30.046 --> 00:05:33.126 align:middle
By the way, the order of arguments
in a controller doesn't matter.

00:05:33.786 --> 00:05:37.356 align:middle
Then $starship = $repository-&gt;find($id).

00:05:38.256 --> 00:05:41.676 align:middle
Finish at the bottom with
return $this-&gt;json($starship).

00:05:42.346 --> 00:05:43.256 align:middle
I think we're ready!

00:05:43.686 --> 00:05:45.946 align:middle
Refresh. It's perfect!

00:05:46.536 --> 00:05:51.426 align:middle
But try an id that does not exist
in our fake database - like /200.

00:05:52.286 --> 00:05:53.716 align:middle
The word null is...

00:05:54.016 --> 00:05:55.066 align:middle
not what we want.

00:05:55.596 --> 00:05:59.546 align:middle
In this situation, we should return
a response with a 404 status code.

00:06:00.226 --> 00:06:04.196 align:middle
To do that, we're going to follow a
common pattern: query for an object,

00:06:04.366 --> 00:06:06.486 align:middle
then check if it returned anything.

00:06:07.096 --> 00:06:09.666 align:middle
If it did not return something, trigger a 404.

00:06:10.376 --> 00:06:14.376 align:middle
Do that with throw
$this-&gt;createNotFoundException().

00:06:14.806 --> 00:06:16.136 align:middle
I'll pass this a message.

00:06:17.486 --> 00:06:22.596 align:middle
Notice the throw keyword: we're throwing
a special exception that triggers a 404.

00:06:23.056 --> 00:06:27.966 align:middle
That's nice because, as soon as it hits
this line, nothing after will be executed.

00:06:28.786 --> 00:06:29.306 align:middle
Try it out!

00:06:30.246 --> 00:06:32.536 align:middle
Yes! A 404 response!

00:06:32.956 --> 00:06:37.326 align:middle
The message - "Starship not found" - is
only shown to developers in dev mode.

00:06:37.916 --> 00:06:42.026 align:middle
In production, a totally different
page - or JSON - would be returned.

00:06:42.406 --> 00:06:45.506 align:middle
You can check the docs for
details on production error pages.

00:06:46.286 --> 00:06:49.086 align:middle
Next: let's build the HTML version of this page,

00:06:49.336 --> 00:06:52.106 align:middle
a page that shows details
about a single starship.

00:06:52.636 --> 00:06:55.756 align:middle
Then we'll learn how to link
between pages using the route name.

