Back on the submit of forms - with use case code

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Back on the submit of forms - with use case code

Post by josh »

ZF already handles this elegantly, viewscript form decorator. Uses view scripts for high level formatting, renders elements via helpers so you don't duplicate HTML. Element abstraction is necessary at the micro level, unless you consider it efficient to deal with collections directly when generating things like <select>s. I believe you can use view scripts for generating each element, I usually implement that thru subForms ( Zend_Form implements composite pattern ) which uses array notation and provides for a more flexible form ( turning single fields into "multi" fields is a matter of configuring a few things and using a jquery plugin i wrote )
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Re: Back on the submit of forms - with use case code

Post by inghamn »

You know....I really do believe it's easier and more effecient to deal with the collections directly, instead of using ZF's views and decorators. I believe the views should look like HTML. That way, they can be edited by designers with minimal PHP experience. And if you're reusing the views throughout the application, there's really not any major repetition.

Code: Select all

 
<?php
/**
 * @param Media $this->media
 * @param string $this->return_url
 */
?>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>">
<fieldset><legend>Media</legend>
    <input name="media_id" type="hidden" value="<?php echo $this->media->getId(); ?>" />
    <input name="return_url" type="hidden" value="<?php echo View::escape($this->return_url); ?>" />
 
    <table>
    <tr><td><label for="media-title">Title</label></td>
        <td><input name="media[title]" id="media-title" value="<?php echo View::escape($this->media->getTitle(); ?>" />
        </td>
    </tr>
    <tr><td colspan="2">
            <div><label for="media-description">Description</label></div>
            <textarea name="media[description]" id="media-description" rows="3" cols="60"><?php echo View::escape($this->media->getDescription()); ?></textarea>
        </td>
    </tr>
    <tr><td><label for="media-roll_id">Roll</label></td>
        <td><select name="media[roll_id]" id="media-roll_id">
            <?php
                $rolls = new RollList();
                $rolls->find();
                foreach ($rolls as $roll) {
                    $selected = $roll->getId()==$this->media->getRoll_id()
                                ? ' selected="selected"' : '';
                    echo "<option value=\"{$roll->getId()}\"$selected>{$roll}</option>";
                }
            ?>
            </select>
        </td>
    </tr>
    </table>
 
    <button type="submit" class="save">Save</button>
    <button type="button" class="cancel" onclick="document.location.href='<?php echo $this->return_url; ?>';">
        Cancel
    </button>
</fieldset>
</form>
 
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Back on the submit of forms - with use case code

Post by Christopher »

PCSpectra wrote:In my own applications there are two controllers, one which builds the view,
So one controller acts as a Controller and the other controller acts as a View ... sounds good ...
josh wrote:ZF already handles this elegantly, viewscript form decorator. Uses view scripts for high level formatting, renders elements via helpers so you don't duplicate HTML.
inghamn wrote:You know....I really do believe it's easier and more effecient to deal with the collections directly, instead of using ZF's views and decorators. I believe the views should look like HTML.
Apparently there is a difference of opinion on this subject. ;)
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Back on the submit of forms - with use case code

Post by Christopher »

Chris Corbyn wrote:I was thinking that myself too. It's more of a RequestHandler than a FormHandler. The only things that make my code closer to forms than other request inputs are the fact I have isSubmitted() and isCancelled() methods. The View helper is very much tied to forms, but that's since it's view level stuff.
The problem with the RequestHandler idea is that is ends up being something that is never used. A FormHandler solves a very specific problem. Forms are just not like regular requests because of they must be resubmitted until valid.
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Back on the submit of forms - with use case code

Post by Chris Corbyn »

I got this working nicely myself. My view is HTML that I write myself. If I want re-usable fragments I'll do that in the normal way I'd do it for any other view fragments.

The form processing code is more or less what I proposed... it's more about request data validation than anything else. It would need some work to be considered full-featured but I'm happy to move on for now. The only disadvantage I can note at this point is that the lack of marriage between form handling and HTML generation means there's more chance for inconsistencies in validation error messages compared with form <label> elements. For example <label>Confirm Password</label> in the view, but "Repeat Password" being used in the controller. Something to think about, but really it's a minor caveat.

(Not yet refactored...):

Code: Select all

   $form = $this->_getForm( //Convenience method for the FormHandler creation
      array('filename', 'source'),
      array(
        'filename' => $this->_getCurrentDownload()->getName(),
        'source' => $this->_getCurrentDownload()->getSource()
      )
    );
    
    $this->view->assign(array(
      'title' => 'Download Manager',
      'user' => $this->_getAuthenticator()->getIdentity(),
      'downloads' => $this->_getAllDownloads(),
      'formValues' => $form->getValues()
    ));
    
    if (!$form->isSubmitted())
    {
      return;
    }
    
    $validator = $this->_getValidator(array(
      new Swift_Website_Validation_Rules_RequiredRule('filename', 'Filename'),
      new Swift_Website_Validation_Rules_RequiredRule('source', 'Download Location'),
      new Swift_Website_Validation_Rules_EnumRule(
        'source', array('sourceforge', 'googlecode', 'github'), 'Download Location'
      )
    ));
    
    if ($validator->isValid($form->getValues()))
    {
      $values = $form->getValues();
      
      if (Doctrine::getTable('Download')->findAvailableByName($values['filename']))
      {
        $validator->addValidationError(
          'This file already exists.  Perhaps you need to revoke it?'
        );
      }
      else
      {
        $download = new Download();
      
        try
        {
          Zend_Registry::getInstance()->get('download_factory')
            ->createDownload($values['filename'], $values['source'], $download)
            ;
          $download->setStable(false);
          $download->save();
          
          Zend_Registry::getInstance()->get('notification_queue')
            ->addNotification(
              'The download was created but is marked as unstable. You may edit it.'
            )
            ;
          
          return $this->_helper->getHelper('Redirector')
            ->gotoRoute(
              array(
                'action'=>'edit-download',
                'controller'=>'admin',
                'downloadid' => $download->getDownloadId()
              ),
              'edit-download'
          );
        }
        catch (Swift_Website_DownloadNotFoundException $e)
        {
          $validator->addValidationError(
            'This file could not be found at the remote location'
          );
        }
      }
    }
    
    $this->view->assign(array(
      'validationErrors' => $validator->getValidationErrors()
    ));
And the view:

Code: Select all

   <h1>Download Manager</h1>
    
    <?php require('fragments/_notifications.phtml'); ?>
    
    <p>
      From here you can view the list of downloads added for public consumption.  You
      can revoke these downloads (in the event of say a really critical bug) and you
      can add to them.
    </p>
    
    <p>
      In order to add a download it must first exist at either SourceForge, Google Code
      or GitHub.  This Download Manager will lookup the details of the package from
      the remote resource.
    </p>
    
    <h2>Add a new download</h2>
    
    <?php require('fragments/_validation-errors.phtml'); ?>
    
    <form id="adddownloadform" action="" method="post">
      <fieldset class="downloadfields">
        <label for="field_filename">
          <strong>Package Filename</strong>
          <span class="instruction">
            You need to specify the full name of the file that is available for download
          </span>
        </label>
        <input type="text" class="text" id="field_filename" <?php echo $this->formAttributes('input', 'filename'); ?> />
        
        <label for="field_sources">
          <strong>Download Location</strong>
          <span class="instruction">
            Downloads must be hosted at a third-party site. The download manager will determine
            the correct link for the remote file.
          </span>
        </label>
        <fieldset id="field_sources" class="radios">
          <label for="field_source_sourceforge">
            <input type="radio" class="radio" id="field_source_sourceforge" <?php echo $this->formAttributes('radio', 'source', 'sourceforge'); ?> />
            SourceForge
          </label>
          <label for="field_source_googlecode">
            <input type="radio" class="radio" id="field_source_googlecode" <?php echo $this->formAttributes('radio', 'source', 'googlecode'); ?> />
            Google Code
          </label>
          <label for="field_source_github">
            <input type="radio" class="radio" id="field_source_github" <?php echo $this->formAttributes('radio', 'source', 'github'); ?> />
            GitHub
          </label>
        </fieldset>
      </fieldset>
      
      <fieldset class="buttons">
        
        <input type="submit" class="primary button" name="add" value="Add Download" />
        
      </fieldset>
    </form>
 
<!-- ... SNIP ... -->
 
It's so incredibly non-over-engineered and there's really no exciting magic happening here at all. I'm still not 100% happy with it but generally it's the way I see things working. I quite like the idea of breaking the FormHandler away into it's own class though...
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Back on the submit of forms - with use case code

Post by josh »

require != partial helper... I know its more comfortable to do things the old way, but the view helpers really do do a lot of heavy lifting for you. The biggest benefit with the view helpers I see, is lets say a field needed to be changed to an entirely different type, now you don't need a designer at all! I can't really think of any example where I'd need to generate the HTML by hand especially since you can inject custom attributes, but I guess thats just a matter of opinion like what was already said, just wanting to clear up a few misconceptions
User avatar
allspiritseve
DevNet Resident
Posts: 1174
Joined: Thu Mar 06, 2008 8:23 am
Location: Ann Arbor, MI (USA)

Re: Back on the submit of forms - with use case code

Post by allspiritseve »

Chris Corbyn wrote:I got this working nicely myself... It would need some work to be considered full-featured but I'm happy to move on for now.
Chris, have you improved on this code at all?
Chris Corbyn wrote:The only disadvantage I can note at this point is that the lack of marriage between form handling and HTML generation means there's more chance for inconsistencies in validation error messages compared with form <label> elements. For example <label>Confirm Password</label> in the view, but "Repeat Password" being used in the controller. Something to think about, but really it's a minor caveat.
Just a quick thought, but would it work to have error messages in the html already, just hidden until they're needed?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Back on the submit of forms - with use case code

Post by Chris Corbyn »

allspiritseve wrote:Chris, have you improved on this code at all?
Not really, I haven't worked on it since putting the site online.
Just a quick thought, but would it work to have error messages in the html already, just hidden until they're needed?
Do you mean with CSS/JavaScript? I'm really not a fan of that sort of thing since it's not really accessible (screen readers will always report the errors) and how would you predict each possible combination of errors before the form has been submitted?
wei
Forum Contributor
Posts: 140
Joined: Wed Jul 12, 2006 12:18 am

Re: Back on the submit of forms - with use case code

Post by wei »

I don't know if the following structure have been discussed before, at least I haven't seen it:

Code: Select all

 
 
class LoginForm extends BaseFormModel {
   var $username;
   var $password;
   var $password_again;
 
   protected function getRules() {
       //validators, sample syntax
       return array('username' => 'required', 
                        'password' => 'required', 
                        'password_again' => array('required', 
                               array('match' => array('password', 'password_again'))));  
   }
}
 
... controller
 
function postLoginForm() {
     $form = new LoginForm(); 
     if(isPostBack) {
        $form->setAttribute($_POST);
        if($form->isValid()) {
             //do something with it
        }
   } 
   $this->render('login' => $form);
 }
 
So the basic idea is that the form itself is a data model.
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Re: Back on the submit of forms - with use case code

Post by matthijs »

wei wrote:So the basic idea is that the form itself is a data model.
We have been discussing this concept for a while now in the skeleton framework. The idea was that you would have:
- models with rules and filters (domain logic stuff)
- form models which can ask a model (which it is handling the form for) for the rules and filters,
- form models can have additional rules and filters

A good example would be a register form (like you show). The form would ask for a username, email and password and an additional field to repeat the password. That additional password field must have a validation rule as well (it must match the first password). But you don't want to put that extra rule in the User model itself. The User model only cares about getting a valid username+email+password. The repeat-password is a rule for the (model)form itself.
wei
Forum Contributor
Posts: 140
Joined: Wed Jul 12, 2006 12:18 am

Re: Back on the submit of forms - with use case code

Post by wei »

composition or inheritance for domain model + form model?

there are a few nice features for this approach:
1. the default values can be the values of the attributes declared in the form class.
2. the form model can be tested, although it is usually not complex
3. the form model is separate to: rendering, validation and persistence

there are however a few problems:
1. validation error message become naturally coupled with the form model, not such a big deal
2. how to deal with transient state? Think wizard forms, i.e. only persist in the final step of the wizard, or saved forms.
3. updating the form model usually requires changes to the form rendering/layout, which is probably ok,
4. who handles data formats? e.g. date input (could be localized) -> timestamp, could be form model or domain model
Post Reply