Array destructuring assignement with optional values - php

Is there any solution in PHP to make an array destructuring assignment with associative keys can extract optional values/default values (like the example below)?
Because when I tried, I always get undefined index 'age' (it's because it is not set).
See example:
// implementation
function myFunction(array $params = ['name' => 'user-name', 'age' => 15]){
['name' => $name, 'age' => $age ] = $params;
echo $name.' '.$age;
}
// Execution:
// normal case
myFunction(['name' => 'user', 'age' => 100]);
// Output: user 100
// Wanted case to be released
myFunction(['name' => 'user']);
// Output: user 15 <== i want to get this default age value if i don't set it in the given associative array params.

What about defining an array with default values?
function myFunc(array $params, array $defaults = [ 'name' => 'Marcel', 'age' => 40 ]): string
{
$data = array_merge($defaults, $params);
[
'name' => $name,
'age' => $age
] = $data;
return $name . $age;
}
The array merge overwrites the default values with the given param values. So the return will always be a complete mixture of both values. If you send a name only, the name will be mixed up with the default age. If you give an age only, the age will be mixed up with the default value.
myFunc([ 'name' => 'sohaieb', 'age' => 15 ]); // => sohaieb15
myFunc([ 'name' => 'yadda' ]); // => yadda40
The second parameter $defaults can be changed, if the defaults change in the future. A possible scenario: You set the default values in a config. With this solution the function can take the default settings from the config and don 't has to be touched in the future.

Well, since you pass in a parameter, it will always take that instead of the default one. However, you can use the union operator + to merge it with default values if not present in the originally supplied value during function call.
<?php
function myFunction(array $params = []){
$params = $params + ['name' => 'user-name', 'age' => 15];
['name' => $name, 'age' => $age ] = $params;
echo $name.' '.$age;
}
myFunction(['name' => 'user']);

Because $params is an array, so it overrides the default values even if you pass in an empty array.
One way to do this is to check the value inside your function body.
function myFunction(array $params){
$name = $params['name'] ?? 'user-name';
$age = $params['age'] ?? 15;
echo $name.' '.$age;
}
// Execution:
// normal case
myFunction(['name' => 'user', 'age' => 100]);
// Output: user 100
// Wanted case to be released
myFunction(['name' => 'user']);
Reference
https://wiki.php.net/rfc/isset_ternary

Related

Modify value in multidim array and push it as new key value pair

I've read that it makes no sense to push new key value pairs into arrays. But how could this situation be solved best?
I have a multidim array of tutor and courses data:
$data = [
['tutor_name' => 'John Doe', 'course_day' => 'Monday', ...],
['tutor_name' => 'John Doe', 'course_day' => 'Tuesday', ...]
...
];
I need to translate all the days into my language (cz) so I wrote a switch:
function translate_to_cz ($day) {
switch ($day) {
case "Monday":
$day_cz = "Pondělí";
break;
case "Tuesday":
$day_cz = "Úterý";
break;
...
}
return $day_cz;
}
I need a loop that would send $data['course_day'] to the switch. The $day_cz would then be returned to the original array, preferably with a new key $data['course_day_cz'] waiting for it.
Can this be done?
Thanks
how could this situation be solved best?
Well, you seem to want to perform a value replacement, so create a lookup and access the appropriate replacement by its key.
Do not bloat your script with a switch block, just create a lookup array, then replace while iterating your rows of data. You can use an iterating function or a simple foreach() loop.
Code: (Demo)
$enToCz = [
'Monday' => 'Pondělí',
'Tuesday' => 'Úterý'
];
$data = [
['tutor_name' => 'John Doe', 'course_day' => 'Monday',],
['tutor_name' => 'John Doe', 'course_day' => 'Tuesday',]
];
array_walk(
$data,
function (&$row) use ($enToCz) {
$row['course_day_cz'] = $enToCz[$row['course_day']];
}
);
/* or you can use:
foreach ($data as &$row) {
$row['course_day_cz'] = $enToCz[$row['course_day']];
}
*/
var_export($data);
Output:
array (
0 =>
array (
'tutor_name' => 'John Doe',
'course_day' => 'Monday',
'course_day_cz' => 'Pondělí',
),
1 =>
array (
'tutor_name' => 'John Doe',
'course_day' => 'Tuesday',
'course_day_cz' => 'Úterý',
),
)
Explanation of syntax:
& before a variable means "modify by reference" (that is the term to search if you want to do some more research). It allows the data held in the variable to be directly mutated. This is necessary when a "copy of a variable" is being accessed.
There is some rather informative chatter at PHP foreach change original array values.
use() is a technique used to transfer global variables into the scope of a custom function (closure).
In PHP, what is a closure and why does it use the "use" identifier?
You can use a foreach loop over $data, using a reference to the value so you can modify it in the loop and calling the translate function on the course_day value:
foreach ($data as &$value) {
$value['course_day_cz'] = translate_to_cz($value['course_day']);
}
Demo on 3v4l.org

Sum column values but only if specific key exists in same row

I need to sum the price values of all rows where the optional check element exists.
Sample data:
[
6254 => [
'check' => 'on',
'quantity' => 2,
'name' => 'Testing product_special One Size',
'total' => 15.9,
'price' => 33.0000,
'totalken' => 33075.9,
],
6255 => [
'quantity' => 1,
'name' => 'Testing card',
'total' => 113.85,
'price' => 33.0000,
'totalken' => 16537.95,
],
6256 => [
'check' => 'on',
'quantity' => 1,
'name' => 'Testing food',
'total' => 113.85,
'price' => 33.0000,
'totalken' => 16537.95,
],
]
I tried array_sum(array_column($value, 'price')) but this sums all price values regardless of the check value.
Expected result: 66
I would use array_reduce in this case.
array_reduce loops through the array and uses a callback function to reduce array to a single value.
<?php
$totalPrice = array_reduce($myArray, function ($accumulator, $item) {
// I'm checking 'check' key only here, you can test for 'on' value if needed
if (isset($item['check'])) {
$accumulator += $item['price'];
}
return $accumulator;
});
You can simply filter the array based on condition by using array_filter() function of php.
You can see the usage of array_filter() here.
Here is my solution for you if you want to use condition.
$filteredByCheck = array_filter($value, function ($val){
return isset($val['check']);
});
$total = array_sum(array_column($filteredByCheck, 'price'));
The quickest method would be just to loop over the array and maintain a sum, use ?? '' to default it to blank if not set...
$total = 0;
foreach ($value as $element ) {
if ( ($element['check'] ?? '') == "on" ) {
$total += $element['price'];
}
}
#aliirfaan's implementation of array_reduce() can be modernized and compacted as the following snippet.
Code: (Demo)
echo array_reduce(
$array,
fn($result, $row) =>
$result + isset($row['check']) * $row['price']
);
The above uses arrow function syntax which is available since PHP7.4. The mathematics in the return value of the custom function multiplies the price value by the true/false evaluation of isset() on the check column. The boolean value is coerced to an integer automatically when used with arithmetic -- false is zero and true is one. If the coercion is not to your liking, you can explicitly cast the boolean value using (int) immediately before isset().

PHP filter callback with default value

Is there a way to define a default value (or to force the callback to be called everytime) when using filter_var_array and FILTER_CALLBACK ?
Example data:
{
"name": "John"
}
Example usage:
$params = filter_var_array($datas_from_above, [
'name' => FILTER_SANITIZE_STRING,
'age' => [
'filter' => FILTER_CALLBACK,
'options' => function ($data) {
// I was thinking $data would be null here
// but this function is not called if the
// param is not present in the input array.
die('stop?');
}
]
], true); // Add missing keys as NULL to the return value
When using other filters, there's the default option. So it shouldn't be supernatural to have a default value for callback filters. Am I missing something obvious?
Thanks
ok so after some comments and digging, filters in php only process what the input array contains.
So if you want to guarantee that a custom callback is always called, even if the input array doesn't contain the key => value pair, you can do:
$partial_input = ["name" => "John"]; // E.g. from a GET request
$defaults = ["name" => null, "age" => null];
$input = array_merge($defaults, $partial_input);
$parsed = filter_var_array($input, [
"name" => FILTER_SANITIZE_STRING,
"age" => [
"filter" => FILTER_CALLBACK,
"options" => function ($data) {
// do something special if $data === null
}
]
]);

Optimization of an algorithm performed on a big array in PHP

I have a query that populates an array from the database. In some cases, this query returns a great amount of data, (let's say for purpose of an example, 100.000 records). Each row of the database has at least 6 or 7 columns.
$results = [
['id' => 1, 'name' => 'name', 'status' => true, 'date' => '10-01-2012'],
['id' => 2, 'name' => 'name 2', 'status' => false 'date' => '10-01-2013'],
...
]
I need to perform a substitution of some of the data inside the $results array, based on another one that give me some information about how i would change the values in the rows.
$model = [
'status' => ['function' => 'formatStatus', params => ['status']],
'date' => ['function' => 'formatDate', params => ['date']]
]
Now that i have all the data and what do i do with it i have the following routine.
foreach ($results as &$itemResult) {
$oldValues = $itemResult;
foreach ($itemResult as $attribute => &$value) {
if (isset($model[$attribute]['function'])) {
$function = $model[$attribute]['function'];
$params = $model[$attribute]['params'];
$paramsSize = count($params);
for ($i = 0; $i < $paramsSize; $i++) {
$newParams[] = $oldValues[$params[$i]];
}
$itemResult[$attribute] = call_user_func_array([$this, $function], $newParams);
$newParams = null;
}
}
}
So, for each attribute for each row of my data array, i run check for the existence of a function and params information. When the attribute in question needs to be replaced, i call the function via call_user_func_array and replace the value with the function return value.
Also notice that i am replacing the current array, not creating another, by passing the reference &$itemResult inside the loop, so in the end, i have the same array from the beginning but with all columns that needed to be replaced with its new values.
The thing is, for little arrays, this method is quite good. But for big ones, it becomes a pain.
Could you guys provide me some alternative to the problem?
Should i use another data structure instead of the PHP array?

PHP Function Array Default Values? [duplicate]

This question already has answers here:
PHP function with variable as default value for a parameter
(7 answers)
Closed 1 year ago.
I have a PHP function with a array within. I put the array inside so the parameters would be option and these would be the defaults. Example
/**
* Creates New API Key
*
* #return Response
*/
public function create(
$data = [
"user-id" => Auth::id(),
"level" => '1',
"ignore-limits" => '0',
]){
...
}
However I keep getting the error
syntax error, unexpected '(', expecting ']'
So I assume that you cant pass a array like this when constructing a function. What would be a better way to do this or a fix?
You can only use scalar types for the default values of function arguments.
You can also read this in the manual: http://php.net/manual/en/functions.arguments.php#functions.arguments.default
And a quote from there:
The default value must be a constant expression, not (for example) a variable, a class member or a function call.
EDIT:
But if you still need this value as default value in the array you could do something like this:
Just use a placeholder which you can replace with str_replace() if the default array is used. This also has the advantage if you need the return value of the function in the default array multiple times you just need to use the same placeholder and both are going to be replaced.
public function create(
$data = [
"user-id" => "::PLACEHOLDER1::",
//^^^^^^^^^^^^^^^^ See here just use a placeholder
"level" => '1',
"ignore-limits" => '0',
]){
$data = str_replace("::PLACEHOLDER1::", Auth::id(), $data);
//^^^^^^^^^^^ If you didn't passed an argument and the default array with the placeholder is used it get's replaced
//$data = str_replace("::PLACEHOLDER2::", Auth::id(), $data); <- AS many placeholder as you need; Just make sure they are unique
//...
}
Another idea you could do is set a default array which you can check and then assign the real array like this:
public function create($data = []){
if(count($data) == 0) {
$data = [
"user-id" => Auth::id(),
"level" => '1',
"ignore-limits" => '0',
];
}
//...
}
The issue here is the:
Auth::id()
This calls a method which is illegal to do in this context
I would solve it like this:
public function create(
$data = [
"user-id" => -1,
"level" => '1',
"ignore-limits" => '0',
]){
if($data['user-id'] === -1) {
$data['user-id'] = Auth::id()
}
...
}
More universal solution with array_mearge. This way you can rewrite any parameter without having to check each of them individually.
function create($somthing, $settings = [])
{
$default = [
'date' => date("Y-m-d H:i:s"),
'bold' => false,
'italic' => false,
];
$settings = array_merge($default, $settings);
...
}

Categories