DRAFT / WORK IN PROGRESS

Symfony Controller not extending FOSRest Bundle

Symfony Controller Best Practice by not extending a Base Controller but injecting dependencies

Most examples of a Symfony Controller extend FOSRestController. This is not ideal, it is better to have Controllers as a Service, just like any other Service. This is much easier to Unit Test with tools like PHPUnit and PHPSpec and then forces the Controllers to do less and have less dependencies - we all like Thin Controllers.

Below is a standard example of a Controller.

<?php

namespace Your\Namespace\Controller

use FOS\RestBundle\Controller\FOSRestController;

class UsersController extends FOSRestController
{
    public function getUsersAction()
    {
        $data = ...; // get data, in this case list of users.

        return $this->handleView(
            $this->view($data, 200)
        );
    }
}

To run it as a Service is quite easy and Decouples it from Symfony FOSRest Bundle, which will look like below.

<?php

namespace Your\Namespace\Controller

class UsersController
{
    /** @var Controller */
    private $controller;

    /**
     * @param Controller $controller
     */
    public function __construct(ControllerInterface $controller)
    {
        $this->controller = $controller;
    }

    public function getUsersAction()
    {
        $data = ...; // get data, in this case list of users.

        return $this->controller->handleView(
            $this->controller->view($data, 200)
        );
    }
}

I introduced a new class here called Controller, this will proxy through to the Symfony FOSRestController - it too will have Unit Tests.

<?php

namespace Your\Namespace\Controller

use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;

/**
 * Class Controller
 */
class Controller implements ControllerInterface
{

    /** @var ViewHandler */
    private $viewHandler;

    /** @var View */
    private $view;

    /**
     * @param ViewHandler $viewHandler
     * @param View $view
     */
    public function __construct(ViewHandler $viewHandler, View $view)
    {
        $this->viewHandler = $viewHandler;
        $this->view        = $view;
    }

    /**
     * @param View $view
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handle(View $view)
    {
        return $this->viewHandler
            ->handle($view);
    }

    /**
     * @param  array $data
     * @return View
     */
    public function setData(array $data)
    {
        return $this->view
            ->setData($data);
    }
}

The services.yml will look something like this:

services:
    your_app.fos_rest.view.view:
        class: FOS\RestBundle\View\View
        factory: [FOS\RestBundle\View\View, create]

    your_app.controller:
        class: Your\Namespace\Controller\Controller
        arguments: [@fos_rest.view_handler, @your_app.fos_rest.view.view]

    your_app.controller.users:
        class: Your\Namespace\Controller\UsersController
        arguments: [@your_app.controller]

The routing.yml will be like so:

your_app_users:
    type:     rest
    resource: your_app.controller.users

I hope that helps. Feedback welcome.

Update: Controller Interface added.

Share this on →