使用PUT而不是POST提交表单

时间:2014-04-22 11:33:07

标签: php symfony

我有一个控制器,用于处理来自AJAX请求的表单提交。我不想重复自己,所以我把表单处理代码放在一个方法中:

// Should process POST request
public function create(Request $request)
{
    return $this->processEdit($request);
}

// Should process PUT request
public function update($id, Request $request)
{
    $entity = $this->findEntity($id); // custom method

    if (!$entity)
        return $this->myCustomErrorResponse();

    return $this->processEdit($request, $entity);
}

private function processEdit(Request $request, Entity $entity = null)
{
    $form = $this->createForm('my_entity', $entity);

    $form->handleRequest($request);

    if ($form->isValid()) {
        // Do something
    } else {
        // Handle invalid form
    }

    return $response;
}

我有以下两条路线:

ajax_create:
    pattern: /
    defaults: { _controller: 'MyBundle:Ajax:create' }
    methods: [ POST ]

ajax_update:
    pattern: /{id}
    defaults: { _controller: 'MyBundle:Ajax:update' }
    methods: [ PUT ]
    requirements:
        id: \d+

但是,当我通过AJAX提交表单时,如果没有任何表单错误消息,它将不接受PUT请求并且返回表单无效。如果我改变控制器代码abit,

$form = $this->createForm('my_entity', $entity, array(
    'method' => 'PUT',
));

...它会处理PUT个请求但不处理POST请求。

我想知道Symfony2的哪一部分HTTP方法检查表单,所以我试图在源代码中寻找答案,但我找不到线索。你们中的任何人都可以分享你的知识吗?

另一个问题是,有没有办法绕过HTTP方法检查?我目前正在将$method传递给上面显示的方法。

非常感谢。


更新

为了使我的问题更清楚,我的Symfony2应用程序将请求(POST和PUT)路由到正确的控制器方法。

我提到了上面改变的代码,这里是:

// Should process POST request
public function create(Request $request)
{
    return $this->processEdit($request);
}

// Should process PUT request
public function update($id, Request $request)
{
    $entity = $this->findEntity($id); // custom method

    if (!$entity)
        return $this->myCustomErrorResponse();

    return $this->processEdit($request, 'PUT', $entity);
}

private function processEdit(Request $request, $method = 'POST', Entity $entity = null)
{
    $form = $this->createForm('my_entity', $entity, array(
        'method' => $method,
    ));

    $form->handleRequest($request);

    if ($form->isValid()) {
        // Do something
    } else {
        // Handle invalid form
    }

    return $response;
}

2 个答案:

答案 0 :(得分:3)

[编辑2014-05-23] 我完全修改了我的第一个答案,因为它是一个"肮脏的黑客"。

我有完全相同的问题(和几乎相同的代码)。我在这里已经阅读了答案,并且在我自己的代码中发现了一个主要问题,我忘了修改/web/app.php文件以默认启用HttpMethodParameterOverride参数。 (It's a change introduced in Symfony2.2

现在一切都按预期使用handleRequest()函数:

  • 创建操作使用 POST 查询。
  • 编辑操作使用 PUT 查询。
  • 删除操作使用 DELETE 查询。

我不需要按照接受的答案中的建议修改RequestHandler配置。

现在代码如下:

/**
 * Fruits CRUD service controller.
 *
 * @Route("/services/fruits")
 */
class FruitsController extends Controller
{
    // ...

/**
 * Create a fruit.
 *
 * @param Request $request
 *
 * @Rest\Post("", name="backend_fruits_create")
 *
 * @return View|array
 */
public function createAction(Request $request)
{
    return $this->processForm($request, new Fruit());
}

/**
 * Edit a fruit.
 *
 * @param Request $request
 * @param Fruit   $fruit
 *
 * @Rest\Put("/{id}", name="backend_fruits_edit", requirements={"id" = "\d+"})
 * @throws HttpException
 *
 * ## DEV FORM ##
 * @Rest\Get("/edit/{id}", name="backend_fruits_edit_dev", requirements={"id" = "\d+"})
 * @Rest\View
 * ## DEV FORM ##
 *
 * @return View|array
 */
public function editAction(Request $request, Fruit $fruit)
{
    return $this->processForm($request, $fruit);
}

/**
 * Delete a fruit.
 *
 * @param Fruit $fruit
 *
 * @Rest\Delete("/{id}", name="backend_fruits_delete")
 * @throws HttpException
 *
 * @return View
 */
public function deleteAction(Fruit $fruit)
{
    $fruit->delete();

    return $this->responseHelper->createSuccessResponse(
        $fruit->getTree()->getFruits(),
        Response::HTTP_ACCEPTED
    );
}

/**
 * Form handling.
 *
 * @param Request $request
 * @param Fruit   $fruit
 *
 * @return View|array
 */
protected function processForm(Request $request, Fruit $fruit)
{
    list($statusCode, $httpMethod, $action) = $this->getActionParameters($fruit);

    $form = $this->createForm(
        new FruitType(), $fruit,
        array('action' => $action, 'method' => $httpMethod)
    );

    if (in_array($request->getMethod(), array('POST', 'PUT'))) {
        if (!$form->handleRequest($request)->isValid()) {

            return $this->responseHelper->createErrorResponse($form);
        }
        $form->getData()->save();

        return $this->responseHelper->createSuccessResponse($form->getData(), $statusCode);
    }

    return compact('form');
}

/**
 * Set the form and action parameters depending on the REST action.
 *
 * @param Fruit $fruit
 *
 * @return array
 */
protected function getActionParameters(Fruit $fruit)
{
    if ($fruit->isNew()) {
        $statusCode = Response::HTTP_CREATED;
        $httpMethod = 'POST';
        $action = $this->generateUrl('backend_fruits_create');
    } else {
        $statusCode = Response::HTTP_OK;
        $httpMethod = 'PUT';
        $action = $this->generateUrl('backend_fruits_edit', array('id' => $fruit->getId()));
    }

    return array($statusCode, $httpMethod, $action);
}

注意:表单类型绑定到模型实体。

Note2 :正如您所看到的,我有一个额外的GET路线。它在开发时很有用,因为我可以在浏览器中调试我的表单。作为服务委托人,我将在完成后删除相关代码;路线和processForm函数,不需要测试方法并再返回表单。

/**
 * Form handling.
 *
 * @param Request $request
 * @param Fruit   $fruit
 *
 * @return mixed
 */
protected function processForm(Request $request, Fruit $fruit)
{
    list($statusCode, $httpMethod, $action) = $this->getActionParameters($fruit);

    $form = $this->createForm(
        new FruitType(), $fruit,
        array('action' => $action, 'method' => $httpMethod)
    );

    if (!$form->handleRequest($request)->isValid()) {
        return $this->responseHelper->createErrorResponse($form);
    }
    $form->getData()->save();

    return $this->responseHelper->createSuccessResponse($form->getData(), $statusCode);
}

Note3 :响应帮助器只是使用FOSRestBundle创建自定义View响应对象。

有关REST和Symfony2的更多信息:

答案 1 :(得分:3)

只是一些(希望)有用的说明:

首先,您可以从Request对象获取submit方法,不需要单独传递它:

getMethod()

其次,我想我找到了你要找的代码部分。首先,如果您检查Symfony Form类中的handleRequest调用,您可以看到它从位于配置中的handleRequest类调用RequestHandler(check {{ 1}}类)。我猜测FormConfigInterface的正确实现是NativeRequestHandler。您可以在48行看到检查请求方法是否相等。

现在,为了处理这个问题,您可以将表单的FormConfigInterface设置为自定义值,您可以将RequestHandler设置为您自己的实现。如果将RequestHandlerInterface定义为服务,那么您很幸运(目前我无法访问服务列表)。只需将类切换为指向您自己的实现。

说完这一切之后,我认为表格类型检查是有原因的。您应该像现在一样单独处理表单提交类型。此外,使用POST插入编辑是一个非常好的解决方案。越简单越好,引入新bug的机会就越少!