Symfony 2 - 保持代码DRY - FOSRestBundle

时间:2014-03-10 22:48:01

标签: php symfony dry fosrestbundle

我目前正在使用Symfony2来创建(并学习如何)REST API。我正在使用FOSRestBundle,我创建了一个带有以下内容的“ApiControllerBase.php”:

<?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);
    }
}

它处理一个具有给定id,所有对象和处理表单的对象。但要使用它,我必须根据需要创建尽可能多的控制器。例如: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");
    }
}

这个声音对我来说并不坏,但是有一个主要问题:如果我想创建另一个控制器(通过示例服务器或用户或角色),我将不得不做同样的过程,我不想它将是相同的逻辑。

另一个“可能”问题可能是我的$ entityName和$ entityClass。

任何想法或者我可以做得更好吗?

谢谢!

=====编辑1 =====

我想我决定了。对于那些基础控制器。我希望能够“配置”而不是“重复”。

通过示例,我可以使用以下命令在config.yml中创建一个新节点:

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

这是一个非常基本的例子但可以通过3种方法路径(getGame,getGames,postGame)将其转换为我的GameController吗?

我只是想要一些线索,如果我能用这种方式真正实现,如果是的话用什么组件? (配置,路由器等)

如果不是,我该怎么办? :)

谢谢!

1 个答案:

答案 0 :(得分:1)

我想在这里展示我的方法。

我已经为API创建了一个基本控制器,并且我坚持使用FOSRest's rest routing type生成的路由。因此,控制器看起来像这样:

<?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];
    }

} 

它有大多数RESTful操作的方法。接下来,当我想为实体实现CRUD时,这就是我的工作:

抽象控制器在DI中注册为抽象服务:

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

接下来,当我为新实体实现CRUD时,我为它创建了一个空类和服务定义:

class SettingController extends AbstractRestController implements ClassResourceInterface {} 

请注意,该类实现了ClassResourceInterface。这是rest路由工作所必需的。

这是该控制器的服务声明:

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% ] ]

然后,我只是将控制器包含在routing.yml中作为FOSRestBundle文档说明,并且已完成。