Symfony:如何为与 Voryx REST Bundle 的一对多关系创建 POST 请求

Posted

技术标签:

【中文标题】Symfony:如何为与 Voryx REST Bundle 的一对多关系创建 POST 请求【英文标题】:Symfony: How to create a POST request for one-to-many relationship with Voryx REST Bundle 【发布时间】:2016-03-06 13:00:14 【问题描述】:

鉴于以下Category 实体...

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Category
 *
 * @ORM\Table(name="category", uniqueConstraints = @ORM\UniqueConstraint(name="unique_categoryName", columns="name"))
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
 */
class Category

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

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

    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Subcategory", mappedBy="category", cascade="remove")
     * @ORM\OrderBy("name" = "ASC")
     */
    private $subcategories;

    ...

...以及以下Subcategory 实体:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Subcategory
 *
 * @ORM\Table(name="subcategory")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\SubcategoryRepository")
 */
class Subcategory

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

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

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="subcategories")
     */
    private $category;

    ...

我正在使用https://github.com/voryx/restgeneratorbundle 来生成 REST 控制器...下面是我的 src/AppBundle/Form/SubcategoryType 的样子(src/AppBundle/Form/CategoryType 非常相似):

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class SucategoryType extends AbstractType

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('name')
            ->add('description')
            ->add('category', 'voryx_entity', array('class' => 'AppBundle\Entity\Category'))
        ;
    

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Sucategory'
        ));
    

    /**
     * @return string
     */
    public function getName()
    
        return 'appbundle_subcategory';
    

最后是我的src/AppBundle/Controller/CategoryRESTController:

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Category;
use AppBundle\Form\CategoryType;

use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use FOS\RestBundle\Controller\Annotations\View;
use FOS\RestBundle\Request\ParamFetcherInterface;
use FOS\RestBundle\Util\Codes;
use FOS\RestBundle\View\View as FOSView;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use Voryx\RESTGeneratorBundle\Controller\VoryxController;

/**
 * Category controller.
 * @RouteResource("Category")
 */
class CategoryRESTController extends VoryxController

    /**
     * Get a Category entity
     *
     * @View(serializerEnableMaxDepthChecks=true)
     *
     * @return Response
     *
     */
    public function getAction(Category $entity)
    
        return $entity;
    

    /**
     * Get all Category entities.
     *
     * @View(serializerEnableMaxDepthChecks=true)
     *
     * @param ParamFetcherInterface $paramFetcher
     *
     * @return Response
     *
     * @QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing notes.")
     * @QueryParam(name="limit", requirements="\d+", default="20", description="How many notes to return.")
     * @QueryParam(name="order_by", nullable=true, array=true, description="Order by fields. Must be an array ie. &order_by[name]=ASC&order_by[description]=DESC")
     * @QueryParam(name="filters", nullable=true, array=true, description="Filter by fields. Must be an array ie. &filters[id]=3")
     */
    public function cgetAction(ParamFetcherInterface $paramFetcher)
    
        try 
            $offset = $paramFetcher->get('offset');
            $limit = $paramFetcher->get('limit');
            $order_by = $paramFetcher->get('order_by');
            $filters = !is_null($paramFetcher->get('filters')) ? $paramFetcher->get('filters') : array();

            $em = $this->getDoctrine()->getManager();
            $entities = $em->getRepository('AppBundle:Category')->findBy($filters, $order_by, $limit, $offset);
            if ($entities) 
                return $entities;
            

            return FOSView::create('Not Found', Codes::HTTP_NO_CONTENT);
         catch (\Exception $e) 
            return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR);
        
    

    /**
     * Create a Category entity.
     *
     * @View(statusCode=201, serializerEnableMaxDepthChecks=true)
     *
     * @param Request $request
     *
     * @return Response
     *
     */
    public function postAction(Request $request)
    
        $entity = new Category();
        $form = $this->createForm(new CategoryType(), $entity, array("method" => $request->getMethod()));
        $this->removeExtraFields($request, $form);
        $form->handleRequest($request);

        if ($form->isValid()) 
            $em = $this->getDoctrine()->getManager();
            $em->persist($entity);
            $em->flush();

            return $entity;
        

        return FOSView::create(array('errors' => $form->getErrors()), Codes::HTTP_INTERNAL_SERVER_ERROR);
    

    /**
     * Update a Category entity.
     *
     * @View(serializerEnableMaxDepthChecks=true)
     *
     * @param Request $request
     * @param $entity
     *
     * @return Response
     */
    public function putAction(Request $request, Category $entity)
    
        try 
            $em = $this->getDoctrine()->getManager();
            $request->setMethod('PATCH'); //Treat all PUTs as PATCH
            $form = $this->createForm(new CategoryType(), $entity, array("method" => $request->getMethod()));
            $this->removeExtraFields($request, $form);
            $form->handleRequest($request);
            if ($form->isValid()) 
                $em->flush();

                return $entity;
            

            return FOSView::create(array('errors' => $form->getErrors()), Codes::HTTP_INTERNAL_SERVER_ERROR);
         catch (\Exception $e) 
            return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR);
        
    

    /**
     * Partial Update to a Category entity.
     *
     * @View(serializerEnableMaxDepthChecks=true)
     *
     * @param Request $request
     * @param $entity
     *
     * @return Response
     */
    public function patchAction(Request $request, Category $entity)
    
        return $this->putAction($request, $entity);
    

    /**
     * Delete a Category entity.
     *
     * @View(statusCode=204)
     *
     * @param Request $request
     * @param $entity
     *
     * @return Response
     */
    public function deleteAction(Request $request, Category $entity)
    
        try 
            $em = $this->getDoctrine()->getManager();
            $em->remove($entity);
            $em->flush();

            return null;
         catch (\Exception $e) 
            return FOSView::create($e->getMessage(), Codes::HTTP_INTERNAL_SERVER_ERROR);
        
    

像这样发布添加单个类别的请求非常有效:

curl -i -H "Content-Type: application/json" -X POST -d '"name" : "Sport", "description" : "Sport category"' http://localhost:8000/api/categories

...结果如下:

+----+-------+---------------------------+
| id | name  | description               |
+----+-------+---------------------------+
|  1 | Sport | Sport category            |
+----+-------+---------------------------+

但是我如何发布请求以向上面创建的类别添加子类别?我们的想法是添加这样的子类别:

curl -i -H "Content-Type: application/json" -X POST -d '"name" : "Football", "description" : "Groups ranks about football"' http://localhost:8000/api/categories/1/subcategories

【问题讨论】:

我的回答解决了你的问题吗? 【参考方案1】:

是的,为类别子类别资源创建 POST 请求是完全可以的。

我还认为(基于您的实体的外观)您不需要SubcategoryType。因为它将与CategoryType 相同。

您可以查看 Gedmo Tree extension: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/tree.md 并将其全部保存在一个实体上,而不是两个实体。

【讨论】:

以上是关于Symfony:如何为与 Voryx REST Bundle 的一对多关系创建 POST 请求的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 2. 如何为实体的嵌入表单设置默认值?

如何为 Symfony2 站点正确设置 Varnish?

Symfony2:如何为整个应用程序强制使用 HTTPS?

Django Rest API,如何为 2 个模型条目创建 post api,并具有与模型关联的外键

如何为 Symfony3 上的 AJAX 调用添加 CSRF 保护?

如何为我的 symfony2 网站实现权限角色/组系统