【问题标题】:Submit a Form using PUT instead of POST使用 PUT 而不是 POST 提交表单
【发布时间】:2014-04-22 11:33:06
【问题描述】:

我有一个控制器来处理来自 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 请求并且返回表单无效,没有任何表单错误消息。如果我修改控制器代码,

$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;
}

【问题讨论】:

    标签: php symfony


    【解决方案1】:

    只是一些(希望是)有用的注释:

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

    getMethod()

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

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

    说了这么多,我认为表单类型检查是有原因的。您应该像现在一样单独处理表单提交类型。此外,使用 POST 插入 编辑也是一个很好的解决方案。越简单越好,引入新错误的机会越小!

    【讨论】:

    • 你好。昨天我正在实现另一个类似的表单,我突然意识到我可以使用Request::getMethod()!无论如何,非常感谢。
    【解决方案2】:

    [EDIT 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);
    }
    

    注意 3:响应助手只是使用 FOSRestBundle 创建一个自定义的 View 响应对象。

    更多关于 REST 和 Symfony2:

    【讨论】:

    • 很抱歉这么晚才回复。该项目已进入下一步,一旦我有时间重构旧代码,我将测试您的解决方案。会努力尽快做到的!谢谢。
    • 您好,您的回答看起来不错,但我找到了更好的处理方法。请参阅标记的答案。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    • 1970-01-01
    • 1970-01-01
    • 2017-05-04
    • 1970-01-01
    • 2013-04-10
    相关资源
    最近更新 更多