KevinM1 Posted March 18, 2008 Share Posted March 18, 2008 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? Quote Link to comment Share on other sites More sharing options...
448191 Posted March 19, 2008 Share Posted March 19, 2008 Validation algorithms is a prime candidate for the Strategy pattern. Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted March 19, 2008 Author Share Posted March 19, 2008 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 ?> Quote Link to comment Share on other sites More sharing options...
448191 Posted March 19, 2008 Share Posted March 19, 2008 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. Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted March 19, 2008 Author Share Posted March 19, 2008 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. Quote Link to comment Share on other sites More sharing options...
448191 Posted March 20, 2008 Share Posted March 20, 2008 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. Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted March 20, 2008 Author Share Posted March 20, 2008 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? Quote Link to comment Share on other sites More sharing options...
448191 Posted March 20, 2008 Share Posted March 20, 2008 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 {} ?> Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted March 20, 2008 Author Share Posted March 20, 2008 Okay, I see it now. I was forgetting that the name property would be static, so the self::$classSuffix bit confused me. Just to be clear, the $type in FormValidator's isValid() method comes from FormInput, via Strategy, correct? Quote Link to comment Share on other sites More sharing options...
448191 Posted March 21, 2008 Share Posted March 21, 2008 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)); ?> Quote Link to comment Share on other sites More sharing options...
KevinM1 Posted March 21, 2008 Author Share Posted March 21, 2008 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? Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.