functions library


Not just the database, we'll need a lot more reusable methods to check form methods, redirect users, etc. Our application framework will be more organized if we created separate class for each of them. So, let's create some individual class files to hold specific methods.

REQUEST CLASS

Rather than checking processing method of data being sent from individual forms, we'll create a function that'll do it for us. The only thing we need to do is instantiate this class in our view file.

<?php
class Request
{
	public static function method($method='post')
	{
		switch ($method){
			case 'post':
			return ($_SERVER['REQUEST_METHOD'] ==='POST' && !empty($_POST));
			break;
			case 'get':
			return ($_SERVER['REQUEST_METHOD'] ==='GET' && !empty($_GET));
			break;
			default:
			throw new Exception("Method Undefined.");			
		}
	}

	public static function post($field){
		return filter_input(INPUT_POST, $field, FILTER_SANITIZE_SPECIAL_CHARS);
	} 

	public static function get($field){
		return filter_input(INPUT_GET, $field, FILTER_SANITIZE_SPECIAL_CHARS);
	} 
}	

First of all, we created a function to check the form method with default method post defined that will return boolean value. Whenever the data is expected to be sent from post method but is being tried to sent via get method or vice-versa, this function will prevent further execution of the codes in the class. Then, we simply sanitized HTML special charaters from the data being processed to prevent SQL injection.

AUTOLOAD CLASS

Here, we're going to create a lot of classes with multiple functions and we need to include or require these files in our main.php file and that'll make our main.php file clumsy. A better option would be to use autoload register function in a class that'll check for all the classes being instantiated in our files and include it whenever that class is called.

<?php
function autoload($class){
	$libPath = ROOT.'admin/Library/'.$class.'.php';
		if (file_exists($functionPath) && is_file($functionPath)){
			require_once $functionPath;
		}else{	
			die('No such File Found');
		}
}
spl_autoload_register('autoload');

Here, we defined the possible path of the class files we're going to create and asked the autoload function to check for the existence of those files. If the file is present in any of the defined path ROOT/ADMIN/LIBRARY for example here, it'll be included in our main.php file and the methods in it will be functionable or an error message will be displayed in the browser. To make this function work, we need to include it in our main.php file.

require_once('Autoload/autoload.php');	

SESSION CLASS

Any data we store in session are accessible globally. So, it'll be a highly useful class where we can store any data we need to, then dispaly it and delete it. Basically we'll create forms and the users will make data entry. We might redirect them to different pages during the process but wherever they get to, we need to display the success or error messages to them depending upon the tasks they performed. Storing such messages in the session will make it possible to display those messages to the users even in situations where they have been redirected to another page.

<?php
class session
{
	public static function put($key,$value){
		if(!isset($key)) return false;
		return $_SESSION[$key] = $value;
	}	

	public static function get($key){
		if(!isset($key)) return false;
		if(self::check($key)){
			return $_SESSION[$key];
		}
		return '';
	}

	public static function check($key){
		return isset($_SESSION[$key]);
	}

	public static function delete($key){
		if(!isset($key)) return false;
		if(self::check($key)){
			unset($_SESSION[$key]);
		}
		return true;
	}	

put() function with parameters key and value will store keys and values to those keys in session. Since some keys might not have values stored all the time, we're checking for the keys only. Whenever, a key and a value is placed in this function it will store the defined valued in the defined key.

get() function is created to retrieve values from session. So, it'll fetch the value from the session whose key is placed in the parameter.

check() function will check the key defined in parameter and return boolean value.

delete() function also takes key as parameter and deletes the value from that key when asked to. The messages stored while a user is performing a task is of no use once it's displayed. So, such values must be deleted from session instantly.

To make the session functions functionable, we need to start session in our application. So, lets go to the Configs.php file where we declared global variables and add the session_start() function on top.

MESSAGE CLASS

We stored the messages that need to be displayed to the users but it would be nice if we could display success and error messages in different ways for better user experience. That's the only reason, we are creating this class.

<?php
if(!function_exists('Message')){
	
	function Message(){
	
		if(isset($_SESSION['success'])){
			$class = 'alert-success';
			$message = $_SESSION['success'];
			unset ($_SESSION['success']);
		}

		if(isset($_SESSION['error'])){
			$class = 'alert-danger';
			$message = $_SESSION['error'];
			unset ($_SESSION['error']);
		}

		$output = '';
		if (isset($message)){
			$output .= "<div class='alert ".$class."'>";
			$output .= $message;
			$output .= "</div>";
		}
		return $output;
	}
}	

It's always a better idea to check for existence of any function before creating it to avoid conflict, so, we're checking if the function already exists. If not then we are storing those messages in a variable $message while adding bootstrap class alert-success in case of success and alert-danger in case of error in another variable $class and deleting those messages from session.

The output for the function is empty string initially. When a mesage is stored in session, it'll get the class value and the message value and return the same to the page.

REDIRECT CLASS

We need to redirect the users from one page to another depending on the tasks they performed or in situations where they attempt to access unauthorized pages. Rather than adding the header() function all the time, it'll be nice if we created a function that handles all of these events. For eample, a user submitted some data to the server from a page successfully. Sending him to the page where recently submitted data will be displayed along with the previous ones would be more appropriate than to keep him in the same page and display the success message and that will be the task of this class.

<?php
class Redirect
{
	public static function to($path=""){
		if(empty($path)) return false;
		if(strpos($path,'/')){
			$path = explode('/',$path); 
			$redirectPath = HTTP . $path[0] .  '/main.php?page=' .$path[1];			
			$header = header('Location: '.$redirectPath);
			exit();
		}else{
			header('Location: '.$path);
			exit();
		}
		
	}	
}	

First, we'll check if the path parameter is empty or not. Here, the rest of the codes totally depends on how you want to send the parameter via the function. In this example, we're sending the path as shown here ('admin/display-posts') though the url pattern for this application is http://localhost/pdo-project/admin/main.php?page=display-posts.

So, we check for that / symbol in the parameter, explode it and save it in a variable as array values then define the url as HTTP . $path[0] . '/main.php?page=' .$path[1] that'll be decoded as http://localhost/pdo-project/admin/main.php?page=display-posts which is the regular pattern for our application.

Then, we use the header() function to redirect the user and exit the function because the codes below it will still be processed after redirecting in absence of the exit function.

if there's no / symbol in the parameter, the redirecting location would be exactly the same that is passed as the parameter in this function.

CLASS TO PREVENT CROSS SITE REQUEST FORGERY (CSRF)

CSRF is an attack that forces a user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attack can force a user to transfer funds, modify their email accounts. In worst scenario, it can compromise the entire application if the user being attacked has administrative authority of the application. So, it's always better to think one step ahead and prevent such attacks.

To prevent such attacks, we'll create a random string value that'll be generated each time a form page is opened and that value will be stored in the session too. The form data will be submitted only in the case when both of these values get matched or the page will display an error.

Lets create a global array csrf at first that has a name csrf_token.

$GLOBALS['configs'] = [
	
	'database' => [
		'host' => 'localhost',
		'username' => 'root',
		'password' => '',
		'name' => 'pdo_project',
	],
	
	'csrf' => [
		'name' => 'csrf_token',
	]	
];	

TOKEN CLASS

Here, we'll create other functions related to the csrf_token.

<?php
class token
{
	private static function generate(){
		return session::put( 'csrf_token', md5(uniqid()) );
	}

	public static function input(){
		return "<input type='hidden' name='csrf_token' value='".self::generate()."'>";
	}

	public static function check($token){
		if(session::get('csrf_token') === $token){
			session::delete('csrf_token');
			return true;
		}
		return false;
	}
}	

generate() function will generate a uniqe id each time the page is refreshed and that'll be encrypted by md5() function and stored in session in the key csrf_token.

input function will generate a new csrf_token value that'll be hidden in the form page and sent to processing along with other form data that's where the check() function comes to action.

check() function will check if the token value being sent to the function via form page matches the token value in session. If it does the form will be sent otherwise not.

HASH CLASS

md5() function is a good way to encrypt a password but relatively easier to decrypt as it creates the same random string each time same password is processed. So, we use hash() function to encrypt passwords and it generates random string value even if the string being processed is same.

<?php
class Hash
{
	public static function make($string=""){
		return password_hash($string,PASSWORD_DEFAULT);
	}

	public static function decrypt($hash,$string){
		return password_verify($string,$hash);
	}
}	

make() function will encrypt the password using inbuilt function password_hash() that uses the CRYPT_BLOWFISH algorithm as default and creates a password in $2y$ crypt format which is always 60 characters wide.

decrypt() function will use inbuilt password_verify() function that accepts two parameters the value inserted in login password field and the password value from database and verifies if the password is correct or not and results a boolean value.

VALIDATION CLASS

We always need to validate the data being sent from the form before inserting it into the database to prevent unwanted inputs values and to be assured all input fields that we don't want empty are also filled up. Similarly while adding users, we might want to prevent two users with same name or email whichever we use as the key or login action. The minimum and maximum lengths of the data string might also need to hecked in some cases. So, we'll create a separate function that'll check the validation of all forms.

<?php
class Validation
{
	private $_errors = [];
	private $_db;

	function __construct()
	{
		$this->_db = Database::instantiate();
	}

	private function setErrors($field,$message){
		$this->_errors[$field] = $message;
	}

	public function getErrors(){
		return $this->_errors;
	}

	public function isValid(){
		if(empty($this->_errors)) return true;
		return false;
	}

	public static function displayErrors($key,$class = ''){
		$errors = session::get($key);
		$output = "";
		if(!empty($errors)){
			foreach ($errors as $error){
				$output .= "<div class='alert alert-danger'>";
				$output .= $error;
				$output .= "</div>";
			}
			session::delete($key);
			return $output;
		}
		return '';
	}
}	

The validation class is quite complexed and a bit long. So, we go with the first part of setting errors, getting errors, checking validation and displaying errors. But before that, we need the database connection. Hence, it's called in __construct() method so that it'll get instantiated whenever the class is called.

We declared two variables $errors as empty array and $_db as null. $_db will get databse instance as its object while $_errors will store validation errors that we'll discuss in the next section.

setErrors() function stores all the form field names as key and all the errors associated to them as values and store in $_errors array variable.

getErrors() funtion returns all those errors.

isValid() function checks if there's any error set or not and returns the value.

displayErrors() function is almost identical to Message() function that creates a key in session and displays the same key in view files in the HTML structure defined in $output. If there's no error, it'll return an empty string.

Now, let's go to the main section validation section.

public function validate($validationRules=array() ){
	
	if(empty($validationRules)) return false;
	
	foreach ($validationRules as $field => $rules){
	
		if(isset($_POST[$field])){
	
			foreach ($rules as $rule => $value){
	
				if($rule === 'required' && Request::post($field) === ''){
					$this->setErrors($field,$rules['label'] . ' can\'t be empty');
				}else if (Request::post($field) !== ''){
					
					switch($rule){
						
						case 'minlength':
						if(strlen(Request::post($field)) < $value){
							$this->setErrors($field,$rules['label'] . ' can\'t be less than ' . $value . ' characters');
						}
						break;
							
						case 'maxlength':
						if(strlen(Request::post($field)) > $value){
							$this->setErrors($field,$rules['label'] . ' can\'t be greater than ' . $value . ' characters');
						}
						break;
							
						case 'email':
						if(!filter_var(Request::post($field), FILTER_VALIDATE_EMAIL)){
							$this->setErrors($field,$rules['label'] . ' is not a valid email address');
						}
						break;
							
						case 'unique':
						$dbArray = explode('.',$value);
	
						if(count($dbArray) < 2) throw new Exception("Unique Requires table and column name");
						$tableName = $dbArray[0];
						$columnName = $dbArray[1];

						if(count($dbArray) > 3){
							$key = $dbArray[2];
							$value = $dbArray[3];

							$dataCount = $this->_db->count($tableName,$columnName.' = ? AND '.$key.' != ?',array(Request::post($field),$value));
						}else{
							$dataCount = $this->_db->count($tableName,$columnName.'=?',array(Request::post($field)));	
						}
	
						if($dataCount > 0){
							$this->setErrors($field,$rules['label'] . ' already exists');
						}
						break;

						case 'matches':
						if(Request::post($field) !== Request::post($rules['matches'])){
							$this->setErrors($field,$rules['label'] . ' doesn\'t match with the Password');
						}
					}
				}
			}
		}	
	}
}	

The validationRules array will be sent from view file. Here's a sample of the validation rules.

protected $validationRules = [
	'username' => [
		'required' => true,
		'minlength' => 3,
		'maxlength' => 20,
		'label' => 'Username', 
	],
	'email' => [
		'required' => true,
		'email' => true,
		'unique' => 'users.email',
		'label' => 'Email',
	],
	'password' => [
		'required' => true,
		'minlength' => 6,
		'label' => 'Password',
	],
	'cpassword' => [
		'required' => true,
		'matches' => 'password',
		'label' => 'Confirm Password',
	],
	'usertype' => [
		'required' => true,
		'label' => 'User Type',
	]
];
	

It's a multidimesional associative array where the first array value is the field name and the second array value is the validation rule and it's value is the third level array.

First, we'll break the first level array and iterate it in loop so that the field name and validation rule are separated.

foreach ($validationRules as $field => $rules){

We'll check if the value is set in the field. Then, we'll iterate the rule and rule values and process each of them individually.

if(isset($_POST[$field])){
	foreach ($rules as $rule => $value){

If the validation rule is required and the post field is empty, we'll set validation error as defined.

if($rule === 'required' && Request::post($field) === ''){
	$this->setErrors($field,$rules['label'] . ' can\'t be empty');	

For other rules, we'll use the swith statement and set errors individually. For minlength and maxlength, we'll count the string length using strlen() funtion and do the needful while filter_var() will check the email format and set errors in case of errors. Case matches will take values from two defined fields and checks if both values match to each other. If not, another error will be set with message too.

}else if (Request::post($field) !== ''){
	switch($rule){	
	case 'minlength':
	if(strlen(Request::post($field)) < $value){
		$this->setErrors($field,$rules['label'] . ' can\'t be less than ' . $value . ' characters');
	}
	break;
	
	case 'maxlength':
	if(strlen(Request::post($field)) > $value){
		$this->setErrors($field,$rules['label'] . ' can\'t be greater than ' . $value . ' characters');
	}
	break;
	
	case 'email':
	if(!filter_var(Request::post($field), FILTER_VALIDATE_EMAIL)){
		$this->setErrors($field,$rules['label'] . ' is not a valid email address');
	}
	break;

	case 'matches':
	if(Request::post($field) !== Request::post($rules['matches'])){
		$this->setErrors($field,$rules['label'] . ' doesn\'t match with the Password');
	}

Case unique is a bit different as you can see in the sample 'unique' => 'users.email'. We are sending the tablename along with the field name instead of any rule values. We'll use the count() function rom database with tablename, columnname and the value being sent as parameter in the function to chcek if same data already exists. If the count is greater than 0, it'll set another validation error.

WHILE INSERTING NEW DATA

case 'unique':
$dbArray = explode('.',$value);

if(count($dbArray) < 2) throw new Exception("Unique Requires table and column name");
$tableName = $dbArray[0];
$columnName = $dbArray[1];

$dataCount = $this->_db->count($tableName,$columnName.'=?',array(Request::post($field)));	

if($dataCount > 0){
	$this->setErrors($field,$rules['label'] . ' already exists');
}
break;	

WHILE UPDATING EXISTING DATA

Same validation rule for email won't work in case of update and we can't allow it to be a duplicate of any other data row too. Hence, we'll send user id as well while updating so that the rule is changed to be as shown below.

'unique' => 'users.email.id'.$id	

Now, if the exploded rulevalue $dbArray has more than 3 values then the third value in the array would be another key and the fourth value would be it's associated value. Then, we'll count the data from the same table and same column but exclude the id of the active user, so that, he can save his previously saved email account which wouldn't have been possible without this extended switch case.

if(count($dbArray) > 3){
	$key = $dbArray[2];
	$value = $dbArray[3];

	$dataCount = $this->_db->count($tableName,$columnName.' = ? AND '.$key.' != ?',array(Request::post($field),$value));
}	

Finally the case unique would look as shown below.

case 'unique':
$dbArray = explode('.',$value);

if(count($dbArray) < 2) throw new Exception("Unique Requires table and column name");
$tableName = $dbArray[0];
$columnName = $dbArray[1];

if(count($dbArray) > 3){
	$key = $dbArray[2];
	$value = $dbArray[3];

	$dataCount = $this->_db->count($tableName,$columnName.' = ? AND '.$key.' != ?',array(Request::post($field),$value));
}else{
	$dataCount = $this->_db->count($tableName,$columnName.'=?',array(Request::post($field)));	
}
						
if($dataCount > 0){
	$this->setErrors($field,$rules['label'] . ' already exists');
}
break;	

UPLOAD CLASS

<?php
class Upload
{
	private static $_upload_path = null;
	private static $_upload_size = null;
	private static $_upload_ext = array();
	private static $_upload_error = [
		1 => 'Larger than upload max_file_size',
		2 => 'MAX_FILE_SIZE',
		3 => 'Partial Uplaod',
		4 => 'No File',
		6 => 'No Temporary Directory',
		7 => 'Can\'t write to disk',
		8 => 'File Upload Prevented by Extension' 
	];
	private static $_errors = [];	

	public static function initialize($configs=array()){
		if(empty($configs)) throw new Exception("Please provide necessary configurations.");
		self::$_upload_ext = explode('|', $configs['upload_ext']);
		self::$_upload_path = rtrim($configs['upload_path'],'/').'/';
		self::$_upload_size = $configs['upload_size'];
		
	}

	public static function load($file=array()){
		if (empty($file)) throw new Exception("File Details Not Found");
		$ext = strtolower(pathinfo($file['name'],PATHINFO_EXTENSION));
		$filename = md5(time().rand()).'.'.$ext;
		$tmp = $file['tmp_name'];
		$size = $file['size'];
		$error = (int)$file['error'];

		if ($error !== 0){
			self::$_errors = self::$_upload_error[$error];
			return false;
		}

		if ($size > self::$_upload_size){
			self::$_errors = 'Maximum upload size is' . (self::$_upload_size / 1000000) . 'mb';
			return false;
		}

		if(!in_array($ext, self::$_upload_ext)){
			self::$_errors = 'Extension not allowed. Allowed file type is '.implode(',',self::$_upload_ext);
			return false;
		}

		if(!move_uploaded_file($tmp, self::$_upload_path.$filename)) throw new Exception("Problem while Uploading");
			return $filename;
	}

	public static function getErrors(){
		return self::$_errors;
	}
}	

First, we created all the required variable to upload an image and defined their default values as null for strings and empty array for arrays. We also added an associative array named $_upload_error with all the error numbers and their values so that we can replace the error number with the values.

initialize() function validates the file being uploaded where all validation rules are sent as array.

$configs['upload_ext'] = 'jpg|jpeg|png|gif';
$configs['upload_size'] = 2000000;
$configs['upload_path'] = ROOT.'admin/Upload/Users/';	

Valid extension file types will be sent in a string separated by pipes in upload_ext key while maximum upload size will be defined in bytes value in upload_size key.. Here, it's 2mb. And the path where the file is to be stored is defined in upload_path where the path is supposed to be ended by a / symbol.

load() function retrieves all the necessary informations from $_FILES array and checks for validation errors. If there's no validation errors, it'll upload the file in the defined location.

getErrors() funtion retrieves all the uploading errors that'll be stored in session and displayed in view files.