Serializer & API Endpoint

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

In addition to our login form authentication, I also want to allow users to log in by sending an API token. But, before we get there, let's make a proper API endpoint first.

Creating the API Endpoint

I'll close a few files and open AccountController. To keep things simple, we'll create an API endpoint right here. Add a public function at the bottom called accountApi():

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 27
public function accountApi()
{
... lines 30 - 32
}
}

This new endpoint will return the JSON representation of whoever is logged in. Above, add @Route("/api/account") with name="api_account":

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 24
/**
* @Route("/api/account", name="api_account")
*/
public function accountApi()
{
... lines 30 - 32
}
}

The code here is simple - excitingly simple! $user = $this->getUser() to find who's logged in:

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 24
/**
* @Route("/api/account", name="api_account")
*/
public function accountApi()
{
$user = $this->getUser();
... lines 31 - 32
}
}

We can safely do this thanks to the annotation on the class: every method requires authentication. Then, to transform the User object into JSON - this is pretty cool - return $this->json() and pass $user:

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 24
/**
* @Route("/api/account", name="api_account")
*/
public function accountApi()
{
$user = $this->getUser();
return $this->json($user);
}
}

Let's try it! In your browser, head over to /api/account. And! Oh! That's not what I expected! It's JSON... but it's totally empty!

Installing the Serializer

Why? Hold Command or Control and click into the json() method. This method does two different things, depending on your setup. First, it checks to see if Symfony's serializer component is installed. Right now, it is not. So, it falls back to passing the User object to the JsonResponse class. I won't open that class, but all it does internally is called json_encode() on that data we pass in: the User object in this case.

Do you know what happens when you call json_encode() on an object in PHP? It only... sorta works: it encodes only the public properties on that class. And because we have no public properties, we get back nothing!

This is actually the entire point of Symfony's serializer component! It's a kick butt way to turn objects into JSON, or any other format. I don't want to talk too much about the serializer right now: we're trying to learn security! But, I do want to use it. Find your terminal and run:

composer require serializer

This installs the serializer pack, which downloads the serializer and a few other things. As soon as this finishes, the json() method will start using the new serializer service. Try it - refresh! Hey! It works! That's awesome!

Serialization Groups

Except... well... we probably don't want to include all of these properties - especially the encoded password. I know, I said we weren't going to talk about the serializer, and yet, I do want to fix this one thing!

Open your User class. To control which fields are serialized, above each property, you can use an annotation to organize into "groups". I won't expose the id, but let's expose email by putting it into a group: @Groups("main"):

... lines 1 - 6
use Symfony\Component\Serializer\Annotation\Groups;
... lines 8 - 11
class User implements UserInterface
{
... lines 14 - 20
/**
... line 22
* @Groups("main")
*/
private $email;
... lines 26 - 159
}

When I auto-completed that annotation, the PHP Annotations plugin added the use statement I need to the top of the file:

<?php
... lines 2 - 6
use Symfony\Component\Serializer\Annotation\Groups;
... lines 8 - 161

Oh, and I totally invented the "main" part - that's the group name, and you'll see how I use it in a minute. Copy the annotation and also add firstName and twitterUsername to that same group:

... lines 1 - 11
class User implements UserInterface
{
... lines 14 - 20
/**
... line 22
* @Groups("main")
*/
private $email;
... lines 26 - 31
/**
... line 33
* @Groups("main")
*/
private $firstName;
... lines 37 - 42
/**
... line 44
* @Groups("main")
*/
private $twitterUsername;
... lines 48 - 159
}

To complete this, in AccountController, we just need to tell the json() method to only serialize properties that are in the group called "main". To do that, pass the normal 200 status code as the second argument, we don't need any custom headers, but we do want to pass one item to "context". Set groups => an array with the string main:

... lines 1 - 11
class AccountController extends BaseController
{
... lines 14 - 24
/**
* @Route("/api/account", name="api_account")
*/
public function accountApi()
{
... lines 30 - 31
return $this->json($user, 200, [], [
'groups' => ['main'],
]);
}
}

You can include just one group name here like this, or tell the serializer to serialize the properties from multiple groups.

Let's try it! Refresh! Yes! Just these three fields.

Ok, we are now ready to take on a big, cool topic: API token authentication.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.0
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.4
        "symfony/console": "^4.0", // v4.1.4
        "symfony/flex": "^1.0", // v1.9.10
        "symfony/framework-bundle": "^4.0", // v4.1.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.4
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.4
        "symfony/web-server-bundle": "^4.0", // v4.1.4
        "symfony/yaml": "^4.0", // v4.1.4
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.4
        "symfony/dotenv": "^4.0", // v4.1.4
        "symfony/maker-bundle": "^1.0", // v1.7.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.4
    }
}