GET a Collection of Programmers
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur API client will need a way to fetch a collection of programmers. Piece of cake. Start with public function listAction()
. For the URI, use /api/programmers
and add an @Method("GET")
:
// ... lines 1 - 67 | |
/** | |
* @Route("/api/programmers") | |
* @Method("GET") | |
*/ | |
public function listAction() | |
{ | |
// ... lines 74 - 85 | |
} | |
// ... lines 87 - 98 |
So, the URI you POST to when creating a resource - /api/programmers
- will be the same as the one you'll GET to fetch a collection of programmer resources. And yes, you can filter and paginate this list - all stuff we'll do later on.
Inside listAction()
, start like we always do: with a query. $programmers = $this->getDoctrine()->getRepository('AppBundle:Programmer')
and for now, we'll find everything with findAll()
:
// ... lines 1 - 71 | |
public function listAction() | |
{ | |
$programmers = $this->getDoctrine() | |
->getRepository('AppBundle:Programmer') | |
->findAll(); | |
// ... lines 77 - 85 | |
} | |
// ... lines 87 - 98 |
Next, we need to transform this array of Programmers into JSON. We'll want to re-use some logic from before, so let's create a new private function called serializeProgrammer()
and add a Programmer
argument. Inside, we can steal the manual logic that turns a Programmer into an array and just return it:
// ... lines 1 - 87 | |
private function serializeProgrammer(Programmer $programmer) | |
{ | |
return array( | |
'nickname' => $programmer->getNickname(), | |
'avatarNumber' => $programmer->getAvatarNumber(), | |
'powerLevel' => $programmer->getPowerLevel(), | |
'tagLine' => $programmer->getTagLine(), | |
); | |
} | |
// ... lines 97 - 98 |
That's a small improvement - at least we can re-use this stuff from inside this controller. In showAction()
, use $this->serializeProgrammer()
and pass it the variable.
// ... lines 1 - 46 | |
public function showAction($nickname) | |
{ | |
// ... lines 49 - 59 | |
$data = $this->serializeProgrammer($programmer); | |
$response = new Response(json_encode($data), 200); | |
// ... lines 63 - 65 | |
} | |
// ... lines 67 - 98 |
Back in listAction()
, we'll need to loop over the Programmers and serialize them one-by-one. So start by creating a $data
array with a programmers
key that's also set to an empty array. We're going to put the programmers there:
// ... lines 1 - 71 | |
public function listAction() | |
{ | |
$programmers = $this->getDoctrine() | |
->getRepository('AppBundle:Programmer') | |
->findAll(); | |
$data = array('programmers' => array()); | |
// ... lines 78 - 98 |
Avoid JSON Hijacking
Why? You can structure your JSON however you want, but by putting the collection inside a key, we have room for more root keys later like maybe count
or offset
for pagination. Second, your outer JSON should always be an object, not an array. So, curly braces instead of square brackets. If you have square brackets, you're vulnerable to something called JSON Hijacking. While not as bad as a car hijacking is something you want to avoid.
Turning the Programmers into JSON
Loop through $programmers
, and one-by-one, say $data['programmers'][]
and push on $this->serializeProgrammer()
. The end is the same as showAction()
, so just copy that:
// ... lines 1 - 71 | |
public function listAction() | |
{ | |
$programmers = $this->getDoctrine() | |
->getRepository('AppBundle:Programmer') | |
->findAll(); | |
$data = array('programmers' => array()); | |
foreach ($programmers as $programmer) { | |
$data['programmers'][] = $this->serializeProgrammer($programmer); | |
} | |
$response = new Response(json_encode($data), 200); | |
$response->headers->set('Content-Type', 'application/json'); | |
return $response; | |
} | |
// ... lines 87 - 98 |
That ought to do it. Update testing.php
to make another call out to /api/programmers
. Let's see what that looks like:
php testing.php
Woh, ok! We've got a database full of programmers. NERDS! Creating a new endpoint is getting easier - that trend will continue.
Returning JSON on Create
Remember how we're returning a super-reassuring text message from our POST endpoint? Well, you can do this, but usually, you'll return the resource you just created. That's easy now - so let's do it. Just, $data = $this->serializeProgrammer()
. Then json_encode()
that in the Response. And don't forget to set the Content-Type
header:
// ... lines 1 - 18 | |
public function newAction(Request $request) | |
{ | |
// ... lines 21 - 32 | |
$data = $this->serializeProgrammer($programmer); | |
$response = new Response(json_encode($data), 201); | |
// ... lines 35 - 39 | |
$response->headers->set('Content-Type', 'application/json'); | |
return $response; | |
} | |
// ... lines 44 - 100 |
To see if this is working, dump again right after the first request:
// ... lines 1 - 18 | |
// 1) Create a programmer resource | |
$response = $client->post('/api/programmers', [ | |
'body' => json_encode($data) | |
]); | |
echo $response; | |
echo "\n\n";die; | |
// ... lines 25 - 35 |
Hit it!
php testing.php
That's a helpful response: it has a Location
header and shows you the resource immediately. Because, why not?
JsonResponse
We're about to move onto testing - which makes this all so much fun. But first, we can shorten things. Each endpoint json_encode
s the data and sets the Content-Type
header. Use a class called JsonResponse
instead. And instead of passing it the JSON string, just pass it the data array. The other nice thing is that you don't need to set the Content-Type
header, it does that for you:
// ... lines 1 - 19 | |
public function newAction(Request $request) | |
{ | |
// ... lines 22 - 33 | |
$data = $this->serializeProgrammer($programmer); | |
$response = new JsonResponse($data, 201); | |
// ... lines 36 - 41 | |
return $response; | |
} | |
// ... lines 44 - 98 |
API consistency is king, and this is just one less spot for me to mess up and forget to set that header. Make sure you update the other spots, which is pretty much a copy-and-paste operation - be careful to keep the right status code.
Take out the extra die()
statement in testing.php
and let's try this whole thing out:
php testing.php
It's lovely. To make sure it doesn't break, we need to add tests! And that will be a whole lot more interesting than you think.