What would make a good PHP validaton class? - php

I would like to read about your opinions on what the best way to create a validation class is.
Below i pasted in my version, it's not really extensive yet, but is it the right approach so far?
I want every element which could appear in a form and which should be validated to have its own properties, in terms of string length for strings or file size for images or files in general (see below).
Also, is it better to declare these rules as nested arrays or should I put them in one big string, which would then be split up during the process?
mb_internal_encoding("UTF-8");
class Phrase {
//creates parts of sentences, not important
static function additive(array $limbs) {
return implode(' and ', array_filter([implode(', ', array_slice($limbs, 0, -1)), end($limbs)], 'strlen'));
}
}
class Text {
static function validate($item) {
$err = array();
$value = $_POST[$item] ?? $_GET[$item];
$criteria = FormProcess::$criteria[$item];
foreach($criteria as $critKey => $critVal) {
if($critKey === 'required' && empty($value)) {
$err[] = "is required";
} else if(!empty($value)) {
switch($critKey) {
case 'length':
if(is_array($critVal)) {
//min and max set
if(mb_strlen($value) < $critVal[0] || mb_strlen($value) > $critVal[1]) {
$this->err[] = "must contain between {$critVal[0]} and {$critVal[1]} characters";
}
} else {
//max set only
if(mb_strlen($value) > $critVal) {
$err[] = "must contain a maximum of $critVal characters";
}
}
break;
case 'pattern':
if(!preg_match($critVal[0], $value)) {
$err[] = "may consist of {$critVal[1]} only";
}
break;
case 'function':
$result = static::$critVal($value);
if($result) {
$err[] = $result;
}
break;
}
}
}
if(!empty($err)) {
return "{$criteria['name']} " . Phrase::additive($err) . "!";
}
return false;
}
private static function email($email) {
//checks if given string is a valid email address
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return "is invalid";
}
return false;
}
}
class File {
//checks for aspects like filesize...
static function validate() {
//...
}
}
class Image extends File {
static function validate() {
parent::validate(); //perform general file checks first
//...checks for images specifically
}
}
class Video extends File {
static function validate() {
parent::validate(); //perform general file checks first
//...checks for videos specifically
}
}
class FormProcess {
public $errors;
//declare, what kind of requirements the items must meet
static $criteria = array(
'email' => array(
'type' => 'Text',
'required' => true,
'name' => 'Email',
'length' => 48,
'function' => 'email',
),
'username' => array(
'type' => 'Text',
'required' => true,
'name' => 'Username',
'length' => [4, 24],
'pattern' => ['/^[-\w]+$/', "alphanumeric characters, underscores and hyphens"],
),
'password' => array(
'type' => 'Text',
'required' => true,
'name' => 'Password',
'length' => [6, 100],
'pattern' => ['/^[\S]+$/', "non-whitespace characters"],
),
);
//runs the validate function on each item while storing occuring errors
function __construct(array $items) {
foreach($items as $item) {
$class = self::$criteria[$item]['type'];
$result = $class::validate($item);
if($result) {
$this->errors[] = $result;
}
}
}
}
Then all you had to do, is, naming all expected items (by their html 'name' attribute in the form) in an array and pass it through the constructor, which would then run the appropriate validation function on each item.
$expected = ['username', 'password'];
$signup = new FormProcess($expected);
if($signup->errors) {
?>
There was something wrong with your request:
<ul>
<?php foreach($signup->errors as $error) { ?>
<li><?= $error ?></li>
<?php } ?>
</ul>
<?php
}
I hope to learn from mistakes and make improvements to the code from you where they are needed.
Thank you in advance!

Related

Improving PHP loop validation

I have a php script that performs validation I have a code block that sets up 4 fields that need the same validation. What I want to achieve is that instead in this code block, I can setup the key name, the display name and field type and below set codes which would then automatically validate the fields according to the rules set for each field type.
Here is the code block in question:
// key name => display name
$fields = [
'firstName' => 'First Name',
'lastName' => 'Last Name',
'companyName' => 'Company Name',
'companyAddress' => 'Company Address',
];
So in this block I want to setup the key name, display name and field type. Currently I just got the these four fields. Is there a way I can achieve what I desire?
Here is my full code:
function validate($formData)
{
// Initiate Array
$validationMSG = array(); // array to hold validation errors
// what to validate (basics, i.e. required fields)
// key name => display name
$fields = [
'firstName' => 'First Name',
'lastName' => 'Last Name',
'companyName' => 'Company Name',
'companyAddress' => 'Company Address',
];
//simple loop
foreach($fields as $name => $display){
if(empty($formData[$name])){
$validationMSG[$name] = "${display} is required.";
}
}
//and NOW wee can perform some specific tests:
$pname_exp = '/^[a-zA-Z0-9\_]{2,20}/';
if(isset($formData['firstName']) && !preg_match($pname_exp, $formData['firstName'])){
$validationMSG['firstName'] = 'First Name is not valid.';
}
if(isset($formData['lastName']) && !preg_match($pname_exp, $formData['lastName'])){
$validationMSG['lastName'] = 'Last Name is required.';
}
//removed company name and company address checks, because we are done with them in the loop.
// Validate state
if (!isset($formData['state'])) {
$validationMSG['state'] = 'State is required.';
}
// Validate city
if (!isset($formData['city'])) {
$validationMSG['city'] = 'City is required.';
}
// Validate Zipcode - If Field is Empty
if (!isset($formData['zipcode'])) {
$validationMSG['zipcode'] = 'Zipcode is required.';
}
// Validate emailAddress
if (!isset($formData['emailAddress'])) {
$validationMSG['emailAddress'] = 'Email Address is required.';
}
// Check if emailAddress is a valid email address
elseif (!filter_var($formData['emailAddress'], FILTER_VALIDATE_EMAIL)) {
$validationMSG['emailAddress'] = 'Email address is not valid.';
}
//Validate phoneNumber
if (!isset($formData['phoneNumber'])) {
$validationMSG['phoneNumber'] = 'Phone Number is required.';
}
//Validate phoneNumber
elseif (preg_match('/^[0-9-\s]+$/D', $formData['phoneNumber'])) {
$validationMSG['phoneNumber'] = 'Must be a valid phone number.';
}
// Validate message
if (!isset($formData['message'])) {
$validationMSG['message'] = 'Message is required.';
}
if (!empty($validationMSG)) {
return $validationMSG;
}
else {
$captcha = checkCaptcha($formData['g-recaptcha-response']);
if(!$captcha['isSuccess']){
$validationMSG['captcha'] = 'ReCaptcha is required.';
return $validationMSG;
}
//End of Validation Function
}
}
//testing
$input = ['firstName' => 'John'];
$errors = validate($input);
var_dump($errors);
You should be using an approach like this. This is a starting point to write a better validation check in your loops:
<?php
function validate($formData)
{
// Initiate Array
$validationMSG = array(); // array to hold validation errors
// what to validate (basics, i.e. required fields)
// key name => display name
$fields = [
'firstName' => [
'label' => 'First Name',
'rules' => 'required'
],
'lastName' => [
'label' => 'Last Name',
'rules' => 'required'
],
'emailAddress' => [
'label' => 'Email',
'rules' => 'required|email'
]
];
//simple loop
foreach($fields as $fieldName => $args) {
$rules = explode('|', $args['rules']);
foreach($rules as $rule)
{
if($rule == 'required' && (!isset($formData[$fieldName]) || empty($formData[$fieldName])))
{
$validationMSG[$fieldName][] = sprintf('%s is a required field.', $args['label']);
}
if((isset($formData[$fieldName]) && $rule == 'email') && !filter_var($formData[$fieldName], FILTER_VALIDATE_EMAIL))
{
$validationMSG[$fieldName][] = sprintf('%s must be a valid email.', $args['label']);
}
}
}
return $validationMSG;
}
This can be improved but the concept will get you started.
Here is a way to do validation, while still having a format that can be promoted to a typed class (e.g., with namespace and dependency injection, etc) on a refactor. What I will show you is a way to acquire an object in such a way you can validate the message payload, based on a common naming construct.
First, create a regular php function called userActions(). This will be used to (globally or as an include where needed) call into and get these objects. This will return a closure.
function userActions(): \Closure { ... }
Next, you'll need to encapsulate your procedure. I recommend doing it like this:
$startUserRecord = static function(
string $firstName,
string $lastName,
string $companyName,
string $companyAddress
) {
return new class(...func_get_args()) {
private $payload = [];
private $firstName;
private $lastName;
private $companyName;
private $companyAddress;
public function __construct(
string $firstName,
string $lastName,
string $companyName,
string $companyAddress
) {
$this->payload = func_get_args();
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->companyName = $companyName;
$this->companyAddress = $companyAddress;
}
public function validate(): array {
// Here is where you would actually validate this one message.
// Empty array means no validation messages (errors).
return [];
}
public function payload(): array {
return $this->payload;
}
};
};
// Other ones go here, too.
$saveUserProfile = static function() { ... };
$resetUserLogin = static function() { ... };
Corral them up to together, so we can use() them in our userActivities() closure:
$activities= [
'startUserRecord' => $startUserRecord,
'saveUserProfile ' => $saveUserProfile,
'resetUserLogin ' => $resetUserLogin,
];
Now, I've called it startUserRecord because I needed to make up a command name; give it the name you wish, however I recommend verb + subject + context construct, generally.
Static checks work for the "string, integer" and other low-level type checks, or class-based constructs as you have available (say, an Email value object). The validation of the values, as you can see, occurs inside the validate() method.
Next, create the closure that you'll return that actually allows you to search for and get only the command objects you want:
return static function(/* autoset as $get */) use($activities) {
$get = func_get_args();
$find = static function($name) use(&$get) {
return in_array($name, $get);
};
$filtered = array_filter($activities, $find, ARRAY_FILTER_USE_KEY);
$found = [];
// Keep in the same order.
foreach($get as $name) {
$found[] = $filtered[$name];
}
return $found;
};
Pay attention to the use() statement on the closure(s).
This is put within the userActivities() function declaration. Now, you can call and set it up, and generally use what you have:
$fields = [
'firstName' => 'First Name',
'lastName' => 'Last Name',
'companyName' => 'Company Name',
'companyAddress' => 'Company Address',
];
$userActivities = userActions();
[$startUserRecord] = $userActivities('startUserRecord');
$activity = $startUserRecord(...array_values($fields));
if ($errors = $activity->validate()) {
throw new InvalidCommand($errors);
}
// Do something with $activity->payload().
Note:
I'm not modifying the payload. You can add a conditioner/sanitizer method that acts on the payload or return the object's properties in some form.
We're destructuring with the [$startUserRecord] line. This allows you to return multiple entries and load them into their own variable names. An example:
[$saveUserProfile,$resetUserLogin] = $userActivites('saveUserProfile','resetUserLogin');
$startUserRecord(...array_values($fields)) This is called a spread or splat operation (splat operator: ... in the invocation), and makes each array item it's own separate argument, in order. I'm getting to that with array_values() here, however it's better to pass in an actual array without the keys, to maintain order in all cases.
Here it is put together: https://3v4l.org/sn61Q
This is just a starting point. Change it up, do what you need to, this should give you some idea what you can accomplish.
For instance, you could move the initializers to a with() method and "autoload" little closure-based validators that you share within the userActivities() function, like a $validateEmail() or $validatePhone() closures you declare and then use($validateEmail, ...) and within that closure new class($validateEmail, ...) to share these within an activity's context.

SQL insert with oop validation

I had been slowly learning PHP OOP, I decided it was time to start inserting into my table, however it doesn't seem to be inserting. I compared my code with working versions and I can't see what the problem might be, I attempted a var_dump(), the query returned as I expected, I tested my database class by creating an new user, it was successfully created so I assume it isn't that, I tested the SQL query and it was able to insert, I'm at a loss for it might be now
form
<?php
session_start();
require ("classes/Review.php");
require ("classes/Database.php");
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$reviewing = new ReviewValidation();
$review = $reviewTitle = "";
$post = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
$reviewTitle = $post['reviewTitle'];
$review = $post['review'];
$errors = array();
$fields = array(
'title' => array(
'validate' => 'title',
'message' => 'Title must be at least fife characters',
'value' => $reviewTitle,
),
'review' => array(
'validate' => 'review',
'message' => 'Your review must be at least three hundred characters',
'value' => $review,
)
);
foreach($fields as $key => $value)
{
$validation_result = $reviewing->{$value['validate']}($value['value']);
if(!$validation_result)
{
$errors[] = ['name' => $key, 'error' => $value['message']];
}
}
if(empty($errors))
{
try
{
$db = new Database;
$success = ["message" => "Review subbbmitted"];
$process = $db->prepare('INSERT INTO reviews (reviewTitle)
VALUES
(:reviewTitle');
$process->bindValue(':reviewTitle', $reviewTitle);
$process->execute();
}
catch(Exception $e)
{
$errors[] = ['response' => 'fail'];
}
}
}
header('Content-Type: application/json');
if (empty($errors))
{
echo json_encode($success);
}
else
{
echo json_encode(["errors" => $errors]);
}
class
<?php
class ReviewValidation
{
private
$db,
$review,
$reviewTitle;
private static
$reviewLength = 50,
$rewviewtitleLength = 5;
public static function title($reviewTitle)
{
return(strlen($reviewTitle) >= self::$rewviewtitleLength);
}
public static function review($review)
{
return(strlen($review) >= self::$reviewLength);
}
}
Looks like you may be missing the closing ) in the insert query:
$process = $db->prepare('INSERT INTO reviews (reviewTitle)
VALUES
(:reviewTitle)');
If you add in a closing parenthesis after :reviewTitle, before the single quote your syntax will be correct (shown above).
I also noticed that your calls to the static methods in the ReviewValidation class are using the object operator (->). To access static methods you need to utilize the scope resolution operator.
So your $validation_result line should look like:
$validation_result = ReviewValidation::{$value['validate']}($value['value']);
I think because of this, the validation may have been passing, which is why you where getting the no default value issue.

More efficient way instead of using repeated if statements

I have this code:
if (strtolower($_POST['skype']) == "yummy")
echo "<pre>".file_get_contents("./.htfullapps.txt")."</pre>";
elseif ($_POST['skype'] == '' or
$_POST['IGN'] == '' or
$_POST['pass'] == '' or
!isset($_POST['rules']) or
!isset($_POST['group']) or
strlen($_POST['pass']) <= 7)
{
redir( "http://ftb.chipperyman.com/apply/?fail&error=one%20or%20more%20fields%20did%20not%20meet%20the%20minimum%20requirements" ); //Redir is a function defined above and works fine.
exit;
}
However, I would like to start reporting specific errors. For example, this is how I would do it with if statements:
...
elseif ($_POST['skype'] == '') redir( "http://ftb.chipperyman.com/apply/?fail&error=your%20skype%20is%20invalid%20because%20it%20is%20empty" );
elseif ($_POST['IGN'] == '') redir( "http://ftb.chipperyman.com/apply/?fail&error=your%20IGN%20is%20invalid%20because%20it%20is%20empty" );
elseif ($_POST['pass'] == '') redir( "http://ftb.chipperyman.com/apply/?fail&error=your%20password%20is%20invalid%20because%20it%20is%20empty" );
elseif (strlen($_POST['pass']) <= 7) redir( "http://ftb.chipperyman.com/apply/?fail&error=your%20password%20is%20invalid%20because%20it%20does%20not%20meet%20minimum%20length%20requirements" );
...
However that's big, messy and inefficient. What would a solution to this be?
You could use associative array like this.
function redir($var){
echo $var;
}
$skypeErr = array(''=>"http://ftb.chipperyman.com/apply/?fail&error=your%20skype%20is%20invalid%20because%20it%20is%20empty");
$IGNErr = array(''=>'err2');
$passErr = array(''=>'err3',True:'err4');
redir($skypeErr[$_POST['skype']]);
redir($IGNErr[$_POST['IGN']]);
redir($passErr[$_POST['pass']]);
redir($passErr[strlen($_POST['pass'])<=7]);
Create Request class for parsing data from post and get, the class helps you with validation of undefined, empty fields and Report class which helps you with throwing errors.
Here is the very simple Request class:
class Request {
protected $items = array(
'get' => array(),
'post' => array()
);
public function __construct(){
$this->items['post'] = $_POST;
$this->items['get'] = $_GET;
}
public function isPost(){
return ($_SERVER['REQUEST_METHOD'] == 'POST') ? true : false;
}
public function isGet(){
return ($_SERVER['REQUEST_METHOD'] == 'GET') ? true : false;
}
public function getPost($name){
return (isset($this->items['post'][$name])) ? $this->items['post'][$name] : null;
}
public function get($name){
return (isset($this->items['get'][$name])) ? $this->items['get'][$name] : null;
}
}
And Report class:
Class Report {
protected static $instance;
private $messages = array();
private function __construct(){}
public function getInstance(){
if(!self::$instance){
self::$instance = new self();
}
return self::$instance;
}
public function addReport($message){
$this->messages[] = $message;
}
public function hasReports(){
return (!empty($this->messages)) ? true : false;
}
public function getReports(){
return $this->messages;
}
//this is not so cleaned .... it must be in template but for example
public function throwReports(){
if(!empty($this->messages)){
foreach($this->messages as $message){
echo $message."<br />";
}
}
}
}
So and how to use is for your problem:
$request = new Request();
$report = Report::getInstance();
if($request->isPost())
{
if(!$request->getPost("icq")){
$report->addMessage("you dont enter ICQ");
}
if(!$request->getPost("skype")){
$report->addMessage("you dont enter SKYPE");
}
//....etc
//if we have some reports throw it.
if($report->hasReports()){
$reports->throwReports();
}
}
The report class you can combine with sessions and throw errors after redirect, just update the class to saving reports to session instead of $messages, and after redirect if u will be have messages throw it and clear at the same time.
how about
$field_min_len = array('skype' => 1, 'IGN' => 1, 'pass' => 7);
for ($field_min_len as $f => $l) {
if (!isset($_POST[$f]) || strlen($_POST[$f]) < $l) {
redir(...);
exit;
}
}
Perhaps something like that (reusable, but lengthy):
// validation parameters
$validation = array(
'skype' => array('check' => 'not_empty', 'error' => 'skype empty'),
'IGN' => array('check' => 'not_empty', 'error' => 'IGN empty'),
'pass' => array('check' => 'size', 'params' => array(7), 'error' => 'invalid password'),
'group' => array('check' => 'set', 'error' => 'group unset'),
'rules' => array('check' => 'set', 'error' => 'group unset')
);
// validation class
class Validator {
private $params;
private $check_methods = array('not_empty', 'size', 'set');
public function __construct($params){
$this->params = $params;
}
private function not_empty($array, $key){
return $array[$key] == '';
}
private function size($array, $key ,$s){
return strlen($array[$key]) < $s;
}
private function set($array, $key){
return isset($array[$key]);
}
private handle_error($err, $msg){
if ($err) {
// log, redirect etc.
}
}
public function validate($data){
foreach($params as $key => $value){
if (in_array($value['check'], $this->check_methods)){
$params = $value['params'];
array_unshift($params, $data, $key);
$this->handler_error(call_user_func_array(array($this,$value['check']),
$params),
$value['error']);
}
}
}
};
// usage
$validator = new Validator($validation);
$validator->validate($_POST);
Just expand the class with new checks, special log function etc.
Warning: untested code.
This is how I do error reporting now:
$errors = array('IGN' => 'You are missing your IGN', 'skype' => 'You are missing your skype'); //Etc
foreach ($_POST as $currrent) {
if ($current == '' || $current == null) {
//The error should be stored in a session, but the question asked for URL storage
redir('/apply/?fail='.urlencode($errors[$current]));
}
}

Adding custom callback to Codeigniter Form Validation

I want to limit my registration to emails with #mywork.com I made the following in My_Form_validation.
public function email_check($email)
{
$findme='mywork.com';
$pos = strpos($email,$findme);
if ($pos===FALSE)
{
$this->CI->form_validation->set_message('email_check', "The %s field does not have our email.");
return FALSE;
}
else
{
return TRUE;
}
}
I use it as follows. I use CI rules for username and password and it works, for email it accepts any email address. Any I appreciate any help.
function register_form($container)
{
....
....
/ Set Rules
$config = array(
...//for username
// for email
array(
'field'=>'email',
'label'=>$this->CI->lang->line('userlib_email'),
'rules'=>"trim|required|max_length[254]|valid_email|callback_email_check|callback_spare_email"
),
...// for password
);
$this->CI->form_validation->set_rules($config);
The problem with creating a callback directly in the controller is that it is now accessible in the url by calling http://localhost/yourapp/yourcontroller/yourcallback which isn't desirable. There is a more modular approach that tucks your validation rules away into configuration files. I recommend:
Your controller:
<?php
class Your_Controller extends CI_Controller{
function submit_signup(){
$this->load->library('form_validation');
if(!$this->form_validation->run('submit_signup')){
//error
}
else{
$p = $this->input->post();
//insert $p into database....
}
}
}
application/config/form_validation.php:
<?php
$config = array
(
//this array key matches what you passed into run()
'submit_signup' => array
(
array(
'field' => 'email',
'label' => 'Email',
'rules' => 'required|max_length[255]|valid_email|belongstowork'
)
/*
,
array(
...
)
*/
)
//you would add more run() routines here, for separate form submissions.
);
application/libraries/MY_Form_validation.php:
<?php
class MY_Form_validation extends CI_Form_validation{
function __construct($config = array()){
parent::__construct($config);
}
function belongstowork($email){
$endsWith = "#mywork.com";
//see: http://stackoverflow.com/a/619725/568884
return substr_compare($endsWith, $email, -strlen($email), strlen($email)) === 0;
}
}
application/language/english/form_validation_lang.php:
Add: $lang['belongstowork'] = "Sorry, the email must belong to work.";
Are you need validation something like this in a Codeigniter callback function?
$this->form_validation->set_rules('email', 'email', 'trim|required|max_length[254]|valid_email|xss_clean|callback_spare_email[' . $this->input->post('email') . ']');
if ($this->form_validation->run() == FALSE)
{
// failed
echo 'FAIL';
}
else
{
// success
echo 'GOOD';
}
function spare_email($str)
{
// if first_item and second_item are equal
if(stristr($str, '#mywork.com') !== FALSE)
{
// success
return $str;
}
else
{
// set error message
$this->form_validation->set_message('spare_email', 'No match');
// return fail
return FALSE;
}
}
A correction to Jordan's answer, the language file that you need to edit should be located in
system/language/english/form_validation_lang.php
not application/.../form_validation_lang.php. If you create the new file under the application path with the same name, it will overwrite the original in the system path. Thus you will lose all the usage of the original filters.

php form validation - the better way?

i've been doing form submission and validationss.
i have been writing long codes to pass data from the controller/php page to a validation class and then pass it back to be displayed on the view.
for instance:
controller
if (isset($_POST["btnSubmit")) {
$result = ClassSomething::validateForm($_POST);
if (!$result) { //no error
ClassSomething::insertRecord(...);
} else {
$error = $result;
}
}
class ClassSomething {
public function validateForm($str) {
if ($str == "") {
return "error messagesss";
}
}
}
and somewhere in the html, i would display $error
is there a better way to do validation in php??
is there validation codes which can be reuse rather then doing it for every form??
tks in adv.
How can I validate POST data for user login form with this class in Kohana:
$post = Validate::factory($_POST)
->rules('login', array(
'not_empty',
'alpha_dash',
'min_length' => array(3),
'max_length' => array(32)
))
->rules('password', array(
'not_empty',
'min_length' => array(4),
'max_length' => array(64)
));
if ($post->check())
{
// Proceed login
}
else
{
// $errors will contain an array of errors. If _POST array was empty - $errors will be an empty array.
$errors = $post->errors('');
}

Categories