Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<h1><ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[Zend_Controller_Action_Helper_Multiform]]></ac:plain-text-body></ac:macro> Proposal Review</h1>

<ac:macro ac:name="info"><ac:parameter ac:name="title">Proposal Review Period</ac:parameter><ac:rich-text-body>
<p>This proposal is for review until <strong><ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><Unable to render embedded object: File (plain-text-body></ac:macro></strong>. This may change as the review progresses and more or less time is needed. You may enter comments directly at the end of the document, or for more directed comments you may <a><ac:plain-text-body><) not found.[CDATA[10017]]></ac:plain-text-body>">create a new issue</a> in the issue tracker for this project.</p></ac:rich-text-body></ac:macro>

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

]]></ac:plain-text-body></ac:macro>

<ac:macro ac:name="include"><ac:default-parameter>ZFDEV:Zend Proposal Zone Template</ac:default-parameter></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jun 21, 2006

    <p>Looks great </p>

    <p>I think you have a small typo in your use case section.</p>

    <p> the second class - MyApp_Form_Page1 extends Zend_Form_Controller_Page</p>

    <p> Should be class MyApp_Form_Page2 extends Zend_Form_Controller_Page</p>

    1. Jun 21, 2006

      <p>Cheers Keith! Sorted.</p>

  2. Jun 28, 2006

    <p>I'm a little confused by this proposal. Is this a Form Controller or an Application Controller. It seems to be a little of both. </p>

    1. Jun 30, 2006

      <p>It's intended to pass the processing of multiple pages (and validation, etc) to the helper class Zend_Form_Controller so that the parent controller needs only act upon a form submission once an entire form has been processed. So IMO it falls into the former definition.</p>

      1. Jun 30, 2006

        <p>So is Zend_Form_Controller an Application Controller and the rest a Form Controller?</p>

        1. Jun 30, 2006

          <p>Sorry, I think I see the meaning of your first question now. Each 'page' is a form controller allowing validation, filtering and processing. The parent class - Zend_Form_Controller - is an application controller soley for those pages, but doesn't provide any routing. It still allows it's parent controller to provide the routing and/or final processing.</p>

  3. Jul 14, 2006

    <p>Sounds very good to my, although I am not quite sure if I fully understand your proposal. Where will the form building and rendering be handled?</p>

    <p>In your UC-01 you have an example for extending Zend_Form_Controller_Page which uses a Form class.</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    class MyApp_Form_Page1 extends Zend_Form_Controller_Page
    {
    protected $_name = 'page1';
    function build()

    Unknown macro: { // $this->_form = new Form(); // $this->_form->addField( ... ); // etc... }

    }
    ]]></ac:plain-text-body></ac:macro>

    <p>Do I get it right, that this proposal does not include the form building part, i.e. generate the HTML for <input> and <select> fields? If so, we could still need a Zend_Form component, couldn't we?</p>

    <p>Thanks for your work so far. <ac:emoticon ac:name="smile" /></p>

    1. Jul 14, 2006

      <p>Indeed - as there is currently no working component for Zend_Form, I've left the actual implementation of form building to the end-user. Hopefully this can be remedied soon with a new Zend_Form proposal...</p>

      <p>So for the moment you would create your own form object and pass it to the controller</p>

      <p>$controller->setForm($yourFormObject);</p>

      <p>...and then when you're ready to display, you simply call render</p>

      <p>$view->form = $controller->render();</p>

      <p>This would return data according to the specs of your form object (associative array, object, fully-formed HTML string, etc).</p>

  4. Jan 22, 2007

    <blockquote><p>as there is currently no working component for Zend_Form, I've left the actual implementation of form building to the end-user.</p></blockquote>

    <p>In which case this is possibly putting the cart before the horse. I know this is an older proposal, but I'd like to comment on it.</p>

    <p>I would like to see more use cases for this, updated to incorporate the Request object as an information broker. An example of how a multi-page form would work would be a good start. As is, the form-as-separate-class approach <em>seems</em> correct, but the rest is a bit too vague.</p>

    <p>This should also be built on the new Zend_Filter and Zend_Validate proposal for form validation.</p>

    <p>Will there be any further progress on this proposal?</p>

    1. Jan 22, 2007

      <p>Yes there will! I've deliberately held back for two reasons - I wanted to see how MVC would progress before I determined how this component should best fit into an application, and I also wanted to see if we could progress Zend_Form a bit. The latter is still TBC but now I have a better understanding of the former I've been able to build a reasonably sturdy model that utilises the new request object and the Zend_Session component.</p>

      <p>Essentially the class skeleton is:-</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      abstract class Zend_Form_Controller extends Zend_Controller_Action
      {
      const ACTION_PREFIX = '_';

      protected $_id = 'default';
      protected $_page;

      private $_map;
      private $_current;
      private $_default;

      public function __construct(...params...)
      final public function indexAction()
      abstract function cancelAction();
      abstract function submitAction();
      abstract function successAction();
      abstract function failureAction();
      protected function _default($values = array())
      protected function _handle($form)
      protected function _register($key, $object)
      protected function _registry($key)
      protected function _export($page = null)
      protected function _clear()
      protected function _complete()
      private function _validate()
      private function _isValid($current)
      private function _getValid()
      private function _checkPage($page)
      private function _save($values, $valid = false)
      private function _filter($value, $prefix)
      ]]></ac:plain-text-body></ac:macro>

      <p>The '_page' property is an array of valid action names that are required for the form to complete. So I may have something like:-</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $_page = array('details', 'product', 'card', 'review');
      ]]></ac:plain-text-body></ac:macro>

      <p>Each of these page names are public methods (detailsAction(), productAction() etc...) and need to be validated/completed sequentially in order to complete the form.</p>

      <p>There are 4 abstract methods that need to be given concrete methods in the user class - cancel, submit, success and failure. That's because it's easier to allow the developer to implement their own business logic without needing to call parent methods to perform housekeeping on the form. So really the 'cancel', 'success' and 'failure' methods should call '_clear' when they are done (to reset the session) but it's a choice left to the developer.</p>

      <p>The '<em>handle' method takes care of validating and processing. An array of user-submitted data (taken from a PEAR form class, for example) is passed acrossed and saved in the $_map property. If a submit value contains a '</em>' prefix it indicates an action is to be taken from the post. So if you had a next button with the form name of '<em>next' it calls the next action, '_back' is back, '_submit' calls the 'submit' action, '_cancel' calls cancel, etc. Or you could jump to a specific action by prefixing the action name with '</em>' (like _product would redirect the user to the 'productAction' page). The prefix is developer-configurable, too.</p>

      <p>The default implementation enforces sequential entry but there's no reason why a more flexible approach could be taken (for a user pref form, for example, that's controlled by 'tabs').</p>

      <p>Here's a concrete example that extends this class:-</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <?php

      class Payment_MemberController extends Zend_Form_Controller
      {
      protected $_product;
      protected $_page = array('member',
      'card',
      'review');

      protected function init()

      Unknown macro: { $this->_product = new Zend_Config(new Zend_Config_Ini('product/member.ini', 'staging')); }

      public function memberAction()

      Unknown macro: { $form = new My_Form_PaymentMember('member'); $form->register('product', $this->_product); $form->populate($this->_default('member')); $this->_handle($form); // Allocate and render $this->view->title = 'Payment'; $this->view->form = $form->render(); $this->view->render('payment/member.tpl'); }

      public function cancelAction()

      Unknown macro: { $this->_clear(); $this->_forward('about', 'success'); }

      public function submitAction()

      Unknown macro: { ...payment processing... ...log transaction.... ...email notification... $this->_forward('member', 'success'); }

      public function successAction()

      Unknown macro: { $this->view->title = 'Order confirmation'; $this->view->receipt = $this->_registry('receipt'); $this->view->render('payment/member-success.tpl'); $this->_clear(); }

      public function failureAction()

      Unknown macro: { $this->view->title = Order confirmation'; $this->view->render('payment/member-failure.tpl'); $this->_clear(); }

      }
      ]]></ac:plain-text-body></ac:macro>

      <p>The code above represents a working solution in a production environment, so I'll post the less-specific abstract class shortly for everyone's review to see if it meets expectations. I'm still on-the-fence as to whether this deserves a whole new component or whether it's simply a new class within the Zend_Controller family... Would appreciate everyone's feedback.</p>

      <p>Thanks for the prod Matthew!</p>

      1. Jan 26, 2007

        <p>Is it possible to modify this proposal to use some kind of submission token to prevent duplicate submissions? Perhaps using Zend_Session?</p>

  5. Jan 24, 2008

    <p>With Zend_Form in the 1.5 release I'm interested in the status of this idea. This combined with Zend_Form's subforms would be quite useful.<br />
    I think I'm gonna play a bit with this and Zend_Form... <ac:emoticon ac:name="smile" /></p>

    1. Jan 24, 2008

      <p>Wow, I hadn't realised it had been over a year since I wrote this! Would love to hear how you get on with it - I'm using it in a production site without any troubles at the moment.</p>

      <p>I'm not sure what MR's requirement was for duplicate submissions... but if it helps, the component performs a HTTP redirect after each form is submitted so performing a browser refresh will only act on the current page, not attempt to re-submit any POST data.</p>

      1. Jan 25, 2008

        <p>The idea and working of this helper is actually quite similar to something I had in my own Form proposal. I really like this approach to tell you the truth.</p>

        <p>You could run into problems with the session usage if you haven't registered an autoload and session_start() is called before the corresponding form class is loaded.<br />
        I'm toying with the idea to modify the helper a bit to also support cache a a storage method. (also had this in my own _Form <ac:emoticon ac:name="wink" />) This could help getting around this problem.</p>

        <p>Am I correct when I think this helper doesn't support multiple instances of a form being used at the same time? E.g.: run two or more blog-post wizards at the same time in the same browser.<br />
        I know such a feature complicates things a lot, so I'm still not sure if the costs weigh up to the benefits.</p>

        1. Jan 25, 2008

          <p>Forgot what I said about the problems with autoload... I shouldn't comment early in the morning. I assumed you serialized the entire form, instead of just its values.</p>

  6. Jan 28, 2008

    <p>An update on my progress with this helper.<br />
    I hacked the original source up to specialize the helper in Zend_Form.<br />
    Some stuff is completely new, other stuff is completely copied from this proposal <ac:emoticon ac:name="wink" /><br />
    It's still a WIP and could use a lot more testing and refactoring, but here's a sneak preview:
    <a class="external-link" href="http://trac2.assembla.com/zym/browser/trunk/incubator/library/Zym/Controller/Action/Helper">http://trac2.assembla.com/zym/browser/trunk/incubator/library/Zym/Controller/Action/Helper</a></p>

    <p>The good part is: it actually works! <ac:emoticon ac:name="laugh" /></p>

    <p>The controller code I use in a small demo (not online yet):</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    class FormController extends Zend_Controller_Action
    {
    protected $_multiformHelper;
    protected $_form;

    public function init()

    Unknown macro: { $this->_form = new MyForm(); $this->_multiformHelper = $this->getHelper('MultiForm'); $this->_multiformHelper->setForm($this->_form); $this->_multiformHelper->setActions(array('order', 'user', 'survey')); }

    public function indexAction()

    Unknown macro: { $this->_multiformHelper->clear(); $this->getHelper('Redirector')->gotoAndExit('order'); }

    public function orderAction()
    {
    }

    public function userAction()
    {
    }

    public function surveyAction()
    {
    }

    public function processAction()

    Unknown macro: { $data = $this->_multiformHelper->getAllSubformData(); /** * Display a success screen for this demo. * In real-life use you'd probably write the data to the DB here. * Note that all data in the $data array is filtered and validated, * so you don't have to do that here anymore. */ $this->view->formData = $data; }

    }
    ]]></ac:plain-text-body></ac:macro>

    <p>The form instance has three subforms registered. For each subform is a controller action with the same name (for some custom stuff you might want to do).<br />
    Once the submit button is pressed and form is completely valid it automagically redirects to the processAction. This of course can be customized.</p>