1
\$\begingroup\$

Have I followed best practices for PHP development, or can this class be improved?

GitHub

<?php namespace azi;
/**
 * Class Validator
 *
 * @package azi
 * @author  Azi Baloch <http://www.azibaloch.com>
 * @version 1.0
 * @license The MIT License (MIT)
 *
 */
class Validator {
    /**
     * RegExp patterns
     *
     * @var array
     */
    private $expressions = [ ];
    /**
     * Custom RegExp error messages
     *
     * @var array
     */
    private $error_messages = [ ];
    /**
     * holds validation errors
     *
     * @var array
     */
    private $validation_errors = [ ];
    /**
     * @var array
     */
    private static $errors = [ ];
    /**
     * @var null
     */
    private static $instance = null;
    /**
     * @var string
     */
    private static $session_data_key = "form_validation_errors";
    /**
     * @var array
     */
    private $builtin_rules = [];
    /**
     *  Class Constructor
     */
    public function __construct() {
        // load built-in expressions
        $this->expressions = array(
            'alpha'     => '#^([a-zA-Z\s])+$#',
            'num'       => '#^([0-9])+$#',
            'alpha-num' => '#^([a-zA-Z0-9\s])$#',
        );
        $this->builtin_rules = array(
            array(
                'id' => 'email',
                'exp' => '#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#',
                'message' => 'Please enter a valid email address'
            )
        );
        foreach($this->builtin_rules as $rule) {
            $rule = (object) $rule;
            $this->registerExpression($rule->id, $rule->exp, $rule->message);
        }
        static::$instance = $this;
    }
    /**
     * @param $key
     * @param $expression
     * @param null $message
     *
     * @return bool
     * @throws \Exception
     */
    public function registerExpression( $key, $expression, $message = null ) {
        if ( ! isset( $this->expressions[ $key ] ) ) {
            $this->expressions[ $key ] = $expression;
            if ( $message ) {
                $this->error_messages[ $key ] = $message;
            }
            return true;
        }
        throw new \Exception( "Expression key already exists" );
    }
    /**
     * @param $key
     * @param $newExpression
     * @param null $message
     *
     * @return bool
     * @throws \Exception
     */
    public function updateExpression( $key, $newExpression, $message = null ) {
        if ( $this->expressions[ $key ] ) {
            $this->expressions[ $key ] = $newExpression;
            if ( $message ) {
                $this->error_messages[ $key ] = $message;
            }
            return true;
        }
        throw new \Exception( "Expression dose not exists" );
    }
    /**
     * @return bool
     */
    public function passed() {
        if ( count( $this->validation_errors ) < 1 ) {
            return true;
        }
        return false;
    }
    /**
     * retrieve error messages after validation
     *
     * @return array
     */
    public function getErrors() {
        return $this->validation_errors;
    }
    /**
     * Get error message of a field
     *
     * @param $fieldKey
     *
     * @return mixed
     */
    public static function error( $fieldKey ) {
        if(!session_id()) {
            session_start();
        }
        if(isset($_SESSION[static::$session_data_key])) {
            if(count($_SESSION[static::$session_data_key]) > 0) {
                self::$errors = $_SESSION[ static::$session_data_key ];
                unset($_SESSION[static::$session_data_key]);
            }
        } else {
            if(!is_null(static::$instance)) {
                self::$errors = static::$instance->validation_errors;
            }
        }
        if ( isset( self::$errors[ $fieldKey ][ 'message' ] ) ) {
            return self::$errors[ $fieldKey ][ 'message' ];
        }
        return false;
    }
    /**
     * @param array $fields the array of form fields - ( $_POST , $_GET, $_REQUEST )
     * @param $rules
     *
     * @return Validator $this
     */
    public function validate( $fields, $rules ) {
        $return = [ ];
        foreach ( $fields as $key => $field ) {
            if ( ! array_key_exists( $key, $rules ) ) {
                continue;
            }
            $r       = $rules[ $key ];
            $matches = [ ];
            $not     = false;
            if ( preg_match( '#if:#', $r ) ) {
                preg_match( "#if:(.*)\\[\\!(.*)\\]:(.*)\\[!(.*)\\]\\((.*)\\)#", $r, $matches );
                if ( count( $matches ) < 1 ) {
                    preg_match( "#if:(.*)\\[(.*)\\]\\((.*)\\)#", $r, $matches );
                } else {
                    $not = true;
                }
                $r = end( $matches );
            }
            if ( ! strpos( $r, "|" ) ) {
                $r .= "|IGNORE_ME5";
            }
            $theRules = explode( "|", $r );
            foreach ( $theRules as $theRule ) {
                if ( $theRule == "IGNORE_ME5" ) {
                    continue;
                }
                $customMessage = [ ];
                if ( strpos( $theRule, "--" ) ) {
                    $rcm     = explode( "--", $theRule ); // custom message for current rule
                    $theRule = $rcm[ 0 ];
                    if ( strpos( $rcm[ 0 ], ":" ) ) {
                        $rcm[ 0 ] = explode( ":", $rcm[ 0 ] )[ 0 ];
                    }
                    $customMessage[ $rcm[ 0 ] ] = $rcm[ 1 ];
                }
                if ( count( $matches ) > 0 ) {
                    if ( $not ) {
                        if ( $fields[ $matches[ 1 ] ] == $matches[ 2 ] && $fields[ $matches[ 3 ] ] == $matches[ 4 ] ) {
                            continue;
                        }
                    } else {
                        if ( $fields[ $matches[ 1 ] ] != $matches[ 2 ] ) {
                            continue;
                        }
                    }
                }
                if ( strtolower( $theRule ) == "required" ) {
                    if ( ! empty( $customMessage[ 'required' ] ) ) {
                        $theMessage = $customMessage[ 'required' ];
                    } else {
                        $theMessage = $this->keyToLabel( $key ) . ' is required';
                    }
                    if ( $field == "" ) {
                        $return[ $key ] = [
                            'error'   => 'required',
                            'message' => $theMessage
                        ];
                        continue;
                    }
                }
                if ( strtolower( $theRule ) == "alpha" ) {
                    if ( ! preg_match( $this->expressions[ 'alpha' ], $field ) ) {
                        if ( ! empty( $customMessage[ 'alpha' ] ) ) {
                            $theMessage = $customMessage[ 'alpha' ];
                        } else {
                            $theMessage = $this->keyToLabel( $key ) . ' must not contain numbers and special characters';
                        }
                        $return[ $key ] = [
                            'error'   => 'alpha',
                            'message' => $theMessage
                        ];
                        continue;
                    }
                }
                if ( strtolower( $theRule ) == "num" ) {
                    if ( ! empty( $customMessage[ 'num' ] ) ) {
                        $theMessage = $customMessage[ 'num' ];
                    } else {
                        $theMessage = $this->keyToLabel( $key ) . ' may only contain numbers';
                    }
                    if ( ! preg_match( $this->expressions[ 'num' ], $field ) ) {
                        $return[ $key ] = [
                            'error'   => 'num',
                            'message' => $theMessage
                        ];
                        continue;
                    }
                }
                if ( strtolower( $theRule ) == "alpha-num" ) {
                    if ( ! empty( $customMessage[ 'alpha-num' ] ) ) {
                        $theMessage = $customMessage[ 'alpha-num' ];
                    } else {
                        $theMessage = $this->keyToLabel( $key ) . ' may only contain alpha numeric characters';
                    }
                    if ( ! preg_match( $this->expressions[ 'alpha-num' ], $field ) ) {
                        $return[ $key ] = [
                            'error'   => 'alpha-num',
                            'message' => $theMessage
                        ];
                        continue;
                    }
                }
                if ( strpos( $theRule, ':' ) ) {
                    $theRule = explode( ":", $theRule );
                    $length = $theRule[1];
                    $theRule = $theRule[0];
                    if ( strtolower( $theRule ) == "min" ) {
                        if ( ! empty( $customMessage[ 'min' ] ) ) {
                            $theMessage = $customMessage[ 'min' ];
                        } else {
                            $theMessage = $this->keyToLabel( $key ) . ' must be at least ' . $length . " characters long";
                        }
                        if ( strlen( $field ) < $length ) {
                            $return[ $key ] = [
                                'error'   => 'min',
                                'message' => $theMessage
                            ];
                            continue;
                        }
                    }
                    if ( strtolower( $theRule ) == "max" ) {
                        if ( ! empty( $customMessage[ 'max' ] ) ) {
                            $theMessage = $customMessage[ 'max' ];
                        } else {
                            $theMessage = $this->keyToLabel( $key ) . ' must be less than ' . $length . " characters";
                        }
                        if ( strlen( $field ) > $length ) {
                            $return[ $key ] = [
                                'error'   => 'max',
                                'message' => $theMessage
                            ];
                            continue;
                        }
                    }
                }
                /* Custom Expressions */
                if ( array_key_exists( $theRule, $this->expressions ) ) {
                    if ( ! preg_match( $this->expressions[ $theRule ], $field ) ) {
                        if(isset($customMessage[$theRule])) {
                            $error_message = $customMessage[$theRule];
                        } else if ( array_key_exists( $theRule, $this->error_messages ) ) {
                            $error_message = $this->error_messages[ $theRule ];
                        } else {
                            $error_message = $this->keyToLabel( $key ) . ' dose\'t match the required pattern';
                        }
                        $return[ $key ] = [
                            'error'   => $theRule,
                            'message' => $error_message
                        ];
                        continue;
                    }
                }
            }
        }
        $this->validation_errors = $return;
        return $this;
    }
    /**
     * Convert an array key to Label eg. full_name to Full Name
     *
     * @param $key
     *
     * @return string
     */
    private function keyToLabel( $key ) {
        return ucwords( str_replace( [ '-', '_', '+' ], " ", $key ) );
    }
    public function goBackWithErrors(){
        if(!session_id()) {
            session_start();
        }
        $_SESSION[static::$session_data_key] = $this->validation_errors;
        header('Location: ' . $_SERVER['HTTP_REFERER']);
        exit;
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ The validate functions is WAY too hard to read. I would create more methods, with that do just one thing "Single Responsibility Principle". Work in making your code more readable. \$\endgroup\$ – Enrique Moreno Tent Mar 27 '15 at 18:35
2
\$\begingroup\$

Look at Symfony 2 Validation and you will find many useful things.

About the code split validate method to small prices: it should not take more than one screen.

Use protected instead of private if you are not 100% sure it must be private.

In your case, $session_data_key must be constant.

If you are providing a library, it must be unit tested and when you will start do it you will find many other architectural problems.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.