2

我做了一个安静的控制器,如果我发送 id,get 方法会接收它。但是,当我更新表单时,我希望更新方法能够处理,但我无法为此获得正确的配置,并且在遇到此问题 1 天后,我决定将其修复在这里。

这里的代码涉及模块配置中的路由:

        'activities' => array(
            'type' => 'segment',
            'options' => array(
                'route' => '/activities[/:id][/:action][.:formatter]',
                'defaults' => array(
                    'controller' => 'activities'
                ),
                'constraints' => array(
                    'formatter' => '[a-zA-Z0-9_-]*',
                    'id' => '[0-9_-]*'
                ),
            ),
        ),

控制器组长:

namespace Clock\Controller;

use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\Mvc\MvcEvent;
use Zend\View\Model\ViewModel;
use Zend\Form\Annotation\AnnotationBuilder;
use Zend\Form;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Clock\Entity\Activity;
use \Clock\Entity\Project;

Wich contains the get method:

    public function get($id)
    {
        $entity = $this->getRepository()->find($id);
        $form = $this->buildForm(new Activity());
        #$form->setAttribute('action', $this->url()->fromRoute("activities", array('action' => 'update')));
        $form->setAttribute('action', "/activities/$id/update");
        $form->bind($entity);
        return array(
            "activities" => $entity,
            "form" => $form
        );
    }

这支持了这种观点:

<h3>Edit activity</h3>
<div>
    <?php echo $this->form()->openTag($form);?>
    <?php echo $this->formSelect($form->get("project"));?><br>
    <?php echo $this->formInput($form->get("duration"));?><br>
    <?php echo $this->formInput($form->get("description"));?><br>
    <input type="submit" value="save changes" />
    <?php echo $this->form()->closeTag($form);?>
</div>

发送后,我希望活动中的更新方法能够控制,但我得到:

A 404 error occurred
Page not found.

The requested controller was unable to dispatch the request.

Controller:
    activities 

编辑:@DrBeza 这是我得到的,我认为(不是路线大师)是正确的:

Zend\Mvc\Router\Http\RouteMatch Object
(
    [length:protected] => 21
    [params:protected] => Array
        (
            [controller] => activities
            [id] => 30
            [action] => update
        )

    [matchedRouteName:protected] => activities
)

--

而已。有什么帮助吗?

4

2 回答 2

3

Quick Fix

The RouteMatch object tries to dispatch ActivitiesController::updateAction but you have defined ActivitiesController::update That's due to you using a Restful Controller. the Controller::update-Method is specifically tied to PUT-Requests. You need to define an extra method to handle updates via POST-Requests.

I suggest you define ActivitiesController::updateAction, make clear in the docblock it is meant to handle POST-Update requests and refactor both ::updateAction and ::update to share as much common helper-methods as possible for a fast solution.

Common URI Structur information

As a nice information to have when you start developing RESTful applications/APIs: The ruby community suggests the following url-structure for your resources:

# These are restful 
/resource          GET (lists)   | POST (creates)
/resource/:id      PUT (updates) | DELETE (deletes)

# these are just helpers, not restful, and may accept POST too.
/resource/new      GET (shows the create-form), POST
/resource/:id/edit GET (shows the update-form), POST

Detailed Problem Analysis

A restful update will be sent by an consumer via PUT, but browsers sending HTML-forms may only send GET or POST requests. You should never use GET to create something. So you have to use POST in a forms-context.

Looking at the problem from an architectural perspective a multitude of possibilities emerge, depending on how big your application is.

  • For a small application, tight integration (formhandling and API handling in the controller) apply best.
  • Getting bigger you may want to split up API-Controllers (only restful actions) from Helper-Controllers (form, website handling) which talk to your API-Controllers
  • Being big (multitude of API-Users) you will want to have dedicated API Servers and dedicated Website Servers (independent applications!). In this case your website will consume the API serverside (thats what twitter is doing). API Servers and Website Servers still may share libraries (for filtering, utilities).

Code Sample

As an educational example I made an gist to show how such a controller could look like in principle. This controller is a) untested b) not production ready and c) only marginally configurable.

For your special interest here two excerpts about updating:

/* the restful method, defined in AbstractRestfulController */
public function update($id, $data)
{
    $response = $this->getResponse();

    if ( ! $this->getService()->has($id) )
    {
        return $this->notFoundAction();
    }

    $form = $this->getEditForm();
    $form->setData($data);

    if ( ! $form->isValid() )
    {
        $response->setStatusCode(self::FORM_INVALID_STATUSCODE);
        return [ 'errors' => $form->getMessages() ];
    }

    $data = $form->getData(); // you want the filtered & validated data from the form, not the raw data from the request.

    $status = $this->getService()->update($id, $data);

    if ( ! $status )
    {
        $response->setStatusCode(self::SERVERSIDE_ERROR_STATUSCODE);
        return [ 'errors' => [self::SERVERSIDE_ERROR_MESSAGE] ];
    }

    // if everything went smooth, we just return the new representation of the entity.

    return $this->get($id);
}

and the editAction which satisfies browser-requests:

public function editAction()
{
    /*
     * basically the same as the newAction
     * differences:
     *  - first fetch the data from the service
     *  - prepopulate the form
     */

    $id = $this->params('id', false);
    $dataExists = $this->getService()->has($id);

    if ( ! $dataExists )
    {
        $this->flashMessenger()->addErrorMessage("No entity with {$id} is known");
        return $this->notFoundAction();
    }

    $request = $this->getRequest();
    $form = $this->getEditForm();
    $data = $this->getService()->get($id);

    if ( ! $request->isPost() )
    {
        $form->populateValues($data);
        return ['form' => $form];
    }

    $this->update($id, $request->getPost()->toArray());
    $response = $this->getResponse();

    if ( ! $response->isSuccess() )
    {
        return [ 'form' => $form ];
    }

    $this->flashMessenger()->addSuccessMessage('Entity changed successfully');
    return $this->redirect()->toRoute($this->routeIdentifiers['entity-changed']);
}
于 2013-02-22T11:22:13.867 回答
1

该错误消息表明调度进程无法找到请求的控制器操作,因此使用notFoundAction().

我会检查匹配的路线并确保值符合预期。您可以通过将以下内容添加到模块的onBootstrap()方法中来做到这一点:

$e->getApplication()->getEventManager()->attach('route', function($event) {
    var_dump($event->getRouteMatch());
    exit;
});
于 2013-02-07T16:34:53.780 回答