Frage

I'm currently using Symfony2 to create (and learn how to) a REST API. I'm using FOSRestBundle and i've created an "ApiControllerBase.php" with the following :

<?php
namespace Utopya\UtopyaBundle\Controller;


use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\View\View;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Class ApiControllerBase
 *
 * @package Utopya\UtopyaBundle\Controller
 */
abstract class ApiControllerBase extends FOSRestController
{
    /**
     * @param string $entityName
     * @param string $entityClass
     *
     * @return array
     * @throws NotFoundHttpException
     */
    protected function getObjects($entityName, $entityClass)
    {
        $dataRepository = $this->container->get("doctrine")->getRepository($entityClass);

        $entityName = $entityName."s";
        $data = $dataRepository->findAll();
        foreach ($data as $object) {
            if (!$object instanceof $entityClass) {
                throw new NotFoundHttpException("$entityName not found");
            }
        }

        return array($entityName => $data);
    }

    /**
     * @param string  $entityName
     * @param string  $entityClass
     * @param integer $id
     *
     * @return array
     * @throws NotFoundHttpException
     */
    protected function getObject($entityName, $entityClass, $id)
    {
        $dataRepository = $this->container->get("doctrine")->getRepository($entityClass);

        $data = $dataRepository->find($id);
        if (!$data instanceof $entityClass) {
            throw new NotFoundHttpException("$entityName not found");
        }

        return array($entityClass => $data);
    }

    /**
     * @param FormTypeInterface $objectForm
     * @param mixed             $object
     * @param string            $route
     *
     * @return Response
     */
    protected function processForm(FormTypeInterface $objectForm, $object, $route)
    {
        $statusCode = $object->getId() ? 204 : 201;

        $em = $this->getDoctrine()->getManager();

        $form = $this->createForm($objectForm, $object);
        $form->submit($this->container->get('request_stack')->getCurrentRequest());

        if ($form->isValid()) {
            $em->persist($object);
            $em->flush();

            $response = new Response();
            $response->setStatusCode($statusCode);

            // set the `Location` header only when creating new resources
            if (201 === $statusCode) {
                $response->headers->set('Location',
                    $this->generateUrl(
                        $route, array('id' => $object->getId(), '_format' => 'json'),
                        true // absolute
                    )
                );
            }

            return $response;
        }

        return View::create($form, 400);
    }
}

This handles getting one object with a given id, all objects and process a form. But to use this i have to create as many controller as needed. By example : GameController.

<?php

namespace Utopya\UtopyaBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Utopya\UtopyaBundle\Entity\Game;
use Utopya\UtopyaBundle\Form\GameType;

/**
 * Class GameController
 *
 * @package Utopya\UtopyaBundle\Controller
 */
class GameController extends ApiControllerBase
{
    private $entityName = "Game";
    private $entityClass = 'Utopya\UtopyaBundle\Entity\Game';

    /**
     * @Rest\View()
     */
    public function getGamesAction()
    {
        return $this->getObjects($this->entityName, $this->entityClass);
    }

    /**
     * @param int $id
     *
     * @return array
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     * @Rest\View()
     */
    public function getGameAction($id)
    {
        return $this->getObject($this->entityName, $this->entityClass, $id);
    }

    /**
     * @return mixed
     */
    public function postGameAction()
    {
        return $this->processForm(new GameType(), new Game(), "get_game");
    }
}

This sound not so bad to me but there's a main problem : if i want to create another controller (by example Server or User or Character), i'll have to do the same process and i don't want to since it'll be the same logic.

Another "maybe" problem could be my $entityName and $entityClass.

Any idea or could i make this better ?

Thank-you !

===== Edit 1 =====

I think i made up my mind. For those basics controllers. I would like to be able to "configure" instead of "repeat".

By example i could make a new node in config.yml with the following :

#config.yml
mynode:
    game:
        entity: 'Utopya\UtopyaBundle\Entity\Game'

This is a very basic example but is it possible to make this and transform it into my GameController with 3 methods routes (getGame, getGames, postGame) ?

I just want some leads if i can really achieve with this way or not, if yes with what components ? (Config, Router, etc.)

If no, what could i do? :)

Thanks !

War es hilfreich?

Lösung

I'd like to show my approach here.

I've created a base controller for the API, and I stick with the routing that's generated with FOSRest's rest routing type. Hence, the controller looks like this:

<?php

use FOS\RestBundle\Controller\Annotations\View;

use \Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\FormFactoryInterface,
    Symfony\Bridge\Doctrine\RegistryInterface,
    Symfony\Component\Security\Core\SecurityContextInterface,
    Doctrine\Common\Persistence\ObjectRepository;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

abstract class AbstractRestController 
{

    /**
     * @var \Symfony\Component\Form\FormFactoryInterface
     */
    protected $formFactory;

    /**
     * @var string
     */
    protected $formType;

    /**
     * @var string
     */
    protected $entityClass;

    /**
     * @var SecurityContextInterface
     */
    protected $securityContext;

    /**
     * @var RegistryInterface
     */
    protected $doctrine;

    /**
     * @var EventDispatcherInterface
     */
    protected $dispatcher;

    /**
     * @param FormFactoryInterface     $formFactory
     * @param RegistryInterface        $doctrine
     * @param SecurityContextInterface $securityContext
     * @param LoggerInterface          $logger
     */
    public function __construct(
        FormFactoryInterface $formFactory,
        RegistryInterface $doctrine,
        SecurityContextInterface $securityContext,
        EventDispatcherInterface $dispatcher
    )
    {
        $this->formFactory = $formFactory;
        $this->doctrine = $doctrine;
        $this->securityContext = $securityContext;
        $this->dispatcher = $dispatcher;
    }

    /**
     * @param string $formType
     */
    public function setFormType($formType)
    {
        $this->formType = $formType;
    }

    /**
     * @param string $entityClass
     */
    public function setEntityClass($entityClass)
    {
        $this->entityClass = $entityClass;
    }

    /**
     * @param null  $data
     * @param array $options
     *
     * @return \Symfony\Component\Form\FormInterface
     */
    public function createForm($data = null, $options = array())
    {
        return $this->formFactory->create(new $this->formType(), $data, $options);
    }

    /**
     * @return RegistryInterface
     */
    public function getDoctrine()
    {
        return $this->doctrine;
    }

    /**
     * @return \Doctrine\ORM\EntityRepository
     */
    public function getRepository()
    {
        return $this->doctrine->getRepository($this->entityClass);
    }

    /**
     * @param Request $request
     *
     * @View(serializerGroups={"list"}, serializerEnableMaxDepthChecks=true)
     */
    public function cgetAction(Request $request)
    {
        $this->logger->log('DEBUG', 'CGET ' . $this->entityClass);

        $offset = null;
        $limit  = null;

        if ($range = $request->headers->get('Range')) {
            list($offset, $limit) = explode(',', $range);
        }

        return $this->getRepository()->findBy(
            [],
            null,
            $limit,
            $offset
        );
    }

    /**
     * @param int $id
     *
     * @return object
     *
     * @View(serializerGroups={"show"}, serializerEnableMaxDepthChecks=true)
     */
    public function getAction($id)
    {
        $this->logger->log('DEBUG', 'GET ' . $this->entityClass);

        $object = $this->getRepository()->find($id);

        if (!$object) {
            throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
        }

        return $object;
    }

    /**
     * @param Request $request
     *
     * @return \Symfony\Component\Form\Form|\Symfony\Component\Form\FormInterface
     *
     * @View()
     */
    public function postAction(Request $request)
    {
        $object = new $this->entityClass();

        $form = $this->createForm($object);
        $form->submit($request);

        if ($form->isValid()) {
            $this->doctrine->getManager()->persist($object);
            $this->doctrine->getManager()->flush($object);

            return $object;
        }

        return $form;
    }

    /**
     * @param Request $request
     * @param int     $id
     *
     * @return \Symfony\Component\Form\FormInterface
     *
     * @View()
     */
    public function putAction(Request $request, $id)
    {
        $object = $this->getRepository()->find($id);

        if (!$object) {
            throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
        }

        $form = $this->createForm($object);
        $form->submit($request);

        if ($form->isValid()) {
            $this->doctrine->getManager()->persist($object);
            $this->doctrine->getManager()->flush($object);

            return $object;
        }

        return $form;
    }

    /**
     * @param Request $request
     * @param         $id
     *
     * @View()
     *
     * @return object|\Symfony\Component\Form\FormInterface
     */
    public function patchAction(Request $request, $id)
    {
        $this->logger->log('DEBUG', 'PATCH ' . $this->entityClass);

        $object = $this->getRepository()->find($id);

        if (!$object) {
            throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
        }

        $form = $this->createForm($object);
        $form->submit($request, false);

        if ($form->isValid()) {
            $this->doctrine->getManager()->persist($object);
            $this->doctrine->getManager()->flush($object);

            return $object;
        }

        return $form;
    }

    /**
     * @param int $id
     *
     * @return array
     *
     * @View()
     */
    public function deleteAction($id)
    {
        $this->logger->log('DEBUG', 'DELETE ' . $this->entityClass);

        $object = $this->getRepository()->find($id);

        if (!$object) {
            throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
        }

        $this->doctrine->getManager()->remove($object);
        $this->doctrine->getManager()->flush($object);

        return ['success' => true];
    }

} 

It has methods for most of RESTful actions. Next, when I want to implement a CRUD for an entity, that's what I do:

The abstract controller is registered in the DI as an abstract service:

my_api.abstract_controller:
  class: AbstractRestController
  abstract: true
  arguments:
    - @form.factory
    - @doctrine
    - @security.context
    - @logger
    - @event_dispatcher

Next, when I'm implementing a CRUD for a new entity, I create an empty class and a service definition for it:

class SettingController extends AbstractRestController implements ClassResourceInterface {} 

Note that the class implements ClassResourceInterface. This is necessary for the rest routing to work.

Here's the service declaration for this controller:

api.settings.controller.class: MyBundle\Controller\SettingController
api.settings.form.class: MyBundle\Form\SettingType

my_api.settings.controller:
  class: %api.settings.controller.class%
  parent: my_api.abstract_controller
  calls:
    - [  setEntityClass, [ MyBundle\Entity\Setting ] ]
    - [  setFormType,    [ %my_api.settings.form.class% ] ]

Then, I'm just including the controller in routing.yml as the FOSRestBundle doc states, and it's done.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top