Jump to content

Form/validation classes (hypothetical designs)


KevinM1

Recommended Posts

It's been a while since I've done any coding, since my PC has been broken for a few weeks.  One of the things I have been thinking of is a way to implement forms in an OOP manner.  It's been one of the things that's continued to bug me since I tried that experimental "Procedural to OOP" thread months back.

 

I'm thinking that two classes are in order.  A generic form class, which houses an array of form input objects and basically sticks the opening and closing form tags in the right place.  The form inputs should all derive from the same superclass.  One hangup: textareas are a bit different than most inputs, if memory serves.  So, at first blush (and this is off the top of my head), something like:

 

<?php
   class Form{
      private inputs;

      public addInput(FormInput $input, $key){
         $this->inputs[$key] = $input;
      }

      public removeInput($key){
         if(!array_key_exists($key, $this->inputs){
            echo "Cannot find form input with an ID of $key.";  //I know, I know...I should subclass Exception here
         } else{
            array_splice($this->inputs, $key, 1);
         }
      }

      public printForm(){
         $output = "<form>";
         
         foreach($this->inputs as $input){
            $output .= $input->printInput();
         }

         $output .= "</form>";
         echo $output;
      }
   }

   abstract class FormInput{
      protected $type;
      protected $name;
      protected $value;
      protected $size;
      protected $maxlength;
      protected $checked;
      protected $src;

      protected $id; //for those who like using CSS id's or JavaScript

      public __construct($type, $name, $value, $id=NULL, $size=NULL, $maxlength=NULL, $checked=NULL, $src=NULL){
         $this->type = $type;
         $this->name = $name;
         $this->value = $value;
         $this->id = $id;
         $this->size = $size;
         $this->maxlength = $maxlength;
         $this->checked = $checked;
         $this->src = $src;
      }

      public printInput(){
         $output = "<input type='$type' name='$name' value='$value' id='$id' size='$size' maxlength='$maxlength' checked='$checked' src='$src' />";
         return $output;
      }
   }

   class TextInput extends FormInput{}
   class PasswordInput extends FormInput{}
?>

 

Looking at it, I think it could work.  One thing that immediately springs to mind is that I probably shouldn't put all required attributes in the FormInput class, as some of that stuff doesn't belong in certain inputs (for instance, a text input won't ever be 'checked').  A better way would probably be to put the bare minimum in the superclass - type, name, value, and id - and separate the rest.  I'm unsure how deep to go with inheritence, though.  The easiest, most flexible way to do it would probably be:

 

<?php
   abstract class FormInput{
      protected $type;
      protected $name;
      protected $value;
      protected $id;

      public __construct($type, $name, $value, $id=NULL){
         $this->type = $type;
         $this->name = $name;
         $this->value = $value;
         $this->id = $id;
      }
   }

   class CheckboxInput extends FormInput{
      private $checked;

      public __construct($type, $name, $value, $id=NULL, $checked){
         parent::__construct($type, $name, $value, $id);
         $this->checked = $checked;
      }

      public printInput(){
         $output = "<input type='$type' name='$name' value='$value' id='$id' checked='$checked' />";
         return $output;
      }
   }
?>

 

I think I'm more or less on the right track.  The big question is whether or not something like this is actually worth implementing.  I guess the most use could come from either a built-in validator, or, since this is OOP, perhaps a set of validation Decorators.  The big Form class could have a validate() method that iterates through the inputs array, causing each decorator to fire.

 

So, thoughts?  Is this something worth fleshing out (add in textarea support, for example)?  Or is it basically best used as a thought exercise?

Link to comment
Share on other sites

Validation algorithms is a prime candidate for the Strategy pattern.

 

Ah, good point, given their variety.  I was thinking, my form idea may be better suited to a factory class that serves the right kind of form input based on their type.  Something like:

 

<?php
   class FormInputFactory{ //say that three times fast
      static public getInput($type, $name, $value=NULL, $id=NULL, ... /*the other optional parameters*/){
         switch($type){
            case 'text':
               return new TextInput($name, $value, $id);
               break;
            case 'password':
               return new PasswordInput($name, $value, $id);
               break;
            .
            .
            .
         }
      }
   }

   $textInput = FormInputFactory::getInput('text', 'username');
   $passwordInput = FormInputFactory::getInput('password', '', 'specialInput'); //empty value, but an id for those CSS/JavaScript users
?>

Link to comment
Share on other sites

I rarely find the need for a separate Factory class. Simply use a factory in the Supertype. Also, I recommend NOT to do key based factory mapping. Better (more flexible) alternatives are argument type based and/or class naming scheme based mapping.

Link to comment
Share on other sites

I rarely find the need for a separate Factory class. Simply use a factory in the Supertype. Also, I recommend NOT to do key based factory mapping. Better (more flexible) alternatives are argument type based and/or class naming scheme based mapping.

 

Ah, good point on the former.  I'm not quite seeing how a type/class name factory scheme would actually work, though.  It seems a bit recursive, as it sounds as though you're suggesting using a type to retrieve itself.  I'm sure I'm missing the point, though. :)

Link to comment
Share on other sites

Name scheme based:

 

$class = ucfirst($text).self::$suffix;

 

Name scheme and argument type based:

 

$class = get_class($arg).self::$suffix;

 

Both methods do not require explicit key mapping (the switch block), meaning you reduce coupling between context and Strategy. In this case, practically, that means you can add Strategies without changing the Context class.

Link to comment
Share on other sites

Name scheme based:

 

$class = ucfirst($text).self::$suffix;

 

Name scheme and argument type based:

 

$class = get_class($arg).self::$suffix;

 

Both methods do not require explicit key mapping (the switch block), meaning you reduce coupling between context and Strategy. In this case, practically, that means you can add Strategies without changing the Context class.

 

That syntax is really throwing me off.  I'm assuming that it's supposed to be placed within the superclass, but I'm not sure exactly how it works.  What is $suffix?  Does appending self actually append the current class name?  In other words, is the following more or less on the right track?

 

<?php
   class FormInput{ //using just the first example
      public __construct($text){
         $class = ucfirst($text).self; //leaving out $suffix because I'm not sure what it's supposed to be
         return new $class;
      }
   }

   $password = new FormInput('password'); //returns an instance of PasswordFormInput
?>

 

Or am I way off?

Link to comment
Share on other sites

Just referring to using a static class property.

 

Simplified into absurdity:

 

<?php
abstract class FormInput {

protected $name;

private static $classSuffix = 'Input';

protected function __construct($name){
	$this->name = $name;
}
public static function factory($type, $name){
	$class = ucfirst($type) . self::$classSuffix;
	return new $class($name);
}

}
abstract class FormValidator {

private static $classSuffix = 'Validator';

public function isValid(FormInput $input){
	$class = get_class($type) . self::$classSuffix;
	$validator = new $class;
	return $validator->isValid($input);
}	
}
class EmailInput extends FormInput {}
class EmailValidator extends FormValidator {}
?>

 

Link to comment
Share on other sites

Just to be clear, the $type in FormValidator's isValid() method comes from FormInput, via Strategy, correct?

 

No that was a mistake. Anyway, it's not the best example. Try this:

 

<?php
abstract class FormInput {

protected $name;
protected $value;

private static $classSuffix = 'Input';

protected function __construct($name, $value = null){
	$this->name = $name;
	$this->value = $value;
}
public static function factory($type, $name){
	$class = ucfirst($type) . self::$classSuffix;
	return new $class($name);
}
public function setValue($value){
	$this->value = $value;
}
public function getValue(){
	return $this->value;
}
}
class EmailInput extends FormInput {}

interface ValidatorStrategy {
public function isValid(FormInput $input);
}
class FormValidator {

private static $classSuffix = 'Validator';

private $strategies = array();

public function getStrategy(FormInput $input){
	$class = get_class($input) . self::$classSuffix;
	if(!isset($strategies[$class])){
		$strategies[$class] = new $class;
	}
	return $strategies[$class];
}
public function isValid(FormInput $input){
	return $this->getStrategy($input)->isValid($input);
}
}
class EmailInputValidator implements ValidatorStrategy {

const REGEX = '/^[_\.0-9a-zA-Z-]+@([0-9a-zA-Z][0-9a-zA-Z-]+\.)+[a-zA-Z]{2,6}$/';

public function isValid(FormInput $input){
	return (bool) preg_match(self::REGEX, $input->getValue());
}
}

$validator = new FormValidator();

$email = FormInput::factory('email', 'useremail');
$email->setValue('foo@foobarred.com');

var_dump($validator->isValid($email));

?>

Link to comment
Share on other sites

Just to be clear, the $type in FormValidator's isValid() method comes from FormInput, via Strategy, correct?

 

No that was a mistake. Anyway, it's not the best example. Try this:

 

<?php
abstract class FormInput {

protected $name;
protected $value;

private static $classSuffix = 'Input';

protected function __construct($name, $value = null){
	$this->name = $name;
	$this->value = $value;
}
public static function factory($type, $name){
	$class = ucfirst($type) . self::$classSuffix;
	return new $class($name);
}
public function setValue($value){
	$this->value = $value;
}
public function getValue(){
	return $this->value;
}
}
class EmailInput extends FormInput {}

interface ValidatorStrategy {
public function isValid(FormInput $input);
}
class FormValidator {

private static $classSuffix = 'Validator';

private $strategies = array();

public function getStrategy(FormInput $input){
	$class = get_class($input) . self::$classSuffix;
	if(!isset($strategies[$class])){
		$strategies[$class] = new $class;
	}
	return $strategies[$class];
}
public function isValid(FormInput $input){
	return $this->getStrategy($input)->isValid($input);
}
}
class EmailInputValidator implements ValidatorStrategy {

const REGEX = '/^[_\.0-9a-zA-Z-]+@([0-9a-zA-Z][0-9a-zA-Z-]+\.)+[a-zA-Z]{2,6}$/';

public function isValid(FormInput $input){
	return (bool) preg_match(self::REGEX, $input->getValue());
}
}

$validator = new FormValidator();

$email = FormInput::factory('email', 'useremail');
$email->setValue('foo@foobarred.com');

var_dump($validator->isValid($email));

?>

 

Awesome, thanks!  As an added bonus, I didn't realize that one could implement Strategy without having the client (in this case, FormInput) having a Strategy object as one of its properties.  I believe Zandstra's book had the quiz test and seminar pricing Strategies as parts of their clients, so I thought that was an integral part of the pattern.  I'm glad to see it's not, though.

 

EDIT: did the 'Topic Solved' button go away?

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.