SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Getting two classes to work together

    Hey all

    I've recently developed a class (et subclasses) for generating HTML form elements that I REALLY like. Partly because my new job had no such system, and partly because I wanted the challenge of creating one from scratch.

    Separately, I developed a data-validation class (et subclasses) that I intend to use with the form generator, or anything else.

    Now, the trick is getting them hooked together nicely. I've managed to do it in a less-than stellar way for now, but I'm looking for a pattern or design that will help me do it better. I'm willing to trash the data-validation class (it's really small anyways) but the form generator is perfect.

    Here's some example code of the form generator, a simple login form
    PHP Code:
       <?php
       
       
    require_once 'Forms/Forms.php';
       
       
    //    Create form
       
    $Form = new Form();
       
       
    //    Create two form elements
       
    $Form->addElement( new FormTextElement'Username''username', array(), 'Username' ) );
       
    $Form->addElement( new FormPasswordElement'Password''password', array(), 'Password' ) );
       
       
    ?>
       <html>
           <body>
               <form method="post">
                   <div>
                  <?php echo $Form->renderElementLabel'Username' ); ?><br/>
                  <?php echo $Form->renderElement'Username' ); ?>
                   </div>
                   <div>
                  <?php echo $Form->renderElementLabel'Password' ); ?><br/>
                  <?php echo $Form->renderElement'Password' ); ?>
                   </div>
                   <input type="submit" value="Login"/>
           </body>
       </html>
    The class Form is simply a manager for all the FormElement objects (FormElement is abstract, from which FormTextElement etc inherit).

    What I love about the system is that you have the control to render each element and it's label individually, but can develop your own renderers for more automated form generation.

    The data validation class is simple, right now. Here's a small snapshot of the class
    PHP Code:
       /**
       * @abstract
       */
       
    class DataValidator
       
    {
           var 
    $data;
           
           function 
    DataValidator$data )
           {
               
    $this->data        $data;
           }
           
           function 
    validate(){}
           
           function 
    getError()
           {
               return 
    'There was an error with the supplied data';
           }
       }
       
       class 
    DVRequired extends DataValidator 
       
    {    
           function 
    validate()
           {
               if ( 
    '' == trim$this->data ) )
               {
                   return 
    false;
               }
               return 
    true;
           }
           
           function 
    getError()
           {
               return 
    'Field required';    
           }
       }
       
       class 
    DVAlpha extends DataValidator 
       
    {
           function 
    validate()
           {
               if ( 
    ctype_alpha$this->data ) )    
               {
                   return 
    true;    
               }
               return 
    false;
           }
       } 
    There's a 3rd class, FormValidator, which I'm using right now to hook the two together. Example of how it hooks the two together (simple and quick)
    PHP Code:
    <?php
     
     
    require_once 'Forms/Forms.php';
     
     
    //    Create form
     
    $Form = new Form();
     
     
    //    Create two form elements
     
    $Form->addElement( new FormTextElement'Username''username', array(), 'Username' ) );
     
    $Form->addElement( new FormPasswordElement'Password''password', array(), 'Password' ) );
     
     if ( 
    $_POST['submit'] == 'yes' )
     {
         require_once 
    'Validation/FormValidator.php';
         
         
    //    Create a validator and register a Form and data-source
         
    $Validator = new FormValidator$Form$_POST );
         
         
    //    Make some validation rules
         
    $Validator->requireField'Username' );
         
    $Validator->alpha'Username' );
         
         
    //    Validate all rules
         
    if ( $Validator->validateAll() )
         {
             
    //    Do Whatever
         
    } else {
             
    //    Add CSS errors to all labels and their elements that errored
             
    $Form->addCSSErrors$Validator->getErrorIDs(), 'error' );
             
             
    //    Tell the form to look for values from a given source
             
    $Form->registerValueSource$_POST );
         }
     }
     
     
    ?>
     <html>
         <head>
             <style type="text/css">
                 label.error { color: red; }
                 input.error { borer: 1px solid red }
             </style>
         </head>
         <body>
             <form method="post">
                 <div>
                     <?php echo $Form->renderElementLabel'Username' ); ?><br/>
                     <?php echo $Form->renderElement'Username' ); ?>
                 </div>
                 <div>
                     <?php echo $Form->renderElementLabel'Password' ); ?><br/>
                     <?php echo $Form->renderElement'Password' ); ?>
                 </div>
                 <input type="hidden" name="submit" value="yes" />
                 <input type="submit" value="Login"/>
         </body>
     </html>
    Basically, a method exists in FormValidator for each subclass of DataValidator which just passes the info on. For Example, here's the code of FormValidator::requireField()
    PHP Code:
       function requireField$elementName )
           {
              
    $this->addRule(
                 
    $elementName, new DVRequired$this->dataSource->get$elementName ) )
             );
           } 
    To me, this both IS and FEELS very clunky. Everytime I extend DataValidator I have a make a new calling method in FormValidator.

    I think the only way to get around this is to rewrite my DataValidation class. But I don't want to create something that is tied to validating form data. I'd like to be able to leverage DataValidator elsewhere when needed, even when I'm not even dealing with HTML forms at all.

    Any tips/suggestions would be great.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  2. #2
    get into it! bigduke's Avatar
    Join Date
    May 2004
    Location
    Australia
    Posts
    847
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Dunno how to go about your problem but here's something I noticed ...
    PHP Code:
    <?php echo $Form->renderElementLabel'Password' ); ?>
    The above is a nice example of OOP overuse. Why must you echo it like that when you could have just echoed 'Password' ?

  3. #3
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Because it renders an actual <label> element with the "for" attribute properly set for the element it was attached to.

    The HTML generated would look like this

    HTML Code:
    <label for="password">Password</label><br/>
    <input type="password" name="Password" id="password" />
    If there was errors, it might look something like this

    HTML Code:
    <label for="password" class="error">Password</label><br/>
    <input type="password" name="Password" id="password" class="error" />
    Don't assume too much
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  4. #4
    get into it! bigduke's Avatar
    Join Date
    May 2004
    Location
    Australia
    Posts
    847
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I had to, for i saw no code

  5. #5
    SitePoint Addict been's Avatar
    Join Date
    May 2002
    Location
    Gent, Belgium
    Posts
    284
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by beetle
    To me, this both IS and FEELS very clunky. Everytime I extend DataValidator I have a make a new calling method in FormValidator.
    You could have a system whereby you'd just register the class name of the validator to use for a certain field:
    PHP Code:
          $f = new Form();
          
    $f->addValidator('name''Required');
          
    $f->addValidator('name''Alpha'); 
    The form component would then know where and how to get and create the validators or delegate the creation of the validators to some factory. This way, creation of the validators is only done when necessary; when the form page is visited for the very first time, for example, no validators should be created, as there's nothing to validate.

    Keeping the field name out of the validators would make the validators more generic, but there are issues with this approach; if you're developing a multi-language site, you'd have to seperate the validation error messages from your main form components and then you have the issue of managing which errors go with what form field. This becomes rather messy.

    A solution would be to implement a kind of FormFieldValidor as a decorator of the DataValidator. The FormFieldValidator would then take a field name and a DataValidator, you pass through the submitted form data, $_POST for example, and each validator would know which form field it was set out to check. When the validation errors, the validator itself now knows which field and as such can pass this to an error handling system.

    Anyways, you should do a search here on the forums, it's a topic that has been discussed quite a lot
    Per
    Everything
    works on a PowerPoint slide

  6. #6
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Take a look at HTML_QuickForm from the PEAR repository.
    There's a nice tutorial at this link : http://www.devarticles.com/c/a/Web-G...rm-Processing/

  7. #7
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    HTML_QuickForm is much too bulky for my tastes, and it's validation is somewhat inseparable from it. I'm sure it functions wonderfully, it's just not a tool I wish to start using.

    Been,

    That's not a terrible idea, I'd just have to come up with a way to pass on parameters, as many of the validation classes take more parameters besides the name of the form element.

    But, overall, I like your idea. I didn't think to make decorators of DataValidator. I'll mess around designing that sometime soon.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  8. #8
    SitePoint Addict been's Avatar
    Join Date
    May 2002
    Location
    Gent, Belgium
    Posts
    284
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    By passing the parameters along as a simple array and providing an initialization method in the validators, you can use call_user_func_array().

    You could even pass them to the constructor, but this will involve using eval() at some point:
    PHP Code:
    function &instantiate($class$parameters = array()) {
                switch (
    count($parameters)) {
                    case 
    0:
                        
    $object =& new $class();
                        break;
                    case 
    1:
                        
    $object =& new $class($parameters[0]);
                        break;
                    case 
    2:
                 
    $object =& new $class($parameters[0], $parameters[1]);
                        break;
                    case 
    3:
                 
    $object=& new $class($parameters[0], $parameters[1], $parameters[2]);
                        break;
                    
    // Can't go on indefinately: resort to eval eventually
                    
    default:
                 
    $code '$object =& new ' $class '($parameters[';
                 
    $code .= implode('],$parameters['array_keys($parameters));
                        
    $code .= ']);';
                        eval(
    $code);
                }
                return 
    $object;
     } 
    Of course, you have to be carefull with eval(); if you have no control over where the $parameters parameter comes from, this is a potential security risk. In that case, make sure the keys are integers!

    btw, you can call me "Per", although not my official first name, this is what friends and family call me since birth.
    I've come to a point where I feel comfortable enough on this forum for people to call me by my "real" name

    EDIT:
    Oops, allmost forgot a SimpleTest (which is rather, errr, simple, I admit ):
    PHP Code:
    class SomeObject {
          var 
    $_param;
          
          function 
    SomeObject(&$param) {
              
    $this->_param =& $param;
          }
          function &
    getParameter() {
              return 
    $this->_param;
          }
      }
      
      class 
    DynamicInstantiationTest extends UnitTestCase {
          function 
    DynamicInstantiationTest() {
              
    $this->UnitTestCase();
          }
          function 
    testReferencesInParameters() {
              
    $o =& instantiate('SomeObject', array(&$this));
              
    $this->assertIsA($o'SomeObject');
              
    $this->assertReference($this$o->getParameter());
          }
      } 
    Per
    Everything
    works on a PowerPoint slide

  9. #9
    SitePoint Enthusiast
    Join Date
    Mar 2005
    Posts
    31
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I apologize for the long rambly nature of this post. I'm still sifting through my ideas on how to best implement form printing/validation classes. I'm just really starting to try to think in OOP so any help/comments/crticisims are more than welcome.

    My main motivation is to have something that integrates well with Mojavi 2. So my feature list looks something like this:

    • Quick creation of form elements as in Beetle's or QuickForm's implementation (both seem very similiar to me).
    • Easily create and switch out renderers for the form.
    • Server-side validation must be seperable from the creation of the form, but
    • Client-side display of errors should be rendered in the form and,
    • Client-side javascript validation should be included as well.


    I think that covers most everything I want. I've looked around a good bit at different libraries and haven't found anything quite like what I want. PEAR's QuickForm is actually rather close, but as Beetle has noted it's pretty bulky. I also really like voostind's suggestions in this old thread:

    http://www.sitepoint.com/forums/show...threadid=77563

    Here's my half psuedo-code on how I want things to work, which I've been working on from a heavily trimmed down version of PEAR's QuickForm.

    First a couple of classes for a specific form and its validation:

    PHP Code:
    Class NameValidator extends RuleRegistry
    {
        function 
    NameValidator()
        {
            
    // $this->addRule('element', 'rule_type', 'error message' );
            // I'll probably also need the ability to add options to a validator at some point.
            
            
    $this->addRule('first_name''required''Please enter your first name'); 
            
    $this->addRule('last_name''required''Please enter your last name'); 
        }
    }

    Class 
    NameForm extends HTML_QuickForm
    {
        function 
    NameForm($name$method)
        {
            
    $this->addElement('text''first_name''First Name: ');
            
    $this->addElement('text''last_name''Last Name: ');
            
    parent::HTML_QuickForm($name$method);
        }

    Next I want to be able to display said form without any validation, or with client side validation, or with errors displayed in their proper place after an improper submit:

    PHP Code:
    // Printing the form without validation:

    $form =& new NameForm('formname''post');
    print 
    $form->display(new DefaultRenderer());

    // OR printing the form with client-side javascript included

    $form =& new NameForm('formname''post');
    print 
    $form->display(new DefaultRenderer(), new NameValidator());

    // OR Validate and print with errors

    $validator =& new NameValidator();

    if(
    $validator->validate($_POST))
    {
        
    // The Form is ok do whatever I need to do
    }
    else
    {
        
    $form = new NameForm('formname''post');
        
    $form->setDefaults($_POST);
        print 
    $form->display(new DefaultRenderer(), $validator);                                                              

    My main stumbling block right now is like Beetle's, where I'm struggling with how to get my validator to interact with my renderer. My display method for the form looks something like:

    PHP Code:
    class HTML_QuickForm extends HTML_Common {

        function 
    display($renderer$validator)
        {
            
    $html '';
            
    // display the start of the form

            
    foreach (array_keys($this->_elements) as $key) {
                
    $element =& $this->_elements[$key];
                
    $isValid $validator->isValid($element->getName());
                
    $html .= $renderer->renderElement($element$isValid);
            }

            
    $html .= $validator->getJavascript();

            
    //display the end of the form

            
    return $html;
        }


    My main problem with the current set up is in handling the error messages. I want some flexibility in how they're renderered (maybe next to the element's label, or possibly in a box at the top of the form), but I'm not sure how I should have the renderer and validator interact.

    Been, I can also definitely see where the problem you've mentioned with mutliple languages can crop up here as the error message is directly tied to the validator rule.

    TIA for any suggestions or help.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •