PHP Compare Array Values for Validation - php

Ok, I have a pretty customized question so bear with me.
I basically have two sets of data that I want to compare with a lot of different possibilities.
$data = array(
'object'=>'ball', // Should check VALID (Rule 2)
'color'=>'white', // VALID (Rule 2)
'heavy'=>'no', // VALID (Rule 1)
'name'=>'wilson', // VALID (Rule 5)
'funny'=>'no' // INVALID (Rule 4)
);
$data_2 = array(
'object'=>'box', // VALID (Rule 2)
'color'=> 'blue', // VALID (Rule 2)
'texture'=>'hard', // VALID (Rule 1)
'heavy'=>'yes', // INVALID (Rule 4)
'stupid'=>'no' // INVALID (Rule 4)
// Name is INVALID because it is missing (Rule 3)
);
$required = array(
'color'=>array('white','blue'),
'heavy'=> 'no',
'name'
);
$errors = array(
'color'=>array('required'=>'Color is Required','invalid'=>'Color invalid')
'object'=>array('invalid'=>'Object invalid'),
'texture'=>array('invalid'=>'Texture invalid'),
'heavy'=>array('required'=>'Heavy is Required','invalid'=>'Heavy invalid'),
'name'=>array('required'=>'Name is Required','max_char'=>'Name exceeds char limit',
'invalid'=>'Invalid item provided',
);
$blueprint = array(
'object'=>array('box','ball'),
'color'=>array('blue','white'),
'texture'=>'hard',
'heavy'=>'no',
'name'
);
What I want to do is run $data through the $blueprint and make sure of the following:
If the $data key/value pair matches a $blueprint key/value pair, $data's k/v is valid
If the $data key/value pair matches a $blueprint key and a value from the nested array, $data's k/v is valid
If the $data array omits a key/value pair which exists in $blueprint, $data's k/v may still be valid if it is not located in the $required array
If the $data array supplies a key/value pair which does not exist in $blueprint, $data's k/v is invalid
If the $data key from a key/value pair matches a $blueprint value without a defined key, $data's k/v can still be valid. However, if the $blueprint has both a key and value defined, $data's k/v must meet the requirements of rule 1 to be valid.
I'd like to impose a character limit on several of the $blueprint k/v where if a $data's k/v exceeds this character limit, $datas k/v is not valid
If a $data's k/v is invalid, I'd then like to somehow associate an error with that particular k/v describing why it is invalid (surpassed character limit, general error etc.) Perhaps the error would be defined in a third array?
I've looked into array_intersect_assoc but not sure if this is beyond the scope of that function. Also, there will be a good amount of values in the $blueprint, so I need something as versatile as possible.
I think that this is right, my brain sort of melted while I was writing this, so please don't hesitate to ask if confused. Am I better off just validating each k/v individually?
Let's see who is the brainiac out there.

I made one change to your sample code. It seems easier if you make name into a key rather than a numerically keyed value.
$required = array(
'color'=>array('white','blue'),
'heavy'=> 'no',
'name' => '', # name now a key
);
This now works for a number of your rules. Primarily checking required keys exist, and that no extra keys outside of required and blueprint exist.
# check required keys
$missing = array_diff_key($required, $data);
if($missing) {
var_dump($missing); # react to missing keys
}
# check against all possible keys
$possible = array_merge_recursive($blueprint, $required);
$extra = array_diff_key($data, $possible);
if($extra) {
var_dump($extra); # react to extra keys
}
Now for the rest I would really need to know how you respond to malformed data etc. but if your data now passes these two tests and you respond in the way you see fit, you should be clear to iterate through the array and validate using array_search(), and filter_var() to check lengths.

I feel sort of silly, but here's a brute force method. #6 you get for free because it's not 'in' the array in any sense.
foreach ($data as $k => $v) {
if (empty($blueprint[$k])) {
// (3) Data defines a key that isn't defined in blueprint.
} else {
if (is_array($blueprint[$k])) {
if (in_array($v, $blueprint[$k])) {
// (2) Data defines a valid value in a blueprint list.
} else {
// (also 4) Data defines a value not in a blueprint list.
}
} else if ($v == $blueprint[$k]) {
// (1) Data defines a value in the blueprint.
} else if (in_array($v, $blueprint)) {
// (5) Data is in the blueprint without a key.
} else {
// (4) Data is invalid.
}
}
}
EDIT: This is the loop for checking if $blueprint has a key that $data doesn't define. There should probably be a toggle to make sure this is at all necessary (in the previous block) before running it.
foreach ($blueprint as $k => $v) {
if (empty($data[$k])) {
// (6) Data doesn't have a required key from blueprint.
}
}

Truth be told, that's not that difficult per se, its just complex. You can use the array_map function to simplify the mapping; it would look like this:
function validate_data($data, $blueprint)
{
// an implementation of all that stuff you wrote using lots of
// for loops and if statements
}
array_map('validate_data', $data, $blueprint);
Check out the man page for more specifics. You can be the braniac this time :)

You want to use in_array(). It will search through the values of your array and find the different values, eg.
foreach($data as $key => $val) {
$check = in_array($val, $blueprint);
if($check === false) {
print("invalid");
die;
}
}

Yes, you probably have to code it youself, as I don't think there is any internal function that can do this. Shouldn't be too hard as you already have a good description of your requirements - just translate it into PHP.

Related

PHP - Loop an array where only some keys are set explicitely

I have the following array
$a = ["one", "dos" => "two", "three"];
As you see the second element has the key for his value set explicitely, but the other 2 items do not.
I want to loop through the array, but do something different, depending if the key for that item was set explicitly or not. Kinda like this:
foreach($a as $value){
if( has_explicit_key($value) )
// Do something
else
// Do other stuff
}
How can I achieve this?
PS: I guess I could check if the key is an integer, but if the key is set explicitly as an integer, that would not work, right?
try this
foreach($a as $key=>$value){
if( is_int($key) )
// Do something
else
// Do other stuff
}
this is the closest approach since keys are usually, 0,1,2......
In your specific case, you can exploit the fact that elements without explicit string keys automatically receive integer indexes:
$a = ["one", "dos" => "two", "three"];
foreach ($a as $k => $v) {
if (is_int($k)) {
// Do something
} else {
// Do other stuff
}
}
If you allow for the explicit keys to be scalars other than strings (integer, float, boolean, etc), then there is no way (at run-time) to distinguish between non-string keys supplied by the user and integer keys filled in by the parser. Specifically, refer to the PHP source function zend_ast_add_array_element. In that function, when the key is not explicitly given (offset IS_UNDEF), then PHP assigns one with zend_hash_next_index_insert and records no bookkeeping note of that fact.
Now, if you don't mind, and are capable of statically analyzing the data structure, just tokenize or parse the PHP code and see if T_DOUBLE_ARROW precedes the array value. This is probably not worth the effort and only works on static code.
You can loop through the array using
foreach($a as $key => $value) {
/* stuff */
}
To check if the key has been set explicitly can probably only be done by checking if the key is numerical (PHP will assign numerical keys to values without any keys in arrays).
Of course this means that you won't be able to detect a key that was set explicitly and is numeric.
So unless there's some function (which I'm unaware of) this would be the only way.

Setting an array value in multi-dimensional arrays PHP

I have the following problem:
I have an array of arrays with unknown amount of subarrays so my array might look like this:
array('element1'=>array('subelement1'=>'value'
'subelement2'=>array(...),
'element2'=>array('something'=>'this',
'subelement1'=>'awesome'));
Now I want to write a function that is able to replace a value by having it's path in the array, the first parameter is an array that defines the keys to search for. If I want to replace 'value' in the example with 'anothervalue', the function call should look like this:
replace_array(array('element1','subelement1'),'anothervalue');
It should also be able to replace all values on a given level by using null or another placeholder e.g.
replace_array(array(-1, 'subelement1'),'anothervalue');
should replace both 'value' and 'awesome'.
I tried to get it work by using a recursive function and references (one call searches the first array by using the first element in the path variable, and then it calls itself again with the subarray until it has found all occurences defined by the given path).
Is there a smart way to get it done? As my reference idea doesn't seem to work that good.
I can post the code I'm using atm later on.
Thank you.
edit: I updated my answer to also do -1
edit: As -1 is valid for use as an array key, I think you shouldn't use that to mark "all" keys. Arrays themselves however cannot be used as an array key. So in stead of -1 , I have chosen to use an array ([] or array()) to mark "all" keys.
function replace_array(&$original_array, $path, $new_value) {
$cursor =& $original_array;
if (!is_array($path))
return;
while($path) {
$index = array_shift($path);
if (!is_array($cursor) || (!is_array($index) && !isset($cursor[$index])))
return;
if (is_array($index)) {
foreach($cursor as &$child)
replace_array($child, $path, $new_value);
return;
} else {
$cursor =& $cursor[$index];
}
}
$cursor = $new_value;
return;
}
// use like : replace_array($my_array, array("first key", array(), "third key"), "new value");
// or php5.4+ : replace_array($my_array, ["first key", [], "third key"], "new value");
The key in constructing this method lies in passing the array by reference. If you do that, the task becomes quite simple.
It is going to be a recursive method, so the questions to ask should be:
Is this the last recursion?
What key do I have to look for in this level?
array_shift() comes in handy here as you get the first level and at the same moment shorten the $searchPath properly for the next recursion.
function deepReplace($searchPath, $newValue, &$array) {
// ^- reference
//Is this the last recursion?
if (count($searchPath) == 0) {
$array = $newValue;
}
if (!is_array($array))
return;
//What key do I have to look for in this level?
$lookFor = array_shift($searchPath);
//To support all values on given level using NULL
if ($lookFor === null) {
foreach ($array as &$value) {
// ^- reference
deepReplace($searchPath, $newValue, $value);
}
} elseif (array_key_exists($lookFor, $array)) {
deepReplace($searchPath, $newValue, $array[$lookFor]);
}
}
See it work here like
deepReplace(array(null, "subelement1"), "newValue", $data);
deepReplace(array("element1", "subelement1"), "newValue", $data);

Does array element has a key

How can I tell if array element, in foreach loop, has a key?
Some code:
function test($p_arr){
foreach ($p_arr as $key => $value){
// detect here if key 'came with the array' or not
}
}
$arr1['a'] = 10;
$arr2[] = 10;
$arr3[2] = 10;
test($arr1); // yes
test($arr2); // no
test($arr3); // yes
edit #1
I am aware that $arr2 also as an automated index-key. I need to know if it is automated or not.
edit #2
My use of it is simple.
In the function, I create a new array and use the $key as the new $key, if it was provided by the function call. or the $value as the new $key, if it was omitted in the function call.
I know that I can just force the use of key to each element, but in some parts of the code, the data structure itself is very dynamic* - and i'm trying to stay flexible as much as possible.
*code that create other code, that create ... and so on.
There is no difference between explicit keys and implicit keys generated via []. The [] doesn't mean "give this element no key", it means "use the next key for this element".
Every element has a key
$arr1['a'] = 10; // key is the string 'a'
$arr2[] = 10; // key is will be the integer zero
$arr3[2] = 10; // key is the integer 2
Edit
Perhaps it would be good to understand why you wish to know if the index is automated or not? It seems odd.
Every array created has to have a key, whether it's a integer or string as the key or index, without no index the PHP would have no way to interpret or even pull information from the array it's self.
$Var = array ("String","anotherstring","sdfhs","dlj");
the above array will automatically be generated with a numeric index starting from 0.
$Array = array();
$Array[] = "This is a string";
The above will push information into the array, as there has been no index or key specified. It will automatically be assigned with the closest numeric value to 0, which does not already exist in the array.
$Array = array();
$Array['key'] = "This is another string";
The above will push information into the array also, but as we have specified an index with a string representation rather an automatically assigned value.
So the answer to your question, if i'm reading this Correctly.
If your referring to check if the array values are specified by PHP/The Code prior to reading the array. There is no soundproof method, as everything would need to be assigned to the array before it has data. further more, if your only adding elements to the array with a string key, then yes. It is possible.
If your relying on automatically generated numeric values, or assigning your own numeric values. it's impossible to tell if PHP has assigned this automatically, or you have specified.

PHP split and mongodb

ok, this might sound strange, but i have a form and our business wants to track what is getting changed, when a user adds a new lead etc.
So i set up a function that does the following
function savedata($data){
$collection = $this->db->retail_logs;
$this->data = explode('|', $data['data']);
print_r($this->data);
try {
$collection->update(
array($this->data['0']=>$this->data['1'],$this->data[2]=>$this->data[3]),
array("date"=> date("d.m.Y"), "time"=>date("H:i:s"),"whochanged"=>$_COOKIE['CRMUIDkey']), // new lead document to insert
array("upsert" => true, "safe" => true)
);
} catch (Exception $e) {
// Something went wrong ..
}
}
it is basiclly a log file.
but as you may be able to see the $data sends data though like tradingname|ABC|owner|ownerID
But if I want to add to that i would need to run a loop or a foreach I am wondering what is the best way to make sure all teh data gets saved and not just data[0] to 3 so say they send 16 fields and values in it I need a foreach or something to split it.
It appears that you want to map the $data['data'] into key=>value pairs (an associative array). You want to be very careful about what fields you allow in this format especially since it looks like user-provided data (or data they can modify in a post request?). For example, a malicious user could update or add to another user's account if you aren't checking the allowed keys before doing the upsert.
To convert the $data string you want to do something like:
<?php
// Keys that can be updated
$allowed = array('tradingname','owner');
// Sample data
$data = 'tradingname|ABC|owner|ownerID|badkey|foo';
// Split into arrays based on '|' delimiter
preg_match_all("/([^\|]+)\|([^\|]+)/", $data, $keypairs);
// Combine matches into key => value array
$keypairs = array_combine($keypairs[1], $keypairs[2]);
// Sanity check to create $criteria
foreach ($keypairs as $key => $value) {
if (in_array($key, $allowed)) {
// Perhaps do some extra sanity checking that $value is expected format
$criteria[$key] = $value;
} else {
// Log and/or bailout as appropriate
echo "Ignoring: [$key] => [$value]\n";
}
}
// Criteria should now be reasonable to use in $collection->update(..) upsert
print_r($criteria);
?>
Send your data as json. And then use json_decode to convert it to the array you want.

A $_GET input parameter that is an Array

I'm trying to pass 3 parameter to a script, where the 3rd parameter $_GET['value3'] is supposed to be an array
$_GET['value1']
$_GET['value2']
$_GET['value3'] //an array of items
I'm calling the script like this: (notice my syntax for value3, I'm not sure it's correct)
http://localhost/test.php?value1=test1&value2=test2&value3=[the, array, values]
I then use a foreach to hopefully loop through the third parameter value3 which is the array
//process the first input $_GET['value1']
//process the second input $_GET['value2']
//process the third input $_GET['value3'] which is the array
foreach($_GET['value3'] as $arrayitem){
echo $arrayitem;
}
but I get the error Invalid argument supplied for foreach()
I'm not sure if my methodology is correct. Can some clarify how you'd go about doing the sort of thing
There is no such thing as "passing an array as a URL parameter" (or a form value, for that matter, because this is the same thing). These are strings, and anything that happens to them beyond that is magic that has been built into your application server, and therefore it is non-portable.
PHP happens to support the &value3[]=the&value3[]=array&value3[]=values notation to automagically create $_GET['value3'] as an array for you, but this is special to PHP and does not necessarily work elsewhere.
You can also be straight-forward and go for a cleaner URL, like this: value3=the,array,values, and then use explode(',', $_GET['value3']) in your PHP script to create an array. Of course this implies that your separator char cannot be part of the value.
To unambiguously transport structured data over HTTP, use a format that has been made for the purpose (namely: JSON) and then use json_decode() on the PHP side.
try
http://localhost/test.php?value1=test1&value2=test2&value3[]=the&value3[]=array&value3[]=values
For arrays you need to pass the query parameters as
value3[]=abc&value3[]=pqr&value3[]=xyz
You can cast the name of the index in the string too
?value1[a]=test1a&value1[b]=test1b&value2[c][]=test3a&value2[c][]=test3b
would be
$_GET['value1']['a'] = test1a
$_GET['value1']['b'] = test1b
$_GET['value2']['c'] = array( 'test3a', 'test3b' );
http://php.net/manual/en/reserved.variables.get.php
Check out the above link..
You will see how the GET method is implemented.
What happens is that the URL is taken, it is delimited using '&' and then they are added as a key-value pair.
public function fixGet($args) {
if(count($_GET) > 0) {
if(!empty($args)) {
$lastkey = "";
$pairs = explode("&",$args);
foreach($pairs as $pair) {
if(strpos($pair,":") !== false) {
list($key,$value) = explode(":",$pair);
unset($_GET[$key]);
$lastkey = "&$key$value";
} elseif(strpos($pair,"=") === false)
unset($_GET[$pair]);
else {
list($key, $value) = explode("=",$pair);
$_GET[$key] = $value;
}
}
}
return "?".((count($_GET) > 0)?http_build_query($_GET).$lastkey:"");
}
Since, they are added as a key-value pair you can't pass array's in the GET method...
The following would also work:
http://localhost/test.php?value3[]=the&value3[]=array&value3[]=values
A more advanced approach would be to serialize the PHP array and print it in your link:
http://localhost/test.php?value3=a:3:{i:0;s:3:"the";i:1;s:5:"array";i:2;s:6:"values";}
would, essentially, also work.

Categories