<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> <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>
<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>
Labels:
None
16 Comments
comments.show.hideJun 21, 2006
Keith Pope
<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>
Jun 21, 2006
Simon Mundy
<p>Cheers Keith! Sorted.</p>
Jun 28, 2006
Christopher Thompson
<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>
Jun 30, 2006
Simon Mundy
<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>
Jun 30, 2006
Christopher Thompson
<p>So is Zend_Form_Controller an Application Controller and the rest a Form Controller?</p>
Jun 30, 2006
Simon Mundy
<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>
Jul 14, 2006
Ralf Eggert
<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()
}
]]></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>
Jul 14, 2006
Simon Mundy
<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>
Jan 22, 2007
Matthew Ratzloff
<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>
Jan 22, 2007
Simon Mundy
<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()
public function memberAction()
public function cancelAction()
public function submitAction()
public function successAction()
public function failureAction()
}
]]></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>
Jan 26, 2007
Matthew Ratzloff
<p>Is it possible to modify this proposal to use some kind of submission token to prevent duplicate submissions? Perhaps using Zend_Session?</p>
Jan 24, 2008
Jurrien Stutterheim
<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>
Jan 24, 2008
Simon Mundy
<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>
Jan 25, 2008
Jurrien Stutterheim
<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>
Jan 25, 2008
Jurrien Stutterheim
<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>
Jan 28, 2008
Jurrien Stutterheim
<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()
public function indexAction()
public function orderAction()
{
}
public function userAction()
{
}
public function surveyAction()
{
}
public function processAction()
}
]]></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>