Symfony 3.x FOSRestBundleとJMSSerializerBundleでREST APIを実装する


FOSRestBundleと、シリアライザーとしてJMSSerializerBundleを利用し、SymfonyでREST APIを実装する手順についてまとめました。

FOSRestBundleは、非常に多機能で柔軟な実装が可能ですが、ここではレスポンスフォーマットはjsonのみに対応したシンプルなREST APIを実装する手順について紹介してみます。

バンドルのインストールと読み込み

Step 1: Setting up the bundleに従って以下の手順でインストールします

friendsofsymfony/rest-bundlejms/serializer-bundleをComposerでインストール

$ composer require friendsofsymfony/rest-bundle
$ composer require jms/serializer-bundle

app/AppKernel.phpにバンドルの読み込みを追加します

// app/AppKernel.php
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new FOS\RestBundle\FOSRestBundle(),
            new JMS\SerializerBundle\JMSSerializerBundle(),
        );

        // ...
    }
}

View Response Listenerを有効化

コントローラのアクションで、直接Entityを返すには、fos_rest.view.view_response_listenerforceを指定します

# app/config/config/yml
fos_rest:
    view:
        view_response_listener: force

コントローラを作成

モデルPostの一覧を返すコントローラとアクションを作成してルーティングに設定します。

ルーティング設定typerestを指定すると、コントローラのアクション名による自動ルーティングが有効になります。
例えば、getPostsActionという名前のアクションメソッドを定義すると/posts.jsonというルーティングを生成してくれます。

// src/AppBundle/Controller/PostController.php
<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;

class PostController extends FOSRestController
{
    public function getPostsAction()
    {
        return [
            ['name' => 'Geeting Start Symfony 3.'],
            ['name' => 'Implement REST API with FOSRestBundle.'],
        ];
    }
}

Define resource actions | Routing ( FOS Rest Bundle documentation)で、全リソースの記述方法が確認できます。

とりあえず、ここでは、配列をそのままアクションで返しています。
後で、エンティティに置き換えていきます。

ルーティングの設定

Single RESTful controller routes
に従って、作成したコントローラを指定したルーティングを追加します

# app/config/routing.yml
app_project:
    resource: "@AppBundle/Controller/PostController.php"
    type: rest

debug:routeコマンドでルーティングを確認すると、/posts.{format}が追加されています。

$ bin/console debug:route
 -------------------------- -------- -------- ------ ----------------------------------- 
  Name                       Method   Scheme   Host   Path                               
 -------------------------- -------- -------- ------ ----------------------------------- 
  _wdt                       ANY      ANY      ANY    /_wdt/{token}                      
  ...
  homepage                   ANY      ANY      ANY    /                                  
  get_posts                  GET      ANY      ANY    /posts.{_format}                   
 -------------------------- -------- -------- ------ ----------------------------------- 

動作確認

ここまで実装した内容を確認してみます

サーバーを起動して

$ bin/console server:run

以下のように、JSONが返ってきたことを確認できます

$ curl http://localhost:8000/posts.json
[{"name":"Geeting Start Symfony 3."},{"name":"Implement REST API with FOSRestBundle."}]

データベースのレコードを返す

次に以下のように、namebodyフィールドを持つPostのエンティティを追加して、データベースのレコードをJSONシリアライズして返すように修正してみます。

Doctrineまわりの説明はここでは省略します。

// src/AppBundle/Entity/Post.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Post
 *
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
 */
class Post
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="body", type="text", nullable=true)
     */
    private $body;

    //...
}

2行追加しておきます

> insert into post(name, body) values 
    ('Geeting Start Symfony 3.', 'Contents.'),
    ('Implement REST API with FOSRestBundle.', 'Contents.');

コントローラをRepositoryからPostの一覧を取得するように書き換えます。

// src/AppBundle/Controller/PostController.php

class PostController extends FOSRestController
{
    public function getPostsAction()
    {
        return $this->getDoctrine()->getManager()->getRepository('AppBundle:Post')->findAll();
    }
}

curlで確認するとデータベースから取得したレコードが返されていることを確認できます。

$ curl http://localhost:8000/posts.json
[{"id":1,"name":"Geeting Start Symfony 3.","body":"Contents."},{"id":2,"name":"Implement REST API with FOSRestBundle.","body":"Contents."}]

JMSSerializerBundleは、特に何も指定が無ければすべてのフィールドをシリアライズします。
Exclusion Strategies – serializer Documentation (master)

シリアライズするフィールドをアノテーションで指定

特定のフィールドのみを返したりする場合には、アノテーションで制御することができます。
リファレンス: Annotations – serializer Documentation (master)

例えば、idnameのみを返す場合、アノテーションを以下のように追加します。

// src/AppBundle/Entity/Post.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * Post
 *
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
 * @JMS\ExclusionPolicy("all")
 */
class Post
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @JMS\Expose
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     * @JMS\Expose
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="body", type="text", nullable=true)
     */
    private $body;

curlで確認するとbodyフィールドが除外されていることが確認できます。

$ curl http://localhost:8000/posts.json
[{"id":4,"name":"Geeting Start Symfony 3."},{"id":5,"name":"Implement REST API with FOSRestBundle."}]

以上、ここまでfriendsofsymfony/rest-bundlejms/serializer-bundleによるREST APIの実装について、簡単にみてきました。

今回は、リソースもGETのみでしたので、POSTやPUTなど更新系についても別途まとめておきたいところです。

補足

FOSRestBundleのシリアライザーの選択順序について

FOSRestBundleのシリアライザー設定は、Step 1: Setting up the bundleC) Enable a Serializerに依ると、以下の順序で解決されます。

  1. fos_rest.services.serializerの設定
  2. JSMSerializerBundle
  3. framework.serializerに設定がある場合、Symfony Serializer

この記事の設定では、JSMSerializerBundleがインストール済みで、fos_rest.services.serializerを設定していないので、JSMSerializerBundleが選択されます。

参考サイト

,