I am trying to develop a form validation system. But the problem is even though there is no data given by users as required the validation passes. Can't figure out where the problem is.
This is my form validation class
class Validation
{
private $_passed = false,
$_errors = array(),
$_db = null;
public function __construct() {
$this->_db = DB::getInstance();
}
public function check($source, $items= array()) {
foreach ($items as $item => $rules) {
foreach ($rules as $rule => $rule_value) {
$value = $source[$item];
if ($rule === 'required' && empty($value)) {
$this->addError("{$item} is required");
} else {
}
}
}
if (empty($this->_errors)) {
$this->_passed = true;
}
return $this;
}
private function addError($error) {
$this->errors[] = $error;
}
public function errors() {
return $this->_errors;
}
public function passed() {
return $this->_passed;
}
}
And this is the form page containing Html form.
require_once 'core/init.php';
if (Input::exists()) {
$validate = new Validation();
$validation = $validate->check($_POST, array(
'username' => array(
'required' => true,
'min' => 2,
'max' => 20,
'unique' => 'users'
),
'password' => array(
'required' => true,
'matches' => 'password'
),
'password_again' => array(
'required' => true,
'min' => 6
),
'name' => array(
'required' => true,
'min' => 2,
'max' => 60
),
));
if ($validation->passed()) {
//register new user
echo "passed"; //this passes even though users provides no data
} else {
print_r($validation->errors());
}
}
So, all i get is echo passed on the screen even though user provide no data at all. It should throw the errors instead. Please help. Thanks
addError writes in $this->errors, while the other methods use $this->_errors. (with underscore). The _errors array will remain empty, so _passed will be set to true in this statement:
if (empty($this->_errors)) {
$this->_passed = true;
}
Related
I am trying to create a class to clean data for a brand before adding it to my database. As you can see I have added general filters (which can be used elsewhere). On the other hand, some fields will need a personalized cleaning. That's why I created 'function' in my array. My code is currently functional however the "create_function" function is deprecated and I would like to remove it but I cannot find an alternative without using "eval". Can you help me find a solution? Thank you.
<?php
class VehMarques
{
private static $fields_allowed = [
'_id' =>
[
'instanceof' => '\MongoDB\BSON\ObjectID',
],
'name' =>
[
'function' => 'if(!isset($name) && !isset($age)){return false;}',
],
'user' =>
[
'required',
'instanceof' => '\MongoDB\BSON\ObjectID',
],
'centre' =>
[
'required',
'instanceof' => '\MongoDB\BSON\ObjectID',
],
'time' =>
[
'instanceof' => 'MongoDB\BSON\UTCDateTime',
],
];
public static function add(array $fields)
{
$fields_options=array();
foreach(self::$fields_allowed as $key => $val)
{
foreach($val as $key1 => $val1)
{
if(in_array($val1, array('required')))
{
$fields_options[$val1][$key] = null;
}
else
{
$fields_options[$key1][$key] = $val1;
}
}
}
if(!empty(self::$fields_allowed) && !empty(array_diff_key($fields, self::$fields_allowed)))
{
return false;
}
if(!empty($fields_options['function']))
{
foreach($fields_options['function'] as $func)
{
$func = preg_replace('/\$([a-zA-Z0-9]+)/', '$fields[\'$1\']', $func);
if(create_function('$fields', $func)($fields) === false)
{
return false;
}
}
}
if(!empty($fields_options['required']) && !empty(array_diff_key($fields_options['required'], $fields)))
{
return false;
}
if(!empty($fields_options['instanceof']))
{
foreach($fields_options['instanceof'] as $key => $val)
{
if(!($fields[$key] instanceof $val))
{
return false;
}
}
}
if(!isset($fields['_id']))
{
$fields['_id'] = new \MongoDB\BSON\ObjectID();
}
if(!isset($fields['time']))
{
$fields['time'] = new MongoDB\BSON\UTCDateTime();
}
return true;
}
}
$insert_marque = array(
'_id' => new \MongoDB\BSON\ObjectID(),
'name' => 'Test',
'user' => new \MongoDB\BSON\ObjectID(),
'centre' => new \MongoDB\BSON\ObjectID(),
'time' => new MongoDB\BSON\UTCDateTime()
);
var_dump(VehMarques::add($insert_marque));
?>
In ZF3 I created a form with two fields: text and url. Only one of them may be filled out by user and at least one must be filled out.
Imagine: one can put the contents of the site or the url of the site. The form may be used to grab certain data from the site or text.
I prepared two validator classes. One for each input. The classes were getting the input value of the other one from context parameter. The StringLength validator was used for both fields.
This worked almost fine but the bad issue was coming when both fields were submitted empty. Then the data did pass the validation while it should no.
At the case of this issue the fields have required turned to false.
When I switched them to true both of fields got required but I wanted only one to be required.
So the goal is that when both fields were empty the validation result would get false. Then the only one message should appear. I mean the message more or less like this: One of fields must be filled out. Not the 'required' message.
Here you are the form class and both validator classes.
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may by filled.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
Update
I used advises from #Crisp and had to do some correction in the code. Added returns and message handling. The working code is below:
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
if (empty($value)) return false;
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
const ERROR_EMPTY_FIELDS = 'empty-fields';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may be filled out.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
if (empty($value)) {
$this->messages = [
self::ERROR_EMPTY_FIELDS => 'One of the fields must be filled out.',
];
return false;
}
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
To ensure your validators always run, even for an empty value, you need to add the allow_empty and continue_if_empty options to your input specs. Otherwise validation is skipped for any value that isn't required.
The following combination should work
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
That combination should ensure your validators are applied when empty values are encountered.
Rob Allen (#akrabat) wrote a useful blog post detailing the combinations which is worth bookmarking akrabat.com/zend-input-empty-values/
The register.php file
<?php
if(Input::exists()) {
if(Token::check(Input::get('token'))) {
$validate = new Validate();
$validation = $validate->check($_POST, array(
'username' => array(
'required' => true,
'min' => 2,
'max' => 20,
'unique' => 'users'),
'password' => array(
'required' => true,
'min' => 6),
'password_again' => array(
'required' => true,
'matches' => 'password'),
'name' => array(
'required' => false,
'min' => 2,
'max' => 50)
));
if($validation->passed()) {
$user = new User();
$salt = Hash::salt(32);
try {
$user->create(array(
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password'), $salt),
'salt' => $salt,
'name' => Input::get('name'),
'joined' => date('Y-m-d H:i:s'),
'group' => 1
));
Session::flash('home', 'You have been registered and can now log in!');
Redirect::to('index.php');
} catch(Exception $e) {
die($e->getMessage());
}
} else {
foreach($validate->errors() as $error) {
echo $error, '<br>';
}
}
}
}
?>
The Validate.php file
<?php
class Validate {
private $_passed = false,
$_errors = array(),
$_db = null;
public function __construct() {
$this->_db = DB::getInstance();
}
public function check($source, $items = array()) {
foreach($items as $item => $rules) {
foreach($rules as $rule => $rule_value) {
$value = trim($source[$item]);
if($rule === 'required' && $rule_value === true && empty($value)) {
$this->addError("{$item} is required.");
} else if (!empty($value)) {
switch($rule) {
case 'min':
if(strlen($value) < $rule_value) {
$this->addError("{$item} must be a minimum of {$rule_value} characters.");
}
break;
case 'max':
if(strlen($value) > $rule_value) {
$this->addError("{$item} must be a maximum of {$rule_value} characters.");
}
break;
case 'matches':
if($value != $source[$rule_value]) {
$this->addError("{$rule_value} must match {$item}.");
}
break;
case 'unique':
$check = $this->_db->get('users', array($item, '=', $value));
if($check->count()) {
$this->addError("{$item} is already taken.");
}
break;
}
}
}
}
if(empty($this->_errors)) {
$this->_passed = true;
}
return $this;
}
protected function addError($error) {
$this->_errors[] = $error;
}
public function passed() {
return $this->_passed;
}
public function errors() {
return $this->_errors;
}
}
This is the code that i use to check if the password length, username and name is correct. If the password entered by the user is not correct it will output the error. For example "password_again does not match". Is it possible to change this variable name is any way? I want it to output like = "Password Again is not correct" or "Username is already is use". It would look much better with uppercase letters and without the underlines.
I hope you understood my question
Looking forward to your answers.
It may be possible to embed the error message somewhere in your form definition so that when a particular type of error occurs you show a custom message.
'password_again' => array(
'error_message' => 'Your custom error message here!'
'required' => true
)
Or maybe have a lookup file for the text you want to show for a particular field.
$error_messages = array(
'password_again' => 'We need a better password'
);
See Validation section of Laravel to get ideas on how to custom error messages could be stored.
FINAL EDIT:
I'm sorry. I didn't see your Validator.php class.
Here goes what you want:
Validate.php
<?php
class Validate {
private $_passed = false,
$_errors = array(),
$_db = null;
public function __construct() {
$this->_db = DB::getInstance();
}
public function check($source, $items = array()) {
foreach($items as $item => $rules) {
foreach($rules as $rule => $rule_value) {
$itemName = $item;
if ($rule == "display_name") {
continue;
} else {
if (array_key_exists("display_name", $rules)) $itemName = $rules["display_name"];
}
$value = trim($source[$item]);
if($rule === 'required' && $rule_value === true && empty($value)) {
$this->addError("{$itemName} is required.");
} else if (!empty($value)) {
switch($rule) {
case 'min':
if(strlen($value) < $rule_value) {
$this->addError("{$itemName} must be a minimum of {$rule_value} characters.");
}
break;
case 'max':
if(strlen($value) > $rule_value) {
$this->addError("{$itemName} must be a maximum of {$rule_value} characters.");
}
break;
case 'matches':
if($value != $source[$rule_value]) {
$this->addError("{$rule_value} must match {$itemName}.");
}
break;
case 'unique':
$check = $this->_db->get('users', array($item, '=', $value));
if($check->count()) {
$this->addError("{$itemName} is already taken.");
}
break;
}
}
}
}
if(empty($this->_errors)) {
$this->_passed = true;
}
return $this;
}
protected function addError($error) {
$this->_errors[] = $error;
}
public function passed() {
return $this->_passed;
}
public function errors() {
return $this->_errors;
}
}
Just edited the check() method to replace $item (the key name) with the value of the key "display_name" if exists. If not exists, just use the $item itself. Also, when iterating through the array, if the current $rule is "display_name", then it continues the loop from the next iteration. That way you don't process the "display_name" key.
If I am not wrong, now you just need to pass another key alongside the rules with the name "display_name" and the display string you want to represent that item. For example:
$validation = $validate->check($_POST, array(
'username' => array(
'required' => true,
'min' => 2,
'max' => 20,
'unique' => 'users',
'display_name' => 'Username'),
'password' => array(
'required' => true,
'min' => 6,
'display_name' => 'Password'),
'password_again' => array(
'required' => true,
'matches' => 'password',
'display_name' => 'Password Again'),
'name' => array(
'required' => false,
'min' => 2,
'max' => 50,
'display_name' => 'Name')
));
Sorry for all the previous edits.
In order to turn some_string into Some String you can do like this:
echo ucwords(str_replace('_', ' ', 'password_again')); // "Password Again"
In the validate.php file, you could use this approach to modify the value of $item once the value has been fetched:
...
$value = trim($source[$item]);
$item = ucwords(str_replace('_', ' ', $item));
...
Then messages will automatically be like "Password Again must be a minimum of 2 characters". You can also modify any error messages as you see fit:
...
case 'matches':
if($value != $source[$rule_value]) {
// Old: $this->addError("{$rule_value} must match {$item}.")
$this->addError("{$item} is not correct.");
}
break;
case 'unique':
$check = $this->_db->get('users', array($item, '=', $value));
if($check->count()) {
// Old: $this->addError("{$item} is already taken.");
$this->addError("{$item} is already in use.");
}
break;
...
And so on.
I can't seem to figure out how I unit test the update of my controller. i'm getting the following error:
method update() from Mockery_0_App.... Should be called exactly 1 times but called 0 times.
After I remove the if statement in the update (after checking if the allergy exists), I get the following error on the line where I add the id the the unique validation rule:
Trying to get property of on object
My Code:
Controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
...other methods (index,show,destroy) ...
public function update($id)
{
$allergy = $this->allergy->find($id);
//if ($allergy != null) {
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.edit.name') . $allergy->name
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->update($allergy, Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
//}
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Cannot update the allergy!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
TestCase:
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
...Other tests for Index and Show ...
public function testUpdate()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'bar');
$this->allergy->shouldReceive('find')->with(1)->once();
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergy->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();
}
}
Config validation rules file:
return array(
'allergy' => array(
'add' => array(
'name' => 'required|unique:Allergy'
),
'edit' => array(
'name' => 'required|unique:Allergy,name,'
),
'messages' => array(
'name' => 'Name'
)
)
);
Is there a way to actually mock the value provided into the validation rule? Or what is the best way to solve this?
I changed my code to this and now it works! :)
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$allergyObj = Mockery::mock('stdClass');
$allergyObj->name = 1;
$input = array('name' => 'bar');
$this->allergyRepo->shouldReceive('find')->with(1)->once()->andReturn($allergyObj);
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergyRepo->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();
Ok when I get it prevents my validation(server side php) from working.
I have commented out the get token and my code works well, is there a reason for this?
code
<?php
require_once 'core/init.php';
if(Input::exists()) {
echo 'i have been run';
it works will i comment this line out //if(Token::check(Input::get('token'))) {
$validate = new Validate();
$validation = $validate->check($_POST, array(
'username' => array(
'required' => true,
'min' => 2,
'max' => 20,
'unique' => 'users'
),
'password' => array(
'required' => true,
'min' => 6
),
'password_again' => array(
'required' => true,
'matches' => 'password'
),
'name' => array(
'required' => true,
'min' => 2,
'max' => 50
)
));
if($validation->passed()) {
$user = new User();
$salt = Hash::salt(32);
try {
$user->create(array(
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password'), $salt),
'salt' => $salt,
'name' => Input::get('name'),
'joined' => date('Y-m-d H:i:s'),
'group' => 1
));
Session::flash('home', 'You have been registered and now can log in!');
header('Location: index.php');
} catch(Exception $e) {
die($e->getMessage());
}
} else {
foreach($validation->errors() as $error) {
echo $error, '<br>';
}
}
}
//}
?>
Token.php
<?php
class Token {
public static function generate() {
return Session::put(Config::get('session/token_name'), md5(uniqid()));
}
public static function check($token) {
$tokenName = Config::get('session/token_name');
if(Session::exists($tokenName) && $token === Session::get($tokenName)) {
Session::delete($tokenName);
return true;
}
return false;
}
}
Hash.php
<?php
class Hash {
public static function make($string, $salt = '') {
return hash('sha256', $string . $salt);
}
public static function salt($length) {
return mcrypt_create_iv($length);
}
public static function unique() {
return self::make(uniqid());
}
}
It looks like you're using Laravel. If that's the case, there's already a CSRF filter that I think is accomplishing what you're trying to do.
See: /app/filters.php
Route::filter('csrf', function()
{
if (Session::token() != Input::get('_token') {
throw new Illuminate\Session\TokenMismatchException;
}
});
You can enforce this filter on a route like this:
Route::post('register', array('before' => 'csrf', function( ) {
return 'You gave a valid CSRF token!';
}));
See also: http://laravel.com/docs/html#csrf-protection