Validating POST data class - php

This is an extension of a question I asked earlier that was deemed to be unsafe practise, due to the use of eval(). So I went for another approach but I have run into a problem. I do not know how to convert it to a class. My attempt ends with an error when I try to use call_user_func_array. it can't find the function in the class. Can you give me some hint so I get going? Thanks!
The error message I get when I try to run my code is Warning: call_user_func_array() expects parameter 1 to be a valid callback, function 'testlength' not found or invalid function name but on all validation methods. This is what I don't understand. This is what I want help to understand why it does not work.
class ruleValidator
{
protected $postData = array();
protected $ruleSet = array();
var $exceptions = 'Å,Ä,Þ,å,ä,þ,Ø,Ö,Ð,ø,ö,ð,Æ,Ü,æ,ü,á,é,í,ñ,ó,ú,ü,Á,É,Í,Ñ,Ó,Ê,Ú,Ü,ß';
function __construct(){
$this->exceptions = explode(',',$exceptions);
}
function testlength($string,$threshold)
{
return strlen($string)<$threshold?
'Your %s is too short.': // TRUE
''; // FALSE
}
function testnumeric($string,$offset,$length,$switch=true)
{
if(is_numeric(substr($string,$offset,$length))===$switch)
{
return $switch?
'Your %s has to begin with a character.': // Truely TRUE
'Your %s is containing non numeric characters. Please enter only digits.'; // Falsely TRUE
}
}
function testemail($string)
{
return filter_var($string, FILTER_VALIDATE_EMAIL)?
'': // TRUE
'Your email is not in a valid form.'; // FALSE
}
function testpattern($string,$pattern='/^[0-9]{8,10}$/')
{
return preg_match($pattern, $string)?
'': // TRUE
'Your %s is entered incorrect. Please use the correct format when entering data.'; // FALSE
}
function testequalto($string1,$string2)
{
return $string1==$string2?
'': // TRUE
'Your %s fields do not match eachother.'; // FALSE
}
function testchecked($bool)
{
return $bool===true?
'': // TRUE
'You are required to check this %s to continue.'; // FALSE
}
function testspecchar($string,$excludes=array())
{
if(is_array($excludes)&&!empty($excludes))
{
foreach($excludes as $exclude)
{
$string=str_replace($exclude,'',$string);
}
}
if(preg_match('/[^a-z0-9 ]+/i',$string))
{
return 'Your %s contains illegal characters.'; // TRUE
}
return; // FALSE
}
}
This is an array with how the POST data is recieved in the validator and the rules I use for the different fields in the form.
$exceptions = explode(',','Å,Ä,Þ,å,ä,þ,Ø,Ö,Ð,ø,ö,ð,Æ,Ü,æ,ü,á,é,í,ñ,ó,ú,ü,Á,É,Í,Ñ,Ó,Ê,Ú,Ü,ß');
$postData = array
(
'name' => 'Mikael',
'familyname' => 'Eriksson`',
'username' => 'Mik',
'password' => 'testtest',
'password-confirm' => 'testtesty',
'email' => 'try.to#guess.it,se',
'phone' => '0000000000a',
'policy' => 0
);
$ruleSet = array
(
'name'=>array
(
'testlength'=>2,
'testnumeric'=>array(0,1),
'testspecchar'=>array($exceptions)
),
'familyname'=>array
(
'testlength'=>2,
'testnumeric'=>array(0,1),
'testspecchar'=>array($exceptions)
),
'username'=>array
(
'testlength'=>4,
'testnumeric'=>array(0,1),
'testspecchar'=>array()
),
'email'=>array
(
'testemail'=>array()
),
'phone'=>array
(
'testnumeric'=>array(0,strlen($postData['phone']),false),
'testpattern'=>'/^[0-9]{8,10}$/'
),
'password'=>array
(
'testlength'=>8
),
'password-confirm'=>array
(
'testequalto'=>$postData['password-confirm']
),
'policy'=>array
(
'testchecked'=>array()
)
);
Here is how I validated the data up until now. It works, but I want to make this to a class to streamline the code in my project.
foreach($postData as $key => $value)
{
if(!array_key_exists($key,$ruleSet))
{
$errors[] = "The field `$key` is not part of the form. Only send actual form data.";
break;
}
$slice = array($key=>$ruleSet[$key]);
foreach($slice as $rules => $rule)
{
foreach($rule as $rls => $r)
{
$r = array_merge((array)$value,(array)$r);
$errors[] = sprintf(call_user_func_array($rls,$r),$key);
}
}
}
if(count($errors)>0) return implode(';;',array_filter($errors,'strlen'));

When you want to call a method of a class, you have to make an instance (using new) or call them statically when the methods are declared static.
In both ways, you have to tell call_user_func_array() that you are not calling a function in the global scope, but from within a class.
call_user_func_array(array('ruleValidator', $rls), $r)
Then declare the functions static:
public static function testlength($string,$threshold) {
}
Or with new:
$slice = array($key=>$ruleSet[$key]);
$callbackClass = new ruleValidator();
foreach($slice as $rules => $rule)
/** ... */
call_user_func_array(array($callbackClass, $rls), $r)

Thanks #Deadooshka for providing me with the solution.
call_user_func_array("ruleValidator::$rls", $r)

Related

If Class exists && If Method exists PHP / Laravel

PHP/Laravel
Hey, I'm moving into abstraction in php and am attempting to validate and store values based on whatever has been submitted, where I expect that the methods should neither know what to validate against and/or which class and method to use to do so -
What I've got works but I can see that there would be issues where classes/methods do not exist. Here lays my question.
If I were to call a method in the following format, which way would be best to 'check' if class_exists() or the method exists()?
public function store(Request $request)
{
$dataSet = $request->all();
$inputs = $this->findTemplate();
$errors = [];
$inputValidators = [];
foreach ($inputs as $input) {
$attributes = json_decode($input->attributes);
if (isset($attributes->validate)) {
$inputValidators[$input->name] = $input->name;
}
}
foreach ($dataSet as $dataKey => $data) {
if (array_key_exists($dataKey, $inputValidators)) {
$validate = "validate" . ucfirst($dataKey);
$validated = $this->caseValidator::{$validate}($data);
if ($validated == true) {
$inputValidators[$dataKey] = $data;
} else {
$errors[$dataKey] = $data;
}
} else {
$inputValidators[$dataKey] = $data;
}
}
if (empty($errors)) {
$this->mapCase($dataSet);
} else {
return redirect()->back()->with(['errors' => $errors]);
}
}
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}
For some additional context, an input form will consist of a set of inputs, this can be changed at any time. Therefore I am storing all values as a json 'payload'.
When a user submits said form firstly the active template is found, which provides details on what should be validated $input->attributes, once this has been defined I am able to call functions from caseValidator model as $this->caseValidator::{$validate}($data);.
I do not think that any checks for existence will be needed here as the validation parameters are defined against an input, thus if none exist this check will be skipped using if (array_key_exists($dataKey, $inputValidators))
However, I am dispersing some data to other tables within the second block of code using mapCase(). This is literally iterating over all array keys regardless of if a method for it exists and thus the initial check cannot be made as seen in the first block. I've attempted to make use of class_exists() and method_exists but logically it does not fit and I cannot expect them to work as I'd like, perhaps my approach in mapCase is not correct? I guess if I'm defining a class for each key I should instead use one class and have methods exist there, which would remove the need to check for the class existing. Please advise
Reference:
$attribute = $this->{$model}::{$method}($dataKey);
Solved the potential issue by using class_exists(), considering I know the method names as they are the same as the $dataKey.
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
if (class_exists("App\Models\CaseRepository\\" . $model)) {
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
}
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}

Codeigniter Passing Extra Parameters to Custom Validation Rule

Based on this documentation , how to pass second parameter to the rule method?
This is my custom rule
public function email_exists($email, $exclude_id=NULL)
{
if ( $exclude_id !== NULL ) $this->db->where_not_in('id', $exclude_id);
$result = $this->db->select('id')->from('users')->where('email', $email)->get();
if ( $result->num_rows() > 0 ) {
$this->form_validation->set_message('email_exists', '{field} has been used by other user.');
return FALSE;
} else {
return TRUE;
}
}
and this is how i call it from controller
$rules = [
[
'field' => 'email',
'label' => 'Email',
'rules' => [
'required',
'trim',
'valid_email',
'xss_clean',
['email_exists', [$this->m_user, 'email_exists']]
]
]
];
$this->form_validation->set_rules($rules);
How can I pass second parameter to email_exists method?
Its seems CI does not provide a mechanism for this. I found several approaches to solve this. First way, you can hack the file system (Form_validation.php) and modify some script at line 728
if ( preg_match('/(.*?)\[(.*)\]/', $rule[1], $rulea) ) {
$method = $rulea[1];
$extra = $rulea[2];
} else {
$method = $rule[1];
$extra = NULL;
}
$result = is_array($rule)
? $rule[0]->{$method}($postdata, $extra)
: $rule($postdata);
Second way you can extends CI_Form_validation core and add your custom rule in it. I found the detail about this on codeigniter documentation.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation
{
public function __construct()
{
parent::__construct();
}
public function check_conflict_email($str, $exclude_id=NULL)
{
if ( $exclude_id !== NULL ) $this->CI->db->where_not_in('id', $exclude_id);
$result = $this->CI->db->select('id')->from('users')->where('email', $str)->get();
if ( $result->num_rows() > 0 ) {
$this->set_message('check_conflict_email', '{field} has been used by other user.');
return FALSE;
} else {
return TRUE;
}
}
}
/* End of file MY_Form_validation.php */
/* Location: ./application/libraries/MY_Form_validation.php */
Third way, and I think this is the best way to do it. Thanks to skunkbad for provide the solution
$rules = [
[
'field' => 'email',
'label' => 'Email',
'rules' => [
'required',
'trim',
'valid_email',
'xss_clean',
[
'email_exists',
function( $str ) use ( $second_param ){
return $this->m_user->email_exists( $str, $second_param );
}
]
]
]
];
Just do it the right way (at least for CI 2.1+) as described in the docs:
$this->form_validation->set_rules('uri', 'URI', 'callback_check_uri['.$this->input->post('id').']');
// Later:
function check_uri($field, $id){
// your callback code here
}
If this is not working than make an hidden field in your form for $exclude_id and check that directly in your callback via
$exclude_id = $this->input->post('exclude_id');//or whatever the field name is
More here
I use CI 3.1.10 and this issue still exists, I extend the library and use the same way as callback
array('username_callable[param]' => array($this->some_model, 'some_method'))
Extended Form_validation library:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation {
/**
* Executes the Validation routines
*
* #param array
* #param array
* #param mixed
* #param int
* #return mixed
*/
protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
{
// If the $_POST data is an array we will run a recursive call
//
// Note: We MUST check if the array is empty or not!
// Otherwise empty arrays will always pass validation.
if (is_array($postdata) && ! empty($postdata))
{
foreach ($postdata as $key => $val)
{
$this->_execute($row, $rules, $val, $key);
}
return;
}
$rules = $this->_prepare_rules($rules);
foreach ($rules as $rule)
{
$_in_array = FALSE;
// We set the $postdata variable with the current data in our master array so that
// each cycle of the loop is dealing with the processed data from the last cycle
if ($row['is_array'] === TRUE && is_array($this->_field_data[$row['field']]['postdata']))
{
// We shouldn't need this safety, but just in case there isn't an array index
// associated with this cycle we'll bail out
if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
{
continue;
}
$postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
$_in_array = TRUE;
}
else
{
// If we get an array field, but it's not expected - then it is most likely
// somebody messing with the form on the client side, so we'll just consider
// it an empty field
$postdata = is_array($this->_field_data[$row['field']]['postdata'])
? NULL
: $this->_field_data[$row['field']]['postdata'];
}
// Is the rule a callback?
$callback = $callable = FALSE;
if (is_string($rule))
{
if (strpos($rule, 'callback_') === 0)
{
$rule = substr($rule, 9);
$callback = TRUE;
}
}
elseif (is_callable($rule))
{
$callable = TRUE;
}
elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
{
// We have a "named" callable, so save the name
$callable = $rule[0];
$rule = $rule[1];
}
// Strip the parameter (if exists) from the rule
// Rules can contain a parameter: max_length[5]
$param = FALSE;
if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
{
$rule = $match[1];
$param = $match[2];
}
elseif ( is_string($callable) && preg_match('/(.*?)\[(.*)\]/', $callable, $match))
{
$param = $match[2];
}
// Ignore empty, non-required inputs with a few exceptions ...
if (
($postdata === NULL OR $postdata === '')
&& $callback === FALSE
&& $callable === FALSE
&& ! in_array($rule, array('required', 'isset', 'matches'), TRUE)
)
{
continue;
}
// Call the function that corresponds to the rule
if ($callback OR $callable !== FALSE)
{
if ($callback)
{
if ( ! method_exists($this->CI, $rule))
{
log_message('debug', 'Unable to find callback validation rule: '.$rule);
$result = FALSE;
}
else
{
// Run the function and grab the result
$result = $this->CI->$rule($postdata, $param);
}
}
else
{
$result = is_array($rule)
? $rule[0]->{$rule[1]}($postdata, $param)
: $rule($postdata);
// Is $callable set to a rule name?
if ($callable !== FALSE)
{
$rule = $callable;
}
}
// Re-assign the result to the master data array
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
elseif ( ! method_exists($this, $rule))
{
// If our own wrapper function doesn't exist we see if a native PHP function does.
// Users can use any native PHP function call that has one param.
if (function_exists($rule))
{
// Native PHP functions issue warnings if you pass them more parameters than they use
$result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata);
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
else
{
log_message('debug', 'Unable to find validation rule: '.$rule);
$result = FALSE;
}
}
else
{
$result = $this->$rule($postdata, $param);
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
// Did the rule test negatively? If so, grab the error.
if ($result === FALSE)
{
// Callable rules might not have named error messages
if ( ! is_string($rule))
{
$line = $this->CI->lang->line('form_validation_error_message_not_set').'(Anonymous function)';
}
else
{
$line = $this->_get_error_message($rule, $row['field']);
}
// Is the parameter we are inserting into the error message the name
// of another field? If so we need to grab its "field label"
if (isset($this->_field_data[$param], $this->_field_data[$param]['label']))
{
$param = $this->_translate_fieldname($this->_field_data[$param]['label']);
}
// Build the error message
$message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param);
// Save the error message
$this->_field_data[$row['field']]['error'] = $message;
if ( ! isset($this->_error_array[$row['field']]))
{
$this->_error_array[$row['field']] = $message;
}
return;
}
}
}
}

Use Trait Function with Same Name but Optionally

PHP Class Using Same Name as Trait Function
Refer to the question I just asked above here. Here was my original code.
trait sampletrait{
function hello(){
echo "hello from trait";
}
}
class client{
use sampletrait;
function hello(){
echo "hello from class";
//From within here, how do I call traits hello() function also?
}
}
I can call the trait function like this thanks to the answer to the question.
class client{
use sampletrait {
hello as protected sampletrait_hello;
}
function hello(){
$this->sampletrait_hello();
echo "hello from class";
}
}
My question is if my class client did not have a function hello() but wanted to call it is this possible?
So for example...
trait sampletrait{
function hello(){
echo "hello from trait";
}
}
class client{
use sampletrait {
hello as protected sampletrait_hello;
}
}
I'm aware that I could just simply say use sampletrait; and it would have the function but in my use case I can't do that either. Is it possible to have the aliased name but still use the trait name default if it does not exist in the class?
Extra Information
My exact use case involves PHP-ActiveRecord
I have a trait called uniquecheck
trait uniquecheck {
//#JA - Used temporarely to determine if editing for the unique checker
static $isEditing = false;
//#JA - This is used by PHPActiveRecord to trigger events before validation on update calls only.
static $before_validation_on_update = array('before_validation_on_update_callback');
//#JA - This is function used as callback from PHPActiveRecord
public function before_validation_on_update_callback(){
self::$isEditing = true; //#JA - Requires Uniquecheck trait to work
}
//#JA - This function can do single and multi-unique checks.
//#JA - This is programmed to be replaced at a later date when validates_uniqueness_of is fixed (http://www.phpactiverecord.org/projects/main/wiki/Validations#validates_uniqueness_of)
//#JA - EXAMPLES
//SINGLE -- array('name','message' => 'Can't do this')
//MULTIPLE -- array( array('name1','name2'), 'message' => 'can't do this and that together')
//#JA - To be clear multiple does not mean 2 different uniques but a unique on 2 columns. Just use this function twice for 2 separate unique checks.
public function uniquecheck($rules = array()) {
$classname = get_class($this);
//#JA - Basic validation to confirm assumptions for function properties
if(count($rules)<=0){
die('uniquecheck.php -> Property array can not be empty');
}
//#JA - If its an array use the MULTIPLE method
if(is_array($rules[0])){
//#JA - First create the condition string
$conditionstring = '';
$conditionarray = array();
$uniques = $rules[0];
foreach($uniques as $unique){
$conditionstring .= "$unique = ? AND ";
}
$conditionstring = substr($conditionstring, 0, -5);
//#JA - Then generate the array we will use for the conditions
$conditionarray['conditions'][] = $conditionstring;
foreach($uniques as $unique){
$conditionarray['conditions'][] = $this->read_attribute($unique);
}
$results = $classname::find('all',$conditionarray);
if($classname::$isEditing == true){
die('was editing');
}else{
die('was creating');
}
//#JA - If in edit mode, if the values are exactly the same as it was before then ignore this check.
if (count($results)>=1) {
foreach($uniques as $unique){
$this->errors->add($unique, $rules['message']);
}
}
}else{ //#JA - Otherwise use the SINGLE method
$unique = $rules[0];
$results = $classname::find('all',array('conditions' => array("$unique = ?", $this->read_attribute($unique))));
//#JA - If there is more then 1 result then its not unique!
if (count($results)>=1) {
$this->errors->add($unique, $rules['message']);
}
}
}
}
?>
I use this in my model Client like so...
class Client extends ActiveRecord\Model {
use foreignkeycheck;
use uniquecheck {
before_validation_on_update_callback as protected uniquecheck_before_validation_on_update_callback;
}
static $before_destroy = array('before_destroy_callback');
//#gv hide columns that are not in use right now
static $columnsToHide = array(
'affiliate_code',
'autopay',
'stripe_customer_id',
'quickbooks_client_id',
'stripe_customer_info',
'stripe_customer_info_last_update',
'textingnumber'
);
static $easy_name = "Client";
static $validates_presence_of = array(
array('clienttype_id'),
array('company_id'),
array('contactfirstname'),
array('contactlastname'),
array('contactphonenumber')
);
static $validates_size_of = array(
array('contactfirstname', 'within' => array(1, 50)),
array('contactlastname', 'within' => array(1, 50)),
array('contactaddress', 'within' => array(1, 120), 'allow_null' => false),
array('companyaddress', 'within' => array(1, 120), 'allow_null' => true),
array('companyname', 'within' => array(1, 75), 'allow_null' => true),
);
// static $validates_uniqueness_of = array(
// array('affiliate_code', 'allow_null' => true),
// array(array('contactfirstname', 'contactlastname', 'contactemail', 'contactphonenumber', 'contactaddress'),
// 'message' => 'Can\'t have duplicate client.')
// );
static $validates_format_of = array(
array('contactemail', 'with' => '/\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,10}\b/sim',
'message' => 'Must be a correctly formatted email.', 'allow_blank' => true, 'allow_null' => true),
array('companyemail', 'with' => '/\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,10}\b/sim',
'message' => 'Must be a correctly formatted email.', 'allow_blank' => true, 'allow_null' => true),
array('companyphonenumber', 'with' => '/^(\d[\s-]?)?[\(\[\s-]{0,2}?\d{3}[\)\]\s-]{0,2}?\d{3}[\s-]?\d{4}$/i',
'message' => 'Phone number is invalid', 'allow_blank' => true, 'allow_null' => true),
array('contactphonenumber', 'with' => '/^(\d[\s-]?)?[\(\[\s-]{0,2}?\d{3}[\)\]\s-]{0,2}?\d{3}[\s-]?\d{4}$/i',
'message' => 'Phone number is invalid', 'allow_blank' => true, 'allow_null' => false)
);
//This allows you to use your own as well as still call the uniquechecks before_validation callback in case this method is not needed.
public function before_validation_on_update_callback(){
$this->uniquecheck_before_validation_on_update_callback();
}
public function before_destroy_callback(){
$conn = SELF::connection();
$conn->transaction();
try {
//USER *********
//Delete the associated user as well.
$related_users = User::find('all',array(
'conditions' => array(
'client_id' => $this->id)
));
foreach($related_users as $user){
$user->delete();
}
//PROPERTIES ********
//Delete all properties of the client, which in turn delets all routes & visits
$related_properties = Property::find('all',array(
'conditions' => array(
'client_id' => $this->id)
));
foreach($related_properties as $property){
$property->delete();
}
//Only have to delete the user, because deletes will cascade down
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
}
return true; //will actually delete the client now.
}
public function validate() {
//Thought about putting user validation in here, but decided against it.
//Multi-unique check FAILS to work if the parameter is not passsed for one of the multi-uniques. This is BUG in PHP Active Record.
//Does not show message correctly for multi-uniques either. This is ALSO a bug in PHP Active Record.
//#JA - Uses multi-unique check. Its only not allowed if all 4 of these values are the same since its obviously duplicate at that point
$this->uniquecheck(array(array('company_id','contactfirstname', 'contactlastname', 'contactphonenumber', 'contactaddress'),'message' => 'Can\'t have duplicate client.'));
$this->foreignkeycheck('Clienttype');
$this->foreignkeycheck('Company');
$this->foreignkeycheck('Affiliate', 'affiliate_code', true); //Special case where foreign key is not _id, true sent to indicate validate is optional only if a value is not null.
}
public function getReadableColumnNames($flip = false) {
$readableColumns = array();
$readableColumns["contactfirstname"] = "First Name";
$readableColumns["contactlastname"] = "Last Name";
$readableColumns["contactphonenumber"] = "Phone Number";
$readableColumns["contactemail"] = "Email";
$readableColumns["contactaddress"] = "Address";
$readableColumns["companyaddress"] = "Company Address";
$readableColumns["companyemail"] = "Company Email";
$readableColumns["companyname"] = "Company Name";
$readableColumns["companyphonenumber"] = "Company Phone #";
$readableColumns["affiliate_code"] = "Affiliate #";
$readableColumns["clienttype_id"] = "Client Type";
$readableColumns["company_id"] = "Company";
$readableColumns["stripe_customer_id"] = "Stripe Customer ID";
$readableColumns["stripe_customer_info"] = "Stripe Customer Info";
$readableColumns["stripe_customer_info_last_update"] = "Stripe Info Last Update";
$readableColumns["welcome_email_sent"] = "Welcome Email Sent?";
$readableColumns["autopay"] = "Auto Pay?";
$readableColumns["active"] = "Active?";
if ($flip == true) {
$readableColumns = array_flip($readableColumns); //swap keys and values~
}
return $readableColumns;
}
public function getDefaultColumns() {
$defaultColumns = array();
$defaultColumns[] = "contactfirstname"; //first sort order
$defaultColumns[] = "contactlastname"; //second sort order
$defaultColumns[] = "contactphonenumber";
$defaultColumns[] = "contactemail"; //etc...
return $defaultColumns;
}
public function getColumnExceptions() {
$tableNames = array();
return $tableNames;
}
public function getBatchActions() {
$batchActions = array();
//$batchActions['Text to Appear'] = 'ClassName'
//For JS File To Call Correct Function ^^^^
//Order of array determines order in respective dropdown menu.
$batchActions["Make Inactive"] = "batch_make_inactive";
$batchActions["Send Email"] = "batch_send_email";
$batchActions["Send Welcome Email"] = "batch_send_client_welcomeEmail";
return $batchActions;
}
public function getRowActions() {
$rowActions = array();
$rowActions["Edit"] = array("edit_typename", true); //Call generic typename edit function, true means this is the item that shows first.
$rowActions["View Pictures"] = array("view_pictures_for_client", false); //shortcut to prefill information for property~
$rowActions["Add Property"] = array("add_property_for_client", false); //shortcut to prefill information for property~
//$rowActions["Update Quickbooks"] = array("qb_update_customer", false); //shortcut to add customer to quickbooks if connected.
$rowActions["Create User ID"] = array("create_userid_for_client", false); //shortcut method to create user_id straight from the client~
$rowActions["Send Welcome Email"] = array("send_client_welcome_email", false);
$rowActions["Make Inactive"] = array("allinactive_client", false); //will make the user inactive, property and user_id, along with recurring invoices, estimates, invoices that were referenced by client.
$rowActions["Make Active"] = array("allactive_client", false);
$rowActions["Delete"] = array("delete_typename", false); //call to generic typename delete function
//#gv Functions that do not work and not part of Release 1.0
//$rowActions["Add Estimate"] = array("add_estimate_for_client",false); //shortcut to prefill information for property~
//$rowActions["Add Invoice"] = array("add_invoice_for_client",false); //shortcut to prefill information for property~
//$rowActions["Add To Quickbooks"] = array("qb_add_customer",false); //shortcut to add customer to quickbooks if connected.
//$rowActions["Make Inactive"] = array("inactive_typename",false); //Way to filter results if you desired by clients that are not relevant anymore.
//$rowActions["Send Email"] = array("send_client_email",false);
//$rowActions["Send Text"] = array("text_client",false);
return $rowActions;
}
public function getColumnInterestedColumns() {
$columnInterestedColumns = array();
$columnInterestedColumns["clienttype_id"] = array("name");
$columnInterestedColumns["company_id"] = array("companyname");
$columnInterestedColumns["client_id"] = array("contactfirstname", "contactlastname"); //external reference.
return $columnInterestedColumns;
}
//This function indicates to the UI what fields are dependent upon others for purpose of 'flow' for new and edit areas.
//Happens in 2 areas, on initial PHP creation uses this to hide the field, and upon the restricted fields parent values taking on a value or losing a value.
public function getColumnRestrictions() {
global $user;
$restrictedColumns = array();
//$restrictedColumns["property_id"] = array("client_id");//this means that property_id can not show in UI until client_id is set.
return $restrictedColumns;
}
}
?>
I am trying to use this to get around phpactiverecords unique check bug since it does not work in there system for a project I'm working on.
It uses a callback like this (before_validation_on_update_callback), where it has to have that name.
I wanted to use a trait to include it in all my models for unique checking easily.
Refer to this (http://www.phpactiverecord.org/projects/main/wiki/Callbacks)
Try setting public on the trait function and then protected when you rename
trait sampletrait{
public function hello(){
echo "hello from trait";
}
}
class client{
use sampletrait {
hello as protected sampletrait_hello;
}
}
$c = new client();
$c->hello();
As said here PHP Class Using Same Name as Trait Function both hello and sampletrait_hello will exist, but as hello is public and sampletrait_hello protected only hello will be callable from an outer scope.
And if you overwrite hello, you will be able to call sampletrait_hello inside it.

Maintain Element in PHP Array And Update in PHP Class

I have one PHP class as below (part of the code):
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
}
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
The calling code in index.php
$msg = 'Michael,18';
myclass::getHandle()->doProcess($msg);
In my webpage says index.php, it calls function doProcess() over and over again. When the function is called, string is passed and stored in an array. In the next call, if let's say same name is passed again, I want to update his age. My problem is I don't know how to check if the array $arrX contains the name. From my own finding, the array seems to be re-initiated (back to zero element) when the code is called. My code never does the update and always go to the array_push part. Hope somebody can give some thoughts on this. Thank you.
There is a ) missing in your else condition of your doProcess() function, it should read:
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
)); // <-- there was the missing )
}
Here is a complete running solution based on your code:
<?php
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
for ($i=0; $i<count(self::$arrX); $i++) {
if (is_array(self::$arrX[$i]) && self::$arrX[$i]['name'] == $det[0]) {
self::$arrX[$i]['age'] = $det[1];
break;
}
}
} else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
$mc = new myclass();
$mc->doProcess('Michael,18');
$mc->doProcess('John,23');
$mc->doProcess('Michael,19');
$mc->doProcess('John,25');
print_r($mc->returnProcess());
?>
You can test it here: PHP Runnable
As I said in comments, it looks like you want to maintain state between requests. You can't use pure PHP to do that, you should use an external storage solution instead. If it's available, try Redis, it has what you need and is quite simple to use. Or, if you're familiar with SQL, you could go with MySQL for example.
On a side note, you should read more about how PHP arrays work.
Instead of array_push, you could have just used self::$arrX[] = ...
Instead of that, you could have used an associative array, e.g. self::$arrX[$det[0]] = $det[1];, that would make lookup much easier (array_key_exists etc.)
Can you try updating the is_val_exists as follows:
private function is_val_exists($needle, $haystack) {
foreach($haystack as $element) {
if ($element['name'] == $needle) {
return true;
}
return false;
}

optimising php validator to OOP

I need a php validator class that validates user inputs.
I want it to be able to accept an assoc array of fields => values like:
array(
"username" => "Alex",
"email_address" => "###3423£alex#my.mail.com"
);
and then return an array of errors like this:
array(
"username" => "",
"email_address" => "Invalid Email Address"
);
But I'm really struggling on HOW the hell I'm going to do this!
I've read countless pages on PHP validators and read that the best way to do this is with the strategy pattern. But i dont know how??
Like... This is what I've got so far:
class Validator {
private
$_errors,
$_fields,
static private $_map = array (
"firstname" => "name",
"surname" => "name",
"agency_name" => "name",
"agency_office" => "name",
"username" => "username",
"email_address" => "email_address",
);
public function __construct( array $fields ) {
$this->_fields = $fields;
}
public function validate() {
foreach ( $this->_fields as $field => $value ) {
if ( method_exists( __CLASS__, self::$_map[$field] ) ) {
if ( in_array( $field, self::$_map ) ) {
$this->{self::$_map[$field]}( $field, $value );
}
}
else {
die( " Unable to validate field $field" );
}
}
}
public function get_errors() {
return $this->_errors;
}
private function name( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z]{2,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 2 to 50 alphanumerical characters";
}
}
private function username( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9_\-]{10,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 10 to 50 characters. Can contain digits, characters, _ (underscore) and - (hyphen)";
}
}
private function password( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9\.\-]{8,30}$/", $value ) ) {
$this->_errors[$field] = "Invalid. Must be 8 to 30 characters. Can contain digits, characters, . (full stop) and - (hyphen)";
}
}
private function email_address( $field, $value ) {
if ( !filter_var( $value, FILTER_VALIDATE_EMAIL ) ) {
$this->_errors[$field] = "Invalid Email Address";
}
}
}
The problems with this is, it doesn't even consider database connections for like, already registered usernames,
Also is doesn't match passwords
I've just got coders block at the moment and its destroying me on the inside :(
Can anybody give a an explaination of the classes required and functions each class will need to do?
I really need the inputs and outputs to be in the format already explained though!
Thankyou Very Much Internet People!
As a part of the my MVC I have solved the same problem. I could give you a listing, but in a few lines try to describe how.
I got 3 base classes Form, Validator, Field, each of object of this classes configuring through one YAML file, structured somehow like this:
name: // field name
i18n: [ ru, en ] // is the field i18n
field:
class: Base // class to use for field
options: { specific_save: true } // options from available (defined in class)
attributes: { } // attributes, for HTML rendering
validator:
class: String // Class to validate with
options: { required: true, max: 100 } // options for validator
So, lets start with Form, when object is constructing the form takes the YAML file described above, and due to that configuration creates fields. Something like this:
// Imlement this function to configure form;
foreach ($this->_config as $f => $c)
{
$class = '\\Lighty\\Form\\Field\\' . (isset($c['field']['class']) && $c['field']['class'] ? $c['field']['class'] : 'Base');
$o = isset($c['field']['options']) && is_array($c['field']['options']) ? $c['field']['options'] : array();
$a = isset($c['field']['attributes']) && is_array($c['field']['attributes']) ? $c['field']['attributes'] : array();
$field = new $class($this, $o, $a);
$field->setName($f);
$class = '\\Lighty\\Form\\Validator\\' . (isset($c['validator']['class']) && $c['validator']['class'] ? $c['validator']['class'] : 'Base');
$o = isset($c['validator']['options']) && is_array($c['validator']['options']) ? $c['validator']['options'] : array();
$m = isset($c['validator']['messages']) && is_array($c['validator']['messages']) ? $c['validator']['messages'] : array();
$field->setValidator($validator = new $class($field, $o, $m));
if (isset($this->_options['default'][$f]))
{
$field->setValue($this->_options['default'][$f]);
}
if (isset($c['i18n']))
{
if (is_array($c['i18n']))
{
$field->setCultures($c['i18n']);
}
$field->setI18n((bool) $c['i18n']);
}
$this->addField($field);
So, now we have form with fields and validator for each field, then to validate I use this mechanism:
Form goes through each field, calling validate() method,
Field (got the binded value) call validate($value) method of binded Validator, passing the stored value. Inside this method Validator calls the validateOption() method, in which there is a simple switch for each options, for example:
switch ($o)
{
case 'required':
$valid = $state && trim($value) != '' || !$state;
break;
default:
return \warning(sprintf('Undefined validator option "%s" in %s validator class', $o, get_class($this->getField()->getValidator())), null);
}
Here you can see validating on required option. If I need more validators, I extend class of the Base validator, defined few more options, and redefine validateOption(), where in default statement of the option's switch put parent::validateOption(). So specified options validates in new class, and old one in base validator Class.
If there any questions... You're welcome.

Categories