I recently made a very simple MVC'ish framework for my own. Now im trying to expand that framework with some classes, so i can save some time in the future. The class i am trying to make now is a form validation class. I thought it was working quite fine, untill i made a function to check for the minimum length of a string.
The class :
<?php
/**
* Form Validation Class
*/
class formValidation
{
private $errorMessages = array();
private $method;
public $errorString;
public $validationRules = array();
public function setMethod($method)
{
$this->method = $method;
}
public function setRules($rules)
{
$this->validationRules = $rules;
}
public function run()
{
if (!empty($this->method)) {
foreach ($this->method as $fieldname => $fieldvalue) {
foreach ($this->validationRules as $rule) {
if ($rule['field'] == $fieldname) {
foreach ($rule as $option => $rulevalue) {
switch ($option) {
case 'required':
if (!$this->checkRequired($fieldvalue)) {
$this->errorMessages[] = $rule['name'] . " is a required field";
}
break;
case 'letters_numbers':
if (!$this->checkLettersNumbers($fieldvalue)) {
$this->errorMessages[] = $rule['name'] . " may only contain letters and numbers";
}
case 'min_length':
if (!$this->checkMinLength($fieldvalue, $rulevalue)) {
$this->errorMessages[] = $rule['name'] . " must contain at least $rulevalue characters";
}
}
}
}
}
}
if (!empty($this->errorMessages[0])) {
return false;
} else {
return true;
}
} else {
return false;
}
}
// Checks
private function checkRequired($value) // Makes a field mandatory
{
if ($value == "") {
return false;
} else {
return true;
}
}
private function checkLettersNumbers($value) // Allow Letters and Numbers only
{
return preg_match('/^[A-Za-z\s ]+$/', $value);
}
private function checkMinLength($value, $minlength) // Check input for minimum length
{
if (strlen($value) < $minlength) {
return false;
} else {
return true;
}
}
// Error Handling Functions
private function makeErrorString($prelimiter = '<li>', $delimiter = '</li>') // Build all the errors into a string
{
if (!empty($this->errorMessages[0])) {
$errors = "";
foreach ($this->errorMessages as $message) {
$errors .= $prelimiter;
$errors .= $message;
$errors .= $delimiter;
}
$this->errorString = $errors;
return true;
}
}
public function ValidationErrors() // Return the error string
{
if ($this->makeErrorString()) {
return $this->errorString;
}
}
/**
* End of Class
**/
}
/**
* End of formValidation.php
**/
?>
And my controller file :
<?php
/**
* Homepage Controller
* #copyright 2012
*/
class home extends SimpleController
{
public function index()
{
$view = new view('home');
$val = new formValidation();
$val->setMethod($_POST);
$rules = array(
array(
'field' => 'test',
'name' => 'Test',
'required' => true,
'letters_numbers' => true,
'min_length' => '5'
),
array(
'field' => 'bla',
'name' => 'Trololol',
'required' => true
)
);
$val->setRules($rules);
if (!$val->run()) {
echo $val->ValidationErrors();
}
}
}
?>
In my view i have a form with 2 fields, test and bla. When i submit the form when it's empty, the checkMinLength() functions returns 2 value's :
Test must contain at least 1 characters
Test must contain at least 5 characters
First of all, where is it getting that 1 from, and 2nd why is it showing 2 messages for the same function ? I just can't find out why.
Hope you guys can help me out!
(The echo in the controller is just for demo)
Thanks!
You forgot a break in your switch, so you're falling through from the letters_numbers case into the min_length case.
The "rule value" for your letters_numbers rule is true, which converts to a string as 1.
Related
Setup: I have a slim application and I pulled in Illuminate DB and Twig view.
if (!$validator->passed()) {
$errors = $validator->errors();
$users = User::all();
return $this->view($response, 'auth.login', compact('errors','users'));
}
Problem: When I run the above code, I am able to retrieve the users variable in my view, but the errors variable throws the following error.
Notice: Array to string conversion in /Users/davidchen/Documents/sites/slim.com/vendor/twig/twig/lib/Twig/Environment.php(378) : eval()'d code
on line
70 Array
The errors variable returns a multidimensional array, below you'll find the result that I get from print_r($errors).
Array (
[username] => Array (
[0] => username already exists
)
[password] => Array (
[0] => password must consist of at least 6 characters
)
)
Here are my related project files:
Twig Setup File (app.php)
$c = $app->getContainer();
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($config['config']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$c['db'] = function($c) use ($capsule){
return $capsule;
};
$c['view'] = function($c){
$options['cache']=false;
$view = new \Slim\Views\Twig(__DIR__."/../views", $options);
$view->addExtension(new \Slim\Views\TwigExtension(
$c->router,
$c->request->getUri()
));
$view->getEnvironment()->addGlobal('flash', $c->flash);
return $view;
};
$c['flash'] = function($c){
return new Slim\Flash\Messages();
};
Validator Class
namespace App\Models\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Capsule\Manager as DB;
use Carbon\Carbon;
use DateTime;
class Validator extends Model
{
protected $field_name,
$data,
$errors = [];
/*
* Main validator
*/
public function __construct($request, $fields = []){
$data = $request->getParams();
$this->data = $data;
foreach ($fields as $field => $constraints) {
$this->field_name = $field;
if (isset($data[$field])) {
$field_value = $data[$field];
foreach (explode("|", $constraints) as $constraint) {
$obj = explode(":", $constraint);
$function_name = $obj[0];
if (isset($obj[1])) {
if(method_exists($this, $function_name))
{
$this->$function_name($obj[1],$field_value);
}
}
}
}else{
if (strpos($constraints, 'required') !== false) {
$validator->report($validator->field_name.' field is requried');
}
}
}
return $this;
}
/*
* Object Interface Methods
*/
private function report($message){
$this->errors[$this->field_name][]= $message;
}
public function errors(){
return $this->errors;
}
public function passed(){
if (!count($this->errors)) {
return true;
}
}
/*
* Validation Rules
*/
public function max($length,$value){
if (strlen($value)>$length) {
$this->report("{$this->field_name} must consist of less than {$length} characters");
}
}
public function min($length,$value){
if (strlen($value)<$length) {
$this->report("{$this->field_name} must consist of atleast {$length} characters");
}
}
public function distinct($tableName,$value){
if (DB::table($tableName)->where($this->field_name, $value)->exists()) {
$this->report("{$this->field_name} already exists");
}
}
public function date($format,$date){
if (!preg_match("/\d{4}-\d{2}-\d{2}\b/",$date)) {
$this->report("incorrect {$this->field_name} values");
}else{
$d = DateTime::createFromFormat($format, $date);
if ($d && $d->format($format) !== $date) {
$this->report("{$this->field_name} format should be {$format}");
}
}
}
public function match($matchField,$value){
if (isset($this->data[$matchField])) {
$valueTwo = $this->data[$matchField];
if ($value !== $valueTwo) {
$this->report("{$this->field_name} does not match {$matchField}");
}
}else{
$this->report("{$matchField} is required");
}
}
public function format($type,$value){
switch ($type) {
case 'noWhiteSpace':
if (preg_match("/\s/",$value)) {
$this->report("{$this->field_name} may not contain any spaces");
}break;
case 'alpha':
if (preg_match("/[^a-zA-Z]/",$value)) {
$this->report("{$this->field_name} may only contain letters");
}break;
case 'alphaNum':
if (preg_match("/[^a-zA-Z0-9]/",$value)) {
$this->report("{$this->field_name} may only contain letters");
}break;
case 'email':
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->report("in correct {$this->field_name} format");
}break;
default:
# code...
break;
}
}
}
Base Controller
namespace App\Controllers;
/**
*
*/
class Controller
{
protected $c;
function __construct($container)
{
$this->c = $container;
}
public function view($response, $path,$variables = []){
$this->c->view->render($response, str_replace(".","/",$path).".twig", $variables);
}
public function pathFor($routeName,$data = []){
return $this->c->router->pathFor($routeName,$data);
}
}
Auth Controller
namespace App\Controllers\Auth;
use App\Models\User\User;
use App\Controllers\Controller;
use App\Models\Auth\Validator;
/**
*
*/
class AuthController extends Controller
{
public function getLogin($request, $response){
return $this->view($response, 'auth.login');
}
public function postLogin($request, $response){
$validator = new Validator($request,[
'username'=>'required|min:3|max:64|format:alphaNum|distinct:users',
'password'=>'required|min:6|max:64|',
]);
if (!$validator->passed()) {
$errors = $validator->errors();
$users = User::all();
return $this->view($response, 'auth.login', compact('errors','users'));
}
return $this->view($response, 'home.login');
}
}
login.twig file
login.twig file
Hope one of you can shed some light on this problem. I've been struggling with this all morning.
You could try to loop over each item in a sequence. For example, to display a list of users provided in a variable called users:
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
Read more
I've created a custom form element for phone numbers. It is composed of a select for the international prefix and of a text for the actual phone number.
class Mylib_Form_Element_Phone extends Zend_Form_Element_Xhtml
{
public $helper = 'formPhone';
protected $prefix;
protected $phone;
public function setValue($value)
{
if (is_null($value)) {
return;
}
if (!is_array($value) || !isset($value['prefix']) || !isset($value['phone'])) {
throw new Exception('Invalid phone value provided');
}
$this->prefix = $value['prefix'];
$this->phone = $value['phone'];
}
public function getValue()
{
return array(
'prefix' => $this->prefix,
'phone' => $this->phone
);
}
public function isValid($value, $context = null)
{
$valid = parent::isValid($value, $context);
if ($valid && !empty($this->phone) && empty($this->prefix)) {
$this->addError('PHONE_PREFIX_REQUIRED');
return false;
}
return $valid;
}
}
My custom validation error works fine but if the element is created with 'required' => true and i left both fields empty, no error are triggered.
How to you make the NotEmpty validator works with custom array element?
I've a form with 2 text-fields, namely minimum-amount & maximum-amount. I would like to know whether it is possible for me to compare the values in text-field "minimum-amount" with the value in text-field "maximum-amount" and vice-versa using validators in Zendform.
You need to create a custom validation for that.
I think function in this post will help you My_Validate_FieldCompare.
just create your own validator, the isValid method of the validator will get the value of the field the validator is attached to plus the whole context of the form, which is an array of values of all other form fields. additionally you can add functions to the validator to set the context field and if it should be less, equal, greater or even pass a function to compare...
here some example code (not tested)
<?php
namespace My\Validator;
class MinMaxComp extends AbstractValidator
{
const ERROR_NOT_SMALLER = 'not_smaller';
const ERROR_NOT_GREATER = 'not_greater';
const ERROR_CONFIG = 'wrong_config';
const TYPE_SMALLER = 0;
const TYPE_GREATER = 1;
protected $messageTemplates = array(
self::ERROR_NOT_SMALLER => "Blah",
self::ERROR_NOT_GREATER => "Blah",
self::WRONG_CONFIG => "Blah",
);
protected $type;
protected $contextField;
public function setType($type)
{
$this->type = $type;
}
public function setContextField($fieldName)
{
$this->contextField = $fieldName;
}
public function isValid($value, $context = null)
{
$this->setValue($value);
if (!is_array($context) || !isset($context[$this->contextField])) {
return false;
}
if ($this->type === self::TYPE_SMALLER) {
if (!$result = ($value < $context[$this->contextField])) {
$this->error(self::ERROR_NOT_SMALLER);
}
} else if ($this->type === self::TYPE_GREATER) {
if (!$result = ($value > $context[$this->contextField])) {
$this->error(self::ERROR_NOT_GREATER);
}
} else {
$result = false;
$this->error(self::ERROR_CONFIG);
}
return $result;
}
}
I am having a problem with the filtering and validation of the form below. If the two mobile numbers are entered in national format (07777654321) the form validation fails, but if they are entered in international format (447777654321) it validates. My understanding is that the filters will be applied first so when identical is applied both mobile and mobile_confirm should have been processed with the same set of filters.
So if I enter 07777654321 into both fields it will get filtered to 447777654321 in both fields which should then validate. Where am I going wrong.
I am using ZF 1.11.3.
<?php
class Test_Form_Check extends Zend_Form
{
public function init()
{
$mobile= new Zend_Form_Element_Text( 'mobile' );
$mobile->addFilter(new Test_Filter_MobileFilter('44'))
->addValidator(new Test_Validate_Mobile())
->setRequired( true );
$mobile1= new Zend_Form_Element_Text( 'mobile_confirm' );
$mobile1->addFilter(new Test_Filter_MobileFilter('44'))
->addValidator(new Test_Validate_Mobile())
->addValidator('Identical', false, array('token' => 'mobile', 'messages' => 'Mobile numbers do not match.'))
->setRequired( true );
$Add = new Zend_Form_Element_Submit('Add');
$Add->setLabel('Submit');
$this->addElement($mobile)
->addElement($mobile1)
->addElement( $Add );
}
}
class Test_Validate_Mobile extends Zend_Validate_Abstract
{
const NOT_DIGITS = 'notDigits';
const STRING_EMPTY = 'digitsStringEmpty';
const INVALID = 'digitsInvalid';
const INVALIDPHONE = 'phonenumberinvalid';
protected static $_filter = null;
protected $_messageTemplates = array(
self::NOT_DIGITS => "'%value%' must contain only digits",
self::STRING_EMPTY => "'%value%' is an empty string",
self::INVALID => "Invalid type given. String, integer or float expected",
self::INVALIDPHONE => "Invalid number, try with country code",
);
public function isValid($value)
{
if (!is_string($value) && !is_int($value) && !is_float($value)) {
$this->_error(self::INVALID);
return false;
}
if (!preg_match('/^(447)[0-9]{9}$/', $value)) {
$this->_error(self::INVALIDPHONE);
return false;
}
$this->_setValue((string) $value);
if ('' === $this->_value) {
$this->_error(self::STRING_EMPTY);
return false;
}
if (null === self::$_filter) {
require_once 'Zend/Filter/Digits.php';
self::$_filter = new Zend_Filter_Digits();
}
if ($this->_value !== self::$_filter->filter($this->_value)) {
$this->_error(self::NOT_DIGITS);
return false;
}
return true;
}
}
class Test_Filter_MobileFilter implements Zend_Filter_Interface
{
/**
* Default country code
*
* #var string
*/
protected $_def_country = '44';
public function __construct($options = null)
{
if ($options !== null) {
if (is_string($options) && is_numeric($options)) {
$this->_def_country = $options;
}
else {
require_once 'Zend/Filter/Exception.php';
throw new Zend_Filter_Exception('Options not valid country code');
}
}
}
public function filter($value)
{
if (!empty($value)) {
$replace = array(' ','+','-','(',')');
$value = str_replace($replace, '', $value);
$value = preg_replace('/\A0/', $this->_def_country, $value);
}
return $value;
}
}
I will be very grateful for any help with this. It is driving me round the bend.
I guess problem is with Zend_Validate_Identical validator , it uses unfiltered raw value from $_POST. Just quick fix:
class My_Validate_Identical extends Zend_Validate_Identical
{
/**
*
* #var Zend_Form_Element
*/
protected $_element = null;
public function __construct($token = null)
{
if (is_array($token) && array_key_exists('element', $token)) {
$this->_element = $token['element'];
}
parent::__construct($token);
}
public function isValid($value, $context = null)
{
$context[$this->getToken()] = $this->_element->getValue();
return parent::isValid($value, $context);
}
}
Attaching validator:
$mobile1->addValidator(new My_Validate_Identical(array(
'token' => 'mobile',
'messages' => 'Mobile numbers do not match.',
'element' => $mobile
)))
Hope this helps :)
I have a page that receives POST data from a form. I also have a form validation class that attempts to access the $_POST array when a FormValidation object is created on the page. However, the FormValidation object does not seem to have access to the $_POST array. Why is this happening, and how can I fix this?
EDIT: Added code :)
This code is from register.php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
var_dump($_POST);
$errors = validate_form();
if(count($errors) > 0)
{
display($errors);
}
else
{
act();
}
}
else
{
display();
}
function validate_form()
{
$validator = new FormValidation('POST');
$validator->add_field('first_name', 'First Name');
$validator->add_field('last_name', 'Last Name');
$validator->add_field('email_address', 'Email Address');
$validator->add_field('password', 'Password');
$validator->add_field('password_confirmation', 'Password Confirmation');
$validator->add_rule('first_name', 'required');
$validator->add_rule('last_name', 'required');
$validator->add_rule('email_address', 'required');
$validator->add_rule('email_address', 'is_valid_scarsdaleschools_email');
$validator->add_rule('password', 'required');
$validator->add_rule('password_confirmation', 'required');
$validator->add_rule('password_confirmation', 'matches', array('password', 'Password'));
return $validator->validate();
}
The form validation code looks like this
class FormValidation
{
var $fields;
var $rules; //Associative array that maps field_name to array of rules
var $errors;
var $form_method_type;
function FormValidation($form_method_type)
{
$this->form_method_type = strtoupper($form_method_type);
$this->fields = array(); //maps fields to field labels
$this->rules = array();
$this->errors = array();
}
function validate()
{
foreach(array_keys($this->fields) as $field_name)
{
if(!array_key_exists($field_name, $this->rules))
{
continue;
}
foreach(array_keys($this->rules[$field_name]) as $rule_name)
{
call_user_func_array(array($this, $rule_name), $this->rules[$field_name][$rule_name]);
}
}
return $this->errors;
}
function add_field($field_name, $field_label)
{
$this->fields[$field_name] = $field_label;
}
function add_rule($field_name, $rule_name, $parameters=array())
{
if(!isset($this->rules[$field_name]))
{
$this->rules[$field_name] = array();
}
array_unshift($parameters, $field_name, $this->fields[$field_name]);
$this->rules[$field_name][$rule_name] = $parameters;
}
function var_isset($field_name)
{
global ${'$_' . $this->form_method_type};
var_dump(${'$_' . $this->form_method_type}[$field_name]);
return isset(${'$_' . $this->form_method_type}[$field_name]);
}
function get_value($field_name)
{
global ${'$_' . $this->form_method_type};
return ${'$_' . $this->form_method_type}[$field_name];
}
////////////////////////////////////////////////////////////
/////RULES//////////////////////////////////////////////////
////////////////////////////////////////////////////////////
function required($field_name, $field_label)
{
if(!$this->var_isset($field_name))
{
$this->errors[$field_name] = "$field_label is a required field";
}
}
function is_valid_email($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!validEmail($this->get_value($field_name)))
{
$this->errors[$field_name] = "$field_label requires a valid email address";
}
}
}
function is_alpha($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!ctype_alpha($this->get_value($field_name)))
{
$this->errors[$field_name] = "$field_label requires alphabetical characters only";
}
}
}
function is_alphanumeric($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!ctype_alnum($this->get_value($field_name)))
{
$this->errors[$field_name] = "$field_label requires alphanumeric characters only";
}
}
}
function is_integer_number($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!is_int($this->get_value($field_name)) || preg_match('[-+]?[0-9]+', $this->get_value($field_name)) == 0)
{
$this->errors[$field_name] = "$field_label must be an integer";
}
}
}
function is_float_number($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!is_float($this->get_value($field_name)) || preg_match('[-+]?[0-9]*\.?[0-9]+', $this->get_value($field_name)) == 0)
{
$this->errors[$field_name] = "$field_label must be a number";
}
}
}
function is_valid_scarsdaleschools_email($field_name, $field_label)
{
if($this->var_isset($field_name))
{
if(!validEmail($this->get_value($field_name)))
{
$this->errors[$field_name] = "$field_label requires a valid email address";
}
$email = $this->get_value($field_name);
if(!(endsWith($email, 'scarsdaleschools.org', $case=false) || endsWith($email, 'scarsdaleschools.com', $case=false) || endsWith($email, 'scarsdaleschools.k12.ny.edu', $case=false)))
{
$this->errors[$field_name] = "$field_label requires a Scarsdale Schools email address";
}
}
}
function max_length($field_name, $field_label, $max_length)
{
if($this->var_isset($field_name))
{
if(strlen($this->get_value($field_name)) > $max_length)
{
$this->errors[$field_name] = "$field_label cannot be longer than $max_length characters";
}
}
}
function min_length($field_name, $field_label, $min_length)
{
if($this->var_isset($field_name))
{
if(strlen($this->get_value($field_name)) > $min_length)
{
$this->errors[$field_name] = "$field_label must be at least $min_length characters";
}
}
}
function matches($field_name, $field_label, $match_field_name, $match_field_label)
{
if($this->var_isset($field_name) && !$this->var_isset($match_field_name))
{
$this->errors[$field_name] = "$field_label must match $match_field_label";
}
elseif(!$this->var_isset($field_name) && $this->var_isset($match_field_name))
{
$this->errors[$field_name] = "$field_label must match $match_field_label";
}
if($this->var_isset($field_name) && $this->var_isset($match_field_name))
{
if(strcmp($this->get_value($field_name), $this->get_value($match_field_name)) != 0)
{
$this->errors[$field_name] = "$field_label must match $match_field_label";
}
}
}
}
Thanks for the help!
There is a small error in your code. What you were writing was essentially $$_POST. You'll need to remove the extra dollar sign from the variable name (corrected methods below). In addition, you needn't worry about calling global $_POST; as $_POST is a superglobal, or automatic global.
Update: Retrieving $_POST via ${'_' . $this->form_method_type} does not seem to work. The code I sent you works OUTSIDE of the class, but not inside. I wanted to make sure you understood my findings, and the distinction. So while ${'_' . $formMethodType} works outside of the class, inside you'll have to use something like this:
const TYPE_POST = 'POST';
const TYPE_GET = 'GET';
const TYPE_SESSION = 'SESSION';
const TYPE_SERVER = 'SERVER';
const TYPE_REQUEST = 'REQUEST';
public $fields = array();
public $rules = array();
public $errors = array();
public function __construct($formMethodType)
{
$r = new ReflectionClass($this);
$constants = $r->getConstants();
$formMethodType = strtoupper($formMethodType);
if (!array_key_exists('TYPE_' . $formMethodType, $constants)) {
throw new InvalidArgumentException('Could not find type matching $formMethodType : ' . $formMethodType);
}
$this->form_method_type = $formMethodType;
}
public function var_isset($field_name)
{
$values = $this->get_values();
var_dump($values[$field_name]);
return isset($values[$field_name]);
}
public function get_value($field_name)
{
$values = $this->get_values();
return $values[$field_name];
}
public function get_values()
{
switch ($this->form_method_type) {
case self::TYPE_POST:
$values = $_POST;
break;
case self::TYPE_GET:
$values = $_GET;
break;
case self::TYPE_REQUEST:
$values = $_REQUEST;
break;
case self::TYPE_SERVER:
$values = $_SERVER;
break;
case self::TYPE_SESSION:
$values = $_SESSION;
break;
}
return $values;
}
We haven't yet seen any code posted, but I'll make a recommendation about using dependency injection to get the $_POST data into your form validation class, rather than accessing the superglobal from inside the class.
Since you can't always rely on $_POST being populated correctly when you are testing your code, it is advisable to inject the contents of $_POST as a parameter to the constructor of a class where it will be used. This makes it easier to debug later on.
class MyClass {
// Public property to hold post array data
public $postdata;
// constructor receives $_POST as a parameter
function __construct($param1, $param2, $postdata) {
//
$this->postdata = $postdata;
}
}
// Instantiate the class with $_POST injected
$x = new MyClass($a, $b, $_POST);
ADDENDUM after code posted
I don't see any method in your class that actually calls the get_value() method when validating. It's unclear if you are ever accessing the contents of $_POST in that class at all other than in the var_isset() and get_value() methods.
/**
* This should work for $_REQUEST and $_POST the mysql_real_escape_string
* was added to escape urls. It is best not to allow URLs as parameters.
*/
foreach( $_POST as $key=> $val)
{
${$key} = mysql_real_escape_string($val);
}