I found a code snippet that solved the problem I had with uniqueness validation, when dealing with arrays:
$rules = [ // this ones are ok for all
'staff.*.name' => 'required|max:128',
'staff.*.description' => 'max:512',
'staff.*.anothervalue' => 'string',
];
// here loop through the staff array to add the ignore
foreach($request->staff as $key => $staff) {
if ( array_key_exists('id', $staff) && $staff['id'] ) { // if have an id, means an update, so add the id to ignore
$rules = array_merge($rules, ['staff.'.$key.'.email' => 'required|email|unique:users,id,'.$staff['id']]);
} else { // just check if the email it's not unique
$rules = array_merge($rules, ['staff.'.$key.'.email' => 'required|email|unique:users']);
}
}
Now I need to go a step further and avoid duplication of email values inside the same array (not in database), so I found the "distinct" rule in Laravel documentation. Also, there is the need for making the email values required only if some another value is present in the same array entry. I see that there is a "required_with" rule that comes handy in those situations. However, I can't find the way to combine the rules generated with the foreach loop with those that use the "wildcard" array notation.
Your suggestions will be appreciated.
~~You can mix and match.~~
Edit: Turns out you cannot.
So you would need to overwrite the rule each time, or set all the rules as per staff.*.email when keyed individually; or make some custom validation logic that throws a BadRequestHttpException/ValidationException or similar.
e.g.:
foreach ...
$rules['staff.'.$key.'.email' => 'required_with:staff.*.id|email|distinct|unique:users']);
Related
I am reading input from the user (in Laravel) and have rules associated to each of those inputs. If the user selects a checkbox (for that specific table), I would like to remove all the rules from the $rules associative array for that table.
To better illustrate, I have created two arrays mimicking the behaviour.
My input array is as follows:
$input = array("TB1_course" => ['0'=>'CHEM 1E03', '1'=>'ENG 1D04'
],
"TB1_section" => ['0'=>'CHEM 1E03', '1'=>'ENG 1D04'
],
"TB1_checkbox" => "1",
"TB2_course" => ['0'=>'CHEM 1E03', '1'=>'ENG 1D04'
],
"TB2_checkbox" => "0"
);
$rules= array(
'TB1_course.*' => 'required_with',
'TB1_section.*' =>'required_with',
'TB2_course.*' =>'required_with'
);
You can see from the input array that TB1_checkbox has a value of 1. If it has a value of 1, I would like to remove all the rules associated with TB1 (i.e. remove the elements in $rules with a key containing 'TB1').
I attempted to do as such, and was partially successful. My code looks like:
foreach ($input as $key=>$value){//go thru every element of the inputs
if ((strpos($key, 'checkbox')!==false) && $value==1){//if it contains 'checkbox'
//and checkbox is selected
$table_name= substr($key, 0,3);//name of table
//now go thru $rules and remove all elements which contain a word of $table_name
$rules=array_filter($rules, function($x){
return strpos($x, $table_name)!==0;//problem lies here
}, ARRAY_FILTER_USE_KEY);
}
}
However my code isn't working. The strpos() function is unable to read the $table_name variable, and leads to problems. If I manually pass in a string, it ends up working (i.e. 'TB1' instead of $table_name), but I have to have the flexibility of checking all my tables (so the rules containing 'TB2' have to be removed if "TB2_checkbox" has a value of 1). Is there any way to solve this issue?
Thank you for the help.
I have dynamically created element in JQuery where i have assigned controls name as location_1,location_2,location_3, floor_1, floor_2,floor_3 and so on. It was not possible to use html-control array because these all fields are interdependent. So for me the ultimate solution was suffixing number.
Now i want to validate these fields where rules should looks something like
['location_*' => 'required|exists:locations,id'],
['Floor_*' => 'required|exists:floor,id']
how can i do this so that i could get an error back if it failed?
The easiest way is probably to loop over the request data and create rules based on the locations and floors you find. For instance for floors, given your example:
$rules = [...] // initialise your rules array here
$locationRule = 'required|exists:locations,id';
foreach ($request->all() as $input) {
if (preg_match('#^location_#', $input) {
$rules[$input] = $locationRule;
}
}
I have a configuration .php file that contains an array ( it has to be a PHP Array ) that contains something similar to:
<?php
return array(
'api_key' => 'e3awY0HoZr0c6L0791Wl2dA3',
'user' => 'Lequis',
'timeout' => '4'
);
These files are uploaded by users, i'd like to validate that the user doesn't add any malicious code, since these files will only contain an array, i'd like to validate that it is in fact only an array
Edit: see #VolkerK's comment regarding how this doesn't guard against the injection of malicious code.
Like #Jay Blanchard said, it may be better to actually think of a more appropriate data structure such as JSON. If you do want to stick to this structure however, you can use PHP's is_array() (http://php.net/manual/en/function.is-array.php) function to validate that a variable is an array before trying to pass that array to any other functions.
That only validates that you do have an array, and not that your array is in the proper format. To go one step further, you can validate that the array is of the right size using the count() (http://php.net/manual/en/function.count.php) function, these two things combined will ensure that you have an array with the correct number of values stored in the array.
The problem of checking to see whether or not the array values are in the correct format is a different beast. You can run through all of the keys and compare the passed key to an array of acceptable keys like so:
function isValidArr($usrArr){
$apiParams = array('api_key', 'user', 'timeout');
for($i = 0; i < count($usrArr); $i++ {
if(!strcmp($usrArr[$i], $apiParams[$i])) {
return false;
}
}
}
And then to check the values associated with each of the keys, I assume that the api_key is of a specific length or within a range, so you could check against that. For the timeout, you could ensure that the value is an integer. To validate against the username, you could implement a regex to ensure that the value adheres to a specific format.
I'm not sure what you want to check, an array or values?
If array, then, i think you can use simply is_array($arr) func.
If values, then there is a good Symfony component SymfonyOptionsResolver
You can use it like a validator for value types/values etc.
public function configureOptions(OptionsResolver $resolver)
{
// ...
$resolver->setAllowedTypes('host', 'string');
$resolver->setAllowedTypes('port', array('null', 'int'));
}
Or use some normalizer and check value with preg_match:
public function configureOptions(OptionsResolver $resolver)
{
// ...
$resolver->setNormalizer('host', function (Options $options, $value) {
if ('http://' !== substr($value, 0, 7)) {
$value = 'http://'.$value;
}
return $value;
});
}
Check documentation for additional info.
Is that what you're looking for?
I want to save log entries to my MySQL database from Zend Framework 2. I am using Zend\Log\Logger with a Zend\Log\Writer\Db writer. By supplying the writer with an array, one can choose which columns to save what data to (e.g. timestamp into a "log_date" column) and which data to save. Here is what I am doing:
$logger = new Zend\Log\Logger();
$mapping = array(
'timestamp' => 'timestamp_column',
'priority' => 'priority_column',
'message' => 'message_column',
'extra' => 'extra_column'
);
$logger->addWriter(new Zend\Log\Writer\Db($dbAdapter, 'table_name', $mapping));
$logger->err('some message', array('some extra information'));
The problem I am facing is that the array of column names and their values contain an incorrect column name for the "extra" column. Based on the array above, it should be inserting the value "some extra information" into the "extra_column" column. The problem is that the Zend\Log\Writer\Db class is using the letter "e" as the name of the extra column. This comes from the first letter of "extra_column" in my array above. For some reason, it is taking the first letter of "extra_column" and using it as the column name instead of the entire value.
I took a look at the source code. The mapEventIntoColumn method is being used to get the column names and values as an array. I copied in the relevant part of the method below.
// Example:
// $event = array('extra' => array(0 => 'some extra information'));
// $columnMap = array('extra' => 'extra_column');
// Return: array('e' => 'some extra information')
// Expected (without looking at the code below): array('extra_column' => 'some extra information')
protected function mapEventIntoColumn(array $event, array $columnMap = null) {
$data = array();
foreach ($event as $name => $value) {
if (is_array($value)) {
foreach ($value as $key => $subvalue) {
if (isset($columnMap[$name][$key])) {
$data[$columnMap[$name][$key]] = $subvalue;
}
}
}
}
return $data;
}
The $event parameter is an array containing the same keys as my $mapping array in my first code snippet and the values for the log message. The $columnMap parameter is the $mapping array from my first code snippet (array values are column names).
What actually seems to happen is that because I am passing in extra information as an array (this is required), the inner foreach loop is executed. Here, $key is 0 (the index) so it is actually doing like this: $columnMap['extra'][0]. This gives the letter "e" (the first letter in "extra_column"), which is used as the column name, where it should be the entire column name instead.
I tried to supply my own key in the extra array when calling the log method, but the same happens. The official documentation shows no examples of usage of the extra parameter. I want to insert information that can help me debug errors into my table, so I would like to use it.
Is this a bug or am I missing something? It seems really strange to me! I hope I explained it well enough - it is quite tricky!
Since Daniel M has not yet posted his comment as an answer, I will refer you to his comment which solved the problem.
Attached code taken from cakephp bakery, where someone uploaded a sample about custom validation rules.
class Contact extends AppModel
{
var $name = 'Contact';
var $validate = array(
'email' => array(
'identicalFieldValues' => array(
'rule' => array('identicalFieldValues', 'confirm_email' ),
'message' => 'Please re-enter your password twice so that the values match'
)
)
);
function identicalFieldValues( $field=array(), $compare_field=null )
{
foreach( $field as $key => $value ){
$v1 = $value;
$v2 = $this->data[$this->name][ $compare_field ];
if($v1 !== $v2) {
return FALSE;
} else {
continue;
}
}
return TRUE;
}
}
In the code, the guy used a foreach to access an array member which he had its name already!
As far as I understand - it's a waste of resources and a bad(even strange) practice.
One more thing about the code:
I don't understand the usage of the continue there. it's a single field array, isn't it? the comparison should happen once and the loop will be over.
Please enlighten me.
In the code, the guy used a foreach to access an array member which he had its name already! As far as I understand - it's a waste of resources and a bad(even strange) practice.
The first parameter is always an array on one key and its value, the second parameter comes from the call to that function, in a block named as the key... So, all you need is to send the key and no need to iterate
The code uses foreach to iterate through $field, which is an array of one key value pair. It all starts when the validation routine invokes identicalFieldValues, passing it two values - $field, which would be an array that looks like:
array (
[email] => 'user entered value 1'
)
The second parameter $compare_field would be set to the string confirm_email.
In this particular case, it doesn't look like it makes a lot of sense to use foreach since your array only has one key-value pair. But you must write code this way because CakePHP will pass an array to the method.
I believe the reason why CakePHP does this is because an array is the only way to pass both the field name and its value. While in this case the field name (email) is irrelevant, you might need in other cases.
What you are seeing here is one of the caveats of using frameworks. Most of the time, they simplify your code. But sometimes you have to write code that you wouldn't write normally just so the framework is happy.
One more thing about the code: I don't understand the usage of the continue there. it's a single field array, isn't it? the comparison should happen once and the loop will be over. Please enlighten me.
Indeed. And since there are no statements in the foreach loop following continue, the whole else block could also be omitted.
A simplified version of this would be:
function identicalFieldValues($field=array(), $compare_field=null)
{
foreach ($field as $field) {
$compare = $this->data[$this->name][$compare_field];
if ($field !== $compare) {
return FALSE;
}
}
return TRUE;
}
And I agree with you, the loop only goes through one iteration when validating the email field. regardless of the field. You still need the foreach because you are getting an array though.