Jump to content
MakeWebGames

Feedback: Validator class


john.

Recommended Posts

Built a Validator class and would like some feedback and ideas on how to improve the current code; Too cool for comments :cool:

<?php

class Validator {

   protected $messages;
   protected $callbacks;
   protected $errors;

   public function __construct(array $messages = array()) {
       $this->messages = array(
           'required' => '%s required',
           'email' => '%s invalid',
       );

       if (!empty($messages)) {
           $this->messages = array_merge($this->messages, $messages);
       }
   }

   public function registerCallback($rule, $callback) {
       if (!is_callable($callback, true)) {
           throw new InvalidArgumentException(
           sprintf('Invalid callback: %s.', print_r($callback, true)));
       }

       $this->callbacks[$rule] = $callback;
   }

   public function validate(array $fields, array $data) {
       foreach ($fields as $field) {
           $name = $field['name'];

           $rules = $this->parseRules($field['rules']);            
           $value = $this->getValue($name, $data);

           if (in_array('required', $rules) === false && empty($value)) {
               continue;
           }

           foreach ($rules as $rule => $params) {
               $result = $this->execute($rule, $params, $value);

               if ($result === false) {
                   $this->setError($field, $rule);
               }
           }
       }

       return (count($this->errors) === 0);
   }

   public function hasErrors() {
       return (count($this->errors) !== 0);
   }

   public function getErrors($rule = null) {

       if ($rule === null) {
           return call_user_func_array('array_merge', $this->errors);
       }

       return $this->errors[$rule];
   }

   public function setMessage($rule, $message) {
       $this->messages[$rule] = $message;
   }

   protected function execute($rule, array $params, $value) {
       if (isset($this->callbacks[$rule])) {
           $callback = $this->callbacks[$rule];

           return call_user_func_array($callback, array($value, $params));
       }

       if (method_exists($this, $rule)) {
           return call_user_func_array(array($this, $rule), array($value, $params));
       }

       throw new BadMethodCallException();
   }

   protected function parseRules($rules) {
       $matches = array();

       $rules = explode('|', $rules);
       $parsedRules = array();

       foreach ($rules as $rule) {
           if (preg_match('/(.*?)\[(.*)\]/', $rule, $matches)) {
               $params = explode(',', $matches[2]);
               $parsedRules[$matches[1]] = $params;
           } else {
               $parsedRules[$rule] = array();
           }
       }

       return $parsedRules;
   }

   protected function getValue($name, $data) {
       return (isset($data[$name]) ? $data[$name] : null);
   }

   protected function setError(array $field, $rule) {
       $message = sprintf($this->messages[$rule], $field['name']);

       if (isset($field['label'])) {
           $message = sprintf($this->messages[$rule], $field['label']);
       }

       $this->errors[$rule][] = $message;   
   }

   protected function required($value) {
       if (is_null($value) || $value == '') {
           return false;
       }

       return true;
   }

   protected function email($value) {
       return (filter_var($value, FILTER_VALIDATE_EMAIL) !== false);
   }
}

 

Problem: I need to figure out how to let the parameters be added to the error messages, like this: '%s must be between %d and %d'... uh.

Use:

 

$validator = new Validator();

$fields = array(
   array(
       'name' => 'email',
       'rules' => 'email|required', //Currently only rules implemented.
       'label' => 'E-mail',
   ),
);

$validator->validate($fields, $_POST);

if ($validator->hasErrors()) {
var_dump($validator->getErrors()); //Returns a flattened array with all errors...
}

 

It's extendable, for example you could register your own rule callbacks that has precedence before the defined methods, allowing you to override currently existing rules as well.

So yeah, any comments.

Link to comment
Share on other sites

In 2 months time, you'll want to be less cool. Add comments :D

Comments will come, just prototyping at the moment, and stripped all non-appropriate comments out from the source :3 But on a more serious note, won't be doing so much more with this, it taking to long and I am better of using some library available on Packagist instead.

Link to comment
Share on other sites

I Think its a good beginn , but i see there some problem, you put the email validation and required into validator class itselfs, well there are pretty more stuffs to validate not just email and if you wish to extend the validator you need to add even more methods and someday your validator class will be too big.

Instead i recommend to take a look at Symfony Validator, the validator has a validator collection class which accepts validation interfaces and each implementation have just one small method to validate a value.

this looks then like this

https://github.com/Opentribes/Core/blob/develop/silex/Validator/Registration.php#L27

also your current validator just got the inputs from $_POST which means youre not able to validate stuffs like, is unique username.

therefore i personally prepare a simple object which takes some values from the database and the form and validate this object.

@sniko instead of adding comments, write your code like a text and easy to understand, comments rot while code not

just my 2ct ;)

Link to comment
Share on other sites

Valid points. I did write a version before that had more a object-oriented approach, where you defined Rule classes that had a validate() method that could be instantiated through a factory injected in the Validator, but the problem is that I needed it to be lightweight. I want a single-file drop in my current code setup to just quickly validate. I know per basis that many classes doesn't mean it's not lightweight but in all the goal is a one-filer.

My other was to only have the Validator using callbacks instead of defined method, an extra dependency to inject when creating an instance, but would let the Validator have less responsibility.

The problem with unique validation was that I did not want any specific database dependency on the Validator, instead you would write your own custom rule (a callback) that uses dependencies to perform these kind of validations.

A callback like that would look like this:

 


$validator->registerCallback('unique', function ($value, $params) use ($db) {

   //params[0] should contain name of table
   //params[1] would contain the column in the table...
   //

   $stmt = $db....


   return (bool) $stmt->fetch();

});

 

But yeah, probably need a rewrite or two before I get this Validator the way I want, I could post the Rule one too if you like.

The only problem right now is the additional parameters, like "Field %s has to be %d long", (%d) not sure how I would store the other values (parameter values) in the error statement...

Edited by john.
Link to comment
Share on other sites

about drop one file in your project, thats why stuffs like composer are invented, you just dont care how many files a special approach contains, you just setup in your composer.json a library you require with a specific version and just install it. after it you dont need a factory, just call the namespace/class and the autoload basicly load the class.

and smaller classes have the advantage to replace them quickly.

I would suggest you to take a look at the SOLID prinziple, yes you have one file, but one file, means that one class has many Responsiblities, which again means if you change one part of that class, you have to change in worst case many parts in your code. (for example you dicide later to use an array for rules instead of explode a string by | as modifier or you change the modifier to , instead of | )

Link to comment
Share on other sites

why?

$validator = new Validator\Collection();
$validator->addRule('email',new Validator\Rule\Email());
$validator->addRule('email',new Validator\Rule\NotEmpty());
$validator->addRule('username',new Validator\Rule\MaximumLength(8));
$validationObject = new stdClass();
$validationObject->username = 'test';
$validationObject->email = '[email protected]';
$validator->validate($validationObject);
if(!$validatior->isValid()){
var_dump($validator->getMessages());
}

no factory, just handled by autoloader and still readable

Edited by BlackScorp
Link to comment
Share on other sites

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.   Paste as plain text instead

  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...