Variable scope - php - Using variables from one function in another - php

I am having trouble using a variable generated in one function, as a variable in a second function.
The Problem:
I get Notice: Undefined variable: parameter in the validate function, on the line:
$this->$methodName($item,$value,$parameter) OR $valid=false;
When the function call for splitRulesAndParameters is simply replaced with the code within the function, the problem goes away.
The Scenario:
The following two functions are both within the Validator class, the first, validate, makes use of the second, splitRulesAndParameters
Here is the validate function:
public function validate($data, $rules)
{
$valid= true;
foreach($rules as $item=>$ruleSet)
{
$ruleSetArray=explode('|',$ruleSet);
foreach($ruleSetArray as $rule)
{
$this->splitRulesAndParameters($rule);
$methodName='validate'.ucfirst($rule);
$value = isset($data[$item]) ? $data[$item] : NULL;
if(method_exists($this, $methodName))
{
$this->$methodName($item,$value,$parameter) OR $valid=false;
}
}
}
return $valid;
}
And here is the splitRulesAndParameters function
public function splitRulesAndParameters($rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
$parameter = substr($rule,$position + 1);
$rule = substr($rule,0,$position);
}
else
{
$parameter='';
}
}

Seeing as the problem goes away if you "inline" the code in splitRulesAndParameters, I suspect the $parameters variable is used in that method. If so, simply have that method return the value of this variable, and assign it to a variable local to the validate method you've posted here:
$parameters = $this->splitRulesAndParameters($rule);
After adding this to the splitRulsAndParameters method:
return $parameters;
The method itself also modifies the $rule value. Again: this $rule variable is local to each method. It may have the same name, but the value is a copy. Any changes you make to $rule in splitRulesAndParameters is not reflected by $rule in your validate method. If I were you, I'd write:
public function splitRulesAndParameters($rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
return array(
'parameter' => substr($rule, $position+1),
'rule' => substr($rule, 0, $position)
);
}
return array(
'parameter' => null,//no param == null, IMO, change to '' if you want
'rule' => $rule
);
}
Then, to change the variables in validate:
$split = $this->splitRulesAndParameters($rule);
$rule = $split['rule'];
$parameter = $split['parameter'];
That ought to do it.
Side-note:
You seem to be validating everything that needs validating, even if the first validation failed. If I were you, I'd change this fugly statement:
$this->$methodName($item,$value,$parameter) OR $valid=false;
To a more efficient:
if (!$this->{$methodName}($item, $value, $parameter))
return false;//if validation fails, return false
That stops any further valiation from being executed: if one value is invalid, then just stop there. To continue is pointless, because the data-set is not entirely valid anyway.
Bonus:
Using a colon to separate the method name, and some parameter(s) does allow you to specify multiple params, too, and it allows you to simplify the splitRulesAndParameters some more:
protected function splitRulesAndParameters($rule)
{
$all = explode(':', $rule);
return array(
'rule' => array_shift($all),//removes first element in array
'params' => $all//rest of the array
);
}
Tweak this a little to better suite your needs

You can't just use a variable from inside a function in another function. You have to return the variable $parameter. Add a return statement to the end of splitRulesAndParameters and store the result in a variable inside validate ($parameter = $this->spli...).

You actually have two problems here, because you change the &rule variable inside your function, but you are passing it by reference. So after the fucntion is done the $rule variable is the same as it was before.
The way to solve this in the manner you are doing it right now would be to change the function to:
public function splitRulesAndParameters(&$rule)
{
$position = strpos($rule, ':');
if($position !==false)
{
$parameter = substr($rule,$position + 1);
$rule = substr($rule,0,$position);
}
else
{
$parameter='';
}
return $parameter;
}
and change the line
$this->splitRulesAndParameters($rule);
to
$parameter = $this->splitRulesAndParameters($rule);

Related

PHP change create_function to anonymous function

I have inherited some old code and need to convert the create_function to an anonymous function. I have done that but since I cannot see the code in the anonymous function I do not know if it equals the code in the former create_function.
Here is the code and my question is: is 'my translation' equal to the 'original code'?
public static function makePhpVdtorFromRegex($regex, $match = TRUE)
{
//original code
$s = 'return '.($match?'':'0 == ').'preg_match(\'/'.addslashes($regex).'/\',$v);';
return create_function('$v', $s);
// my translation
return function($v) use ($regex, $match) {
return ($match?'':'0 == ').preg_match('/'.addslashes($regex).'/',$v);
};
}
I believe makePhpVdtorFromRegex stands for 'Make PHP Validator From Regex'. The problem in validating this is I am not sure where the actual validator is used as this anonymous function is stored in an array which is used to validate input at some later time doing form input validation.
Because $regex and $match only exist within makePhpVdtorFromRegex() they will not be available when the validator is ultimately run, right? So I suspect my translation is not going to work?
To mimic the original behaviour, you should be able to replace it with (for testing purposes, I turned the method into a function):
function makePhpVdtorFromRegex($regex, $match = true) {
if ($match) {
return function($value) use ($regex) {
return preg_match('/'.addslashes($regex).'/', $value);
};
}
return function($value) use ($regex) {
// Same as '0 == preg_match(...)' from original code
return !preg_match('/'.addslashes($regex).'/', $value);
};
}
$validator = makePhpVdtorFromRegex('^[a-z]+$');
// Check if something matches
var_dump($validator('abc')); // true
// Check if something doesn't match
$validator = makePhpVdtorFromRegex('^[a-z]+$', false);
var_dump($validator('123')); // true
If you've the chance to look into the actual form validation later on & maybe even take control of the regular expressions themselves, you could rewrite this code to something much simpler, like:
function getRegexValidator() {
return function($regex, $value) {
return preg_match($regex, $value);
};
}
$validator = getRegexValidator();
// Check if something matches
var_dump($validator('/^[a-z]+$/', 'abc')); // true
// Check if something doesn't match
var_dump(!$validator('/^[a-z]+$/', '123')); // true

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'])'
}

Optional parameters in PHP function without considering order

Is there a way in PHP to use a function which has optional parameters in its declaration where I do not have to pass an optional arguments which already have values declared and just pass the next argument(s) which have different values that are further down the parameter list.
Assuming I have a function that has 4 arguments, 2 mandatory, 2 optional. I don't want to use null values for the optional arguments. In usage, there are cases where I want to use the function and the value of the 3rd argument is the same as the default value but the value of the 4th argument is different.
I am looking for a not so verbose solution that allows me to just pass the argument that differs from the default value without considering the order in the function declaration.
createUrl($host, $path, $protocol='http', $port = 80) {
//doSomething
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
I find myself repeating declaring variables so that I could use a function i.e to use $port, I redeclare $protocol with the default value outside the function scope i.e
$protocol = "http";
$port = 8080;
Is there any way to pass the 2nd optional parameter($port) without passing $protocol and it would "automatically" fill in the default value of $protocol i.e
getHttpUrl($server, $path, $port);
This is possible in some languages like Dart in the form of Named Optional parameters.See usage in this SO thread. Is their a similar solution in PHP
You could potentially use a variadic function for this.
Example:
<?php
function myFunc(...$args){
$sum = 0;
foreach ($args as $arg) {
$sum += $arg;
}
return $sum;
}
Documentation:
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
PHP doesn't allow at this state to call functions parameters in the order we want.Maybe in the future it will.However you can easily achieve your purpose by using an associative array as the only argument, and then define, the default parameter in the function.For the call you will need to pass an array with only the values which interest you.This array will be merged with the default array.You can even implement required parameters and call them in any order you want.
example:
function mysweetcode($argument){
$required=['first'];//specify required parameters here
$default=['first'=>0,'second'=>1,'third'=>2];//define all parameters with their default values here
$missing=[];
if(!is_array($argument)) return false;
$argument=array_intersect_key($argument,$default);
foreach($required as $k=>$v){//check for missing required parameters
if(!isset($argument[$v]))
$missing[]=$v;
}
if(!empty($missing)){// if required are missing trigger or throw error according to the PHP version
$cm=count($missing);
if (version_compare(PHP_VERSION, '7.0.0') < 0) {
trigger_error(call_user_func_array('sprintf',
array_merge(array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s,',$cm).(($cm>1)?' are':' is').' missing'),$missing)),
E_USER_ERROR);
}else{
throw new Error(call_user_func_array('sprintf',array_merge(
array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s',$cm).(($cm>1)?' are':' is').' missing'),$missing)));
}
}
$default=array_merge($default,$argument);//assign given values to parameters
extract($default);/*extract the parameters to allow further checking
and other operations in the function or method*/
unset($required,$missing,$argument,$default,$k,$v);//gain some space
//then you can use $first,$second,$third in your code
return $first+$second+$third;
}
var_dump(mysweetcode(['first'=>9,'third'=>8]));//the output is 18
var_dump(mysweetcode(['third'=>8]));//this throws Error on PHP7 and trigger fatal error on PHP5
You can check a live working code here
Well, this should work:
function myFunc($arg1, $arg2, $arg3=null, $arg4= null){
if ( is_null( $arg3 ) && is_null( $arg4 ) {
$arg3 = 3;
$arg4 = 4;
} else if ( is_null( $arg4 ) ) {
$arg4 = $arg3;
$arg3 = 3;
}
echo $arg1 + $arg2 + $arg3 + $arg4;
}
However I suggest you to rethink your problem (as a whole) because this is not a very good idea.
You could refactor this to use a parameter object; this way, you could include the default parameters in this object and set them in any order (with a trade-off of more verbose code). As an example using your above code,
<?php
class AdditionParameters
{
private $arg1 = 0;
private $arg2 = 0;
private $arg3 = 3;
private $arg4 = 4;
public function getArg1() { return $this->arg1; }
public function getArg2() { return $this->arg2; }
public function getArg3() { return $this->arg3; }
public function getArg4() { return $this->arg4; }
public function setArg1($value) { $this->arg1 = $value; return $this; }
public function setArg2($value) { $this->arg2 = $value; return $this; }
public function setArg3($value) { $this->arg3 = $value; return $this; }
public function setArg4($value) { $this->arg4 = $value; return $this; }
}
From there, you could simply call the function while passing in this new object.
function myFunc(AdditionParameters $request) {
return $request->getArg1()
+ $request->getArg2()
+ $request->getArg3()
+ $request->getArg4();
}
echo myFunc((new AdditionParameters)->setArg1(1)->setArg2(2)->setArg4(6));
// or echo myFunc((new AdditionParameters)->setArg1(1)->setArg4(6)->setArg2(2));
Otherwise, PHP doesn't allow you to have named optional parameters. (e.g. myFunc(1, 2, DEFAULT, 4);)
You have the response in your question, you can declare your function like
function myFunc($arg1, $arg2, $arg3 = null, $arg4 = null){
//here you check if the $arg3 and $arg4 are null
}
then you call your function using
myFunc($arg1, $arg2);
There is no such way in PHP(like in python for example).
You have to use some tricks in order to do that but will not always work.
For example:
// creates instances of a class with $properties.
// if $times is bigger than 1 an array of instances will be returned instead.(this is just an example function)
function getInstance($class, $properties = [], $times = 1){
//my code
}
$user = getInstance("User", ["name" => "John"]); // get one instance
$users = getInstance("User", ["name" => "John"],2); // get two instances.
If you want to use the function without passing the $parameters argument, like this:
$users = getInstance("User",2);
you can change the function to:
// creates instances of a class with $properties.
// if times is bigger than 1 an array of instances will be returned instead.
function getInstance($class, $properties = [], $times = 1){
if(is_numberic($properties)){
$times = $properties;
$properties = [];
}
//my code
}
Of course, this strategy will work only if you parameters have different types.
PS. This method is use in the Laravel Framework a lot. From there I got the inspiration.
This is modified from one of the answers and allows arguments to be added in any order using associative arrays for the optional arguments
function createUrl($host, $path, $argument = []){
$optionalArgs = [
'protocol'=>'http',
'port'=>80];
if( !is_array ($argument) ) return false;
$argument = array_intersect_key($argument,$optionalArgs);
$optionalArgs = array_merge($optionalArgs,$argument);
extract($optionalArgs);
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
//No arguments with function call
echo createUrl ("www.example.com",'no-arguments');
// returns http://www.example.com:80/no-arguments
$argList=['port'=>9000];
//using port argument only
echo createUrl ("www.example.com",'one-args', $argList);
//returns http://www.example.com:9000/one-args
//Use of both parameters as arguments. Order does not matter
$argList2 = ['port'=>8080,'protocol'=>'ftp'];
echo createUrl ("www.example.com",'two-args-no-order', $argList2);
//returns ftp://www.example.com:8080/two-args-no-order
As of version 8.0, PHP now has named arguments. If you name the arguments when calling the function, you can pass them in any order and you can skip earlier default values without having to explicitly pass a value for them.
For example:
function createUrl($host, $path, $protocol = 'http', $port = 80)
{
return "$protocol://$host:$port/$path";
}
createUrl(host: 'example.com', path: 'foo/bar', port: 8080);
// returns: "http://example.com:8080/foo/bar"

$this in Array of Closures in Class

Say there is a class for objects, let's use a User as an example. The User class contains it's own Rules to validate it's data before submitting. Before saving to the database, the Rules will be checked and any errors will be returned. Otherwise the update will run.
class User extends DBTable // contains $Rules, $Data, $Updates, and other stuff
{
public __construct($ID)
{
parent::__construct($ID);
// I'll only list a couple rules here...
$this->Rules['Email'] = array(
'Empty' => 'ValidateEmpty', // pre-written functions, somewhere else
'Invalid' => 'ValidateBadEmail', // they return TRUE on error
'Duplicate' => function($val) { return existInDatabase('user_table', 'ID_USER', '`Email`="'. $val .'" AND `ID_USER`!='. $this->ID);}
);
$this->Rules['Password'] = array(
'Empty' => 'ValidateEmpty',
'Short' => function($val) { return strlen($val) < 8; }
);
this->Rules['PasswordConfirm'] = array(
'Empty' => 'ValidateEmpty',
'Wrong' => function($val) { return $val != $this->Updates['Password']; }
);
}
public function Save(&$Errors = NULL)
{
$Data = array_merge($this->Data, $this->Updates);
foreach($this->Rules as $Fields => $Checks)
{
foreach($Checks as $Error => $Check)
{
if($Check($Data[$Field])) // TRUE means the data was bad
{
$Errors[$Field] = $Error; // Say what error it was for this field
break; // don't check any others
}
}
}
if(!empty($Errors))
return FALSE;
/* Run the save... */
return TRUE; // the save was successful
}
}
Hopefully I posted enough here. So you'll notice that in the Duplicate error for Email, I want to check that their new email does not exist for any other user excluding themselves. Also PasswordConfirm tries to use $this->Updates['Password'] to make sure they entered the same thing twice.
When Save is run, it loops through the Rules and sets any Errors that are present.
Here is my problem:
Fatal error: Using $this when not in object context in /home/run/its/ze/germans/Class.User.php on line 19
This error appears for all closures where I want to use $this.
It seems like the combination of closures in an array and that array in a class is causing the problem. This Rule array thing works fine outside of a class (usually involving "use") and AFAIK closures are supposed to be able to use $this in classes.
So, solution? Work-around?
Thanks.
The problem is with the Wrong validator. The validation method is called from here:
if($Check($Data[$Field])) // TRUE means the data was bad
This call is not made in an object context (the lambda is not a class method). Therefore $this inside the body of the lambda causes an error because it only exists when in an object context.
For PHP >= 5.4.0 you can solve the problem by causing the capture of $this:
function($val) { return $val != $this->Updates['Password']; }
In this case you will be able to access Updates no matter what its visibility is.
For PHP >= 5.3.0 you need to make a copy of the object reference and capturing that instead:
$self = $this;
function($val) use($self) { return $val != $self->Updates['Password']; }
In this case however, you will only be able to access Updates if it is public.

PHP OOP newbie - Need to get $_POST data into a variable

I was basically playing around with OOP and was creating a way to validate and sanitise input when I started to run into problems sanitising and then performing further validation. What I'm looking for is to take the posted $_POST['name'] data, sanitise the input to remove any numbers and validate that the data left is neither null or numeric characters.
But I cant get the sanitised input saved to $sanitised, It seems to be empty, but when I replace
$sanitised=$fv->noNumbers($_POST['name']);
with
$sanitised=preg_replace('/[0-9]/', '', $_POST['name']);
everything works fine, so I think I'm messing up something in this $sanitised variable.
I wanna learn so either a solution to this or a "you're an idiot and doing it all wrong" would be much appreciated.
<?php
class formSanitise {
public function noNumbers($value) {
$value = preg_replace('/[0-9]/', '', $value);
}
public function isEmpty($value) {
return (!isset($value) || trim($value) == '') ? true : false;
}
public function isAlpha($value) {
return preg_match('/[^a-z]/i', $value) ? false : true;
}
?>
processor.php
<?php
include('class.formSanitise.php');
$fv = new formSanitise();
$sanitised= $fv->noNumbers($_POST['name']);
if ($fv->isEmpty($sanitised)) {
$fv->addError('Name', 'Please enter something');
}
if (!$fv->isAlpha($sanitised)) {
$fv->addError('Name', 'Please enter your name');
}
?>
You'll either need to create a return in noNumbers or pass $value by reference.
Return method:
public function noNumbers($value) {
return preg_replace('/[0-9]/', '', $value);
}
Reference
public function noNumbers(&$value) {
$value = preg_replace('/[0-9]/', '', $value);
}
returning a value means that $value is an entirely different variable, and will be assigned to $sanitized when it's returned from the function. Passing by reference means that $value is the exact same variable as the one you passed to noNumbers and as such, anything that happens to the variable inside the function will happen to the variable that has been passed in.
In the above code snippet the function noNumbers does not return any value.The argument passed to the function has a scope within that function only and in order to make that value available to calling function there must be a return statement within function which will return the value to the calling function .Alternatively you can pass the value to function by reference .

Categories