SitePoint Sponsor

User Tag List

Results 1 to 9 of 9

Hybrid View

  1. #1
    SitePoint Zealot
    Join Date
    Oct 2003
    Location
    Denmark
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    How to do the validation

    Often I construct a class something like this:

    PHP Code:

    require_once 'Validate.php'// PEARs validation class

    class Contact {
      
      private 
    $db;
      private 
    $error = array();

      public function 
    __construct($db) {
         
    $this->db $db;
      }

      static function 
    validate($var) {
         
    // seems as if the class gets very dependent on Validate
         // and that Validate.php is included even often not needed.
         // of course I could move the require_once to here, but
         // that doesn't seem right either?

         
    if (!Validate::uri($var['uri'])) {
            
    $this->error['uri'] = 'error in uri';
         }
         if (empty(
    $var['name'])) {
            
    $this->error['name'] = 'name cannot be empty';
         }

         if (
    sizeof($this->error) > 0) {
            return 
    false;
         }
         return 
    true;
      }

      public function 
    save($var) {
         if (!
    $this->validate($var)) {
            return 
    false;
         }
         
    // database logic

         
    if (PEAR::isError($result)) {
            return 
    false;
         }
         return 
    true;
      }


    I would have some test to test the whole thing.

    PHP Code:

    class ContactTestCase extends UnitTestCase {

       function 
    testValidation() {
          
    $this->assertFalse(Contact::validate(array('name'=>'')));
          
    $this->assertFalse(Contact::validate(array('uri'=>'http://s')));
          
    // and so on
       
    }

       
    // similar tests for the save mechanism


    But when I wrote up the tests I just realised, that it seems my class is doing to much. However, I like to keep the validation close to the actual class, so it knows excactly what has to be validated before saving any thing, as most of my classes are used both from a web application, but also as a webservice through xmlrpc.

    How do you go about structuring your classes and the validation before saving anything?

  2. #2
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I definitely think you can use a strategy pattern here.

    PHP Code:
    $validator = new FormValidator($_POST);
    $validator->attach(new NonEmptyValidator(), "your_field""You must fill in this field");
    $validator->attach(new DDMMYYYYValidator(), "date_field""Please check the format of the date entered");
    $validator->attach(new EmailValidator(), "email_field""The email address entered does not appear to be valid");
    if (
    $validator->run(new DefaultView()))
    {
        
    //go to next stage


  3. #3
    SitePoint Zealot
    Join Date
    Oct 2003
    Location
    Denmark
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by d11wtq View Post
    I definitely think you can use a strategy pattern here.
    Ok, sounds resonable. How would you apply it. Would you simply wrap the logic in a ContactValidator, so I can reuse the validation from several pages? And the DefaultView() you throw into the validation, how will that come into play?

  4. #4
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Don't forget about the Specification pattern either though,

    PHP Code:
    include_once( 'interfaces/specification.php' );
        
        
    /**
         * @since        Feb 2005
         * @author        les quinn (nukebaghdad@yahoo.co.uk)
         * @version        0.1
         * @abstract
         */
         
        
    abstract class Specification implements ISpecification {
            abstract public function 
    isSatisfiedByIDataspace $dataspace$logger );
            
            public function 
    orSpecificationISpecification $specification ) {
                return new 
    OrSpecification$this$specification );
            }
            
            public function 
    andSpecificationISpecification $specification ) {
                return new 
    AndSpecification$this$specification );
            }
        }
        
        final class 
    OrSpecification extends Specification {
            protected 
    $or_specification null;
            protected 
    $specification null;
            
            public function 
    __constructISpecification $specificationISpecification $or_specification ) {
                
    $this -> or_specification $or_specification;
                
    $this -> specification $specification;
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                return ( 
    $this -> specification -> isSatisfiedBy$dataspace$logger ) || $this -> or_specification -> isSatisfiedBy$dataspace$logger ) );
            }
        }
        
        final class 
    AndSpecification extends Specification {
            protected 
    $and_specification null;
            protected 
    $specification null;
            
            public function 
    __constructISpecification $specificationISpecification $and_specification ) {
                
    $this -> and_specification $and_specification;
                
    $this -> specification $specification;
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                return ( 
    $this -> specification -> isSatisfiedBy$dataspace$logger ) && $this -> and_specification -> isSatisfiedBy$dataspace$logger ) );
            }
        } 
    Here are some rules,

    PHP Code:
    final class ExpectedArrayRule extends Specification {
            protected 
    $field_name;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                if( 
    is_array$dataspace -> get$this -> field_name ) ) && count$dataspace -> get$this -> field_name ) ) ) { 
                    return 
    true;
                }
                
    $logger -> set$this -> field_name$this -> error_message );
                return 
    false;
            }
        }

    final class 
    RegExpressionRule extends Specification {
            protected 
    $field_name;
            protected 
    $reg_expression;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> reg_expression array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                if( !
    preg_match$this -> reg_expression$dataspace -> get$this -> field_name ) ) ) {
                    
    $logger -> set$this -> field_name$this -> error_message );
                    return 
    false;
                } else {
                    return 
    true;
                }
            }
        }

    final class 
    MinimumSizeRule extends Specification {
            protected 
    $field_name;
            protected 
    $length 0;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> length array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                if( 
    strlen$dataspace -> get$this -> field_name ) ) < $this -> length ) {
                    
    $logger -> set$this -> field_name$this -> error_message );
                    return 
    false;
                }
                return 
    true;
            }
        }
        
        final class 
    MaximumSizeRule extends Specification {
            protected 
    $field_name;
            protected 
    $length 0;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> length array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                return 
    false;
            }
        }
        
        final class 
    BetweenSizeRule extends Specification {
            protected 
    $field_name;
            protected 
    $minimum 0;
            protected 
    $maximum 0;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> minimum array_shift$parameters );
                
    $this -> maximum array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                return 
    false;
            }
        }

    final class 
    RequiredRule extends Specification {
            protected 
    $field_name;
            protected 
    $error_message '';
            
            public function 
    __construct() {
                
    $parameters func_get_args();
                
    $this -> field_name array_shift$parameters );
                
    $this -> error_message array_shift$parameters );
            }
            
            public function 
    isSatisfiedByIDataspace $dataspace$logger ) {
                
    $value $dataspace -> get$this -> field_name );
                if( empty( 
    $value ) || !isset( $value ) ) {
                    
    $logger -> set$this -> field_name$this -> error_message );
                    return 
    false;
                } else {
                    return 
    true;
                }
            }
        } 
    I use the Specification in this manner,

    PHP Code:
    // form handler abstraction
    abstract class FormActionHandler implements IActionHandler {
            protected 
    $id;
            protected 
    $rules = array();
            protected 
    $children = array();
            protected 
    $successor null;
            
            public function 
    __construct() {}
            public function 
    getId() {
                return 
    $this -> id;
            }
            
            public function 
    hasChildren() {
                return 
    count$this -> children );
            }
            
            public function 
    getChildren() {
                return 
    $this -> children;
            }
            
            public function 
    attachIComposite $composite ) {
                
    $this -> children[$composite -> getId()] = $composite;
            }
            
            public function 
    setHandlerIActionHandler $successor ) {
                
    $this -> successor $successor;
            }
            
            public function 
    addRuleISpecification $rule ) {
                
    $this -> rules[] = $rule;
            }
            
            protected function 
    validateIDataspace $request$logger ) {
                
    $validation true;
                foreach( 
    $this -> rules as $rule ) {
                    
    $validation $rule -> isSatisfiedBy$request$logger ) && $validation;
                }
                return 
    $validation;
            }
            
            abstract public function 
    executeIDataspace $nodesContext $context );
        } 
    PHP Code:
    final class BodyActionHandler extends FormActionHandler {
            public function 
    __construct() {
                
    $this -> initialise();
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodesContext $context ) {
                if( 
    $this -> validate$request $context -> get'request' ), $context -> get'logger' ) ) ) {
                    
    $this -> successor -> execute$nodes$context );
                } else {
                    
    $page = new WebPage$nodes$request );
                    
    $page -> render$request -> language().'/admin/login/body.tpl' );
                }
            }
            
            protected function 
    initialise() {
                
    $this -> setHandler( new SuccessActionHandler() );
                
    $this -> addRule( new PostRequestRule() );    
            }
        }
        
        final class 
    SuccessActionHandler extends FormActionHandler {
            public function 
    __construct() {
                
    $this -> initialise();
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodesContext $context ) {
                if( !
    $this -> validate$request $context -> get'request' ), $context -> get'logger' ) ) ) {
                    
    $this -> successor -> execute$nodes$context );
                } else {
                    
    $request -> get'session' ) -> set'unique'null );
                    
                    
    $username $request -> get'email' );
                    
    $password $request -> get'password' );
                    
                    
    $user = new UserRecord$context -> get'connection' ), new UserStatement() );
                    if( 
    $user -> authenticate$username$passwordtrue ) ) {
                        
    $session $request -> get'session' );
                        
                        
    $session -> set'id'$user -> id );
                        
    $session -> set'username'$user -> email );
                        
    $session -> set'password'md5$password ) );
                        
    // $user -> acknowledge( time() );
                        
                        
    header'location:/admin/' );
                        exit();
                    }
                    
                    
    header'location:/admin/logout/' );
                    exit();
                }
            }
            
            protected function 
    initialise() {
                
    $this -> setHandler( new FailureActionHandler() );
                
    $this -> addRule( new UniqueTokenRule'unique''Suspected form tampering.' ) );
                
    $this -> addRule( new RequiredRule'email''Email form field is required.' ) );
                
    $this -> addRule( new RequiredRule'password''Password form field is required.' ) );
            }
        }
        
        final class 
    FailureActionHandler extends ActionHandler {
            public function 
    __construct() {
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodesContext $context ) {
                
    $page = new WebPage$nodes$request $context -> get'request' ) );
                
    $page -> render$request -> language().'/admin/login/body.tpl' );
            }
        } 
    The idataspace type is just a container with accessors so nothing to chock on

  5. #5
    SitePoint Zealot
    Join Date
    Oct 2003
    Location
    Denmark
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Dr. Livingston --> It seems as if the strategy pattern is a better fit here? I would stress that what I am trying to do is not a FormValidator. It is a Validator which validates the content before I save it regardsless of where I get the information. I will both get information through web forms but also through xmlrpc.

    And I am still not clear on how to apply the strategy pattern in my method. Would it be something like the following:

    PHP Code:
    function validate($validator$var) {
      
    $validator $validator->addRule(new EmailValidator(), $var['email'], 'email''e-mail not valid');
      if (!
    validator->run()) {
         return 
    false;
      }
      return 
    true;

    But I am not sure how to utilize the viewer. Could you try to elaborate on this point.

  6. #6
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Livingston's example had the data processing and validation parts intertwined, which I dislike, but if we disregard the composite tree stuff involved it was a solid example nonetheless.
    Quote Originally Posted by lsolesen View Post
    Dr. Livingston --> It seems as if the strategy pattern is a better fit here?
    Specification also involves Strategy, applied the way Livingston demonstrates. Actually, d11wtq's proposal is a Specification as well. It's a more inflexible solution, though.
    I would stress that what I am trying to do is not a FormValidator. It is a Validator which validates the content before I save it regardsless of where I get the information. I will both get information through web forms but also through xmlrpc.
    I hope you'll understand how the basis of what Livingston does is perfectly applicable to any validation situation by looking at another variant with slightly different names:

    PHP Code:
    interface Rule
    {
        public function 
    isValid($data);
        public function 
    getErrors();
    }

    class 
    Validator implements Rule
    {
        protected 
    $rules = array();

        public function 
    add(Rule $rule)
        {
            
    $this->rules[] = $rule;
            return 
    $this;
        }

        public function 
    isValid($data)
        {
            
    $valid true;
            foreach (
    $this->rules as $rule) {
                
    $valid $rule->isValid($data) && $valid;
            }
            return 
    $valid;
        }
        
        public function 
    getErrors()
        {
            
    $retval = array();
            foreach (
    $this->rules as $rule) {
                foreach (
    $rule->getErrors() as $field => $errors) {
                    if (isset(
    $retval[$field])) {
                        foreach (
    $errors as $e) {
                            
    $retval[$field][] = $e;
                        }
                    } else {
                        
    $retval[$field] = $errors;
                    }
                }
            }
            return 
    $retval;
        }
    }

    abstract class 
    BaseRule implements Rule
    {
        public function 
    isValid($data)
        {
            
    $this->errors = array();
            
    $valid $this->apply($data);
            return 
    $valid;
        }

        protected function 
    error($field$error)
        {
            if (!isset(
    $this->errors[$field])) {
                
    $this->errors[$field] = array();
            }
            
    $this->errors[$field][] = $error;
            return 
    $this;
        }

        abstract protected function 
    apply($data);

    PHP Code:
    class NotEmptyRule extends BaseRule
    {
        public function 
    __construct($field$error '%s was empty')
        {
            
    $this->field $field;
            
    $this->error $error;
        }

        protected function 
    apply($data)
        {
            if (empty(
    $data[$this->field]))
            {
                
    $this->error($this->fieldsprintf($this->error$this->field));
                return 
    false;
            }
            return 
    true;
        }

    PHP Code:
    $validator = new Validator;
    $validator->add(new NotEmptyRule('foo'))->add(new NotEmptyRule('bar'));
    $validator->isValid(array ('foo' => 'not empty''bar' => '')); 
    Do note how Validator is merely an AND-composite of an arbitrary amount of rules: it's in fact quite similar to the AndSpecification and could trivially be transformed into an OR-version.

    Specification is a very versatile and useful pattern. Don't overlook it.

  7. #7
    SitePoint Zealot
    Join Date
    Oct 2003
    Location
    Denmark
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ezku -> thank you. Now I finally got around to implementing validation in a smarter way. And your post really cleared up things for me.

    I used something similar to your base classes, and made the following class which holds the specific rules for a class:

    PHP Code:
    class SkipCheckIn_RequestValidator {
        private 
    $validator;

        public function 
    __construct($validator) {
            
    $this->validator $validator;
            
    $this->setupRules();
        }

        private function 
    setupRules() {
            
    $this->validator->add(new NotEmptyRule('passengers'));
            
    $this->validator->add(new IsNumericRule('passengers'));
        }

        public function 
    isValid($request) {
            return 
    $this->validator->isValid(array('passengers' => $request->passengers));
        }

        public function 
    getErrors() {
            return 
    $this->validator->getErrors();
        }

    That way I can easily reuse it any way I see fit in the application - for instance:

    PHP Code:
    $request = new SkipCheckIn_Request();
    $request->passengers 2;

    $request_validator = new SkipCheckIn_RequestValidator(new Validator);
    if (
    $request_validator->isValid($request)) {
        if (
    $request->save()) { 
            
    header('Location: show.php?id='.$request->getId());
            exit;
        }
        else {
            die(
    'not good, not good at all :(');
        }
    }

    show_form(); 
    I hope that is somewhat correct. Now I am struggling a bit on how to test the validator with one rule at a time (it is easy now when I have only applied one rule, but the specific validator will soon hold more rules). I guess it would take a small rewrite of the isValid-method:

    PHP Code:
        public function isValid($request$test = array(array('passengers' => $request->passengers))) {
            return 
    $this->validator->isValid($test);
        } 
    Sounds reasonable, or have I misunderstood anything?

  8. #8
    SitePoint Zealot
    Join Date
    Oct 2003
    Location
    Denmark
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku View Post
    Do note how Validator is merely an AND-composite of an arbitrary amount of rules: it's in fact quite similar to the AndSpecification and could trivially be transformed into an OR-version.

    Specification is a very versatile and useful pattern. Don't overlook it.
    I have grown fond of the specification for validation. However, I cannot seem to grasp two things:

    - How do you make an OR specification (you say it i trivial, but I cannot get my head around it?)
    - How do you get the error msg to get right in the OR specification?

    So far I got the following:

    http://svn.intraface.dk/intrafacepub...rty/Validator/

    With all my classes gathered in the Validator. And here is the api:

    http://public.intraface.dk/docs/Validator/

    Please educate me

  9. #9
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    With logical AND / OR you do not treat the validating rule as a separate rule, but what you do is to place the constraint of that validating rule on another rule, so in effect you have one or more validating rules to be treated as a whole... Ie One rule therefore?

    So, the validating rule in question is ANDed / ORed with another rule, and not the validator it's self. Not got the time to look over your implementation so I can't tell if this is what your doing already?


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
  •