I need to update a nested array of settings. There are several difficulties that I managed to solve, but I'm pretty sure the code can be written much simpler. I just don't know how, which is why I'm asking for a simpler solution.
Problem:
- I have a nested array of settings in the database.
- I have a form with fields that need to update the database.
- The form contains checkboxes. When the form is sent, the empty checkbox fields don't get transmitted as keys in the form
$input
array. They are just missing. - I have settings in the database that don't get updated with the form and need to be preserved.
It is not possible to just use array_merge(). It doesn't work recursively. And, it will not update a checkbox setting from true to false, because the form $input
array doesn't contain the key.
Also we must preserve all settings that are not being updated with the form.
What I've done:
- Create an array with all keys that are not part of the form.
- Merge that array recursively with the form
$input
. (This ensures that existing non-form keys get preserved with the next merge function. But, we are still missing the key for the empty checkboxes.) - Created a function that iterates recursively through the input array and the database array. If keys on boths sides exist, the form input key overwrites the database key. If the form input key is missing, the database key is set to false.
Here is my working code. It contains a few helpers. It tests if the final processed array is equal to the desired output or not. So this should make it easier to jump in and test quickly.
$input_from_db = [
'first' => [
'one' => '123123132', // form textfield textfield with text, change
'two' => '', // form, empty textfield, don't change
'three' => 0, // form, checkbox, false, change to true
'four' => 1, // form, checkbox, true, change to false
'five' => 1, // form, checkbox, true, keep
'six' => 0, // form, checkbox, false, keep
'seven' => 'testdata' // not in form, must be preserved
],
'second' => [ // test some deep nested arrays too
'one' => 0, // change to true
'two' => 1, // change to false with empty key
'three' => [
'a' => 'uno',
'b' => 'due'
]
],
'third' => '2', // not in form, must be preserved
'fourth' => '3' // not in form, must be preserved
];
$input_from_form = [
'first' => [
'one' => 'abababab',
'two' => '',
'three' => 1,
// four is missing from the form input because it was sent empty
'five' => 1,
// six is missing from the form input because it was sent empty
// seven is missing because it is no part of the form
],
'second' => [ //test some nested array too
'one' => 1,
// form field, must come back with zero
'three' => [
// a is not in form, preserve
'b' => 'due_new',
// c is an empty form checkbox
]
],
'third' => '2',
// four not in form, must be preserved
];
$must_be_result = [
'first' => [
'one' => 'abababab', // changed in the form to 'abababab'
'two' => '',
'three' => 1,
'four' => 0, // because it IS in the form, but was unchecked, it must be present again, with value zero
'five' => 1,
'six' => 0,
'seven' => 'testdata' // not in form, must be preserved
],
'second' => [
'one' => 1,
'two' => 0,
'three' => [
'a' => 'uno',
'b' => 'due_new',
'c' => 0,
]
],
'third' => '2',
'fourth' => '3'
];
// list of keys that the form will never return because they are not part of the form fields
function non_form_keys($array_existing): array
{
return [
'first' => [
'seven' => $array_existing['first']['seven'],
],
'second' => [
'three' => [
'a' => 'uno'
]
],
'third' => $array_existing['third'],
'fourth' => $array_existing['fourth'],
];
}
function update_options($array_existing, $array_input)
{
$output_array = [];
foreach ($array_existing as $key => $value)
{
if(array_key_exists($key, $array_input)){
if(is_array($value)){
$output_array[$key] = update_options($value, $array_input[$key]);
} else {
$output_array[$key] = $array_input[$key];
}
} else {
if(is_array($value)){
$output_array[$key] = set_to_zero($value);
} else {
$output_array[$key] = 0;
}
}
}
return $output_array;
}
function set_to_zero($array){
foreach ($array as $key => $value) {
if(is_array($value)) {
$array[$key] = set_to_zero($value);
} else {
$array[$key] = 0;
}
}
return $array;
}
// this is just a test that helps comparing my computed array with
// with an array that contains the result I want to see
function arrayRecursiveDiff($aArray1, $aArray2)
{
$aReturn = array();
foreach ($aArray1 as $mKey => $mValue) {
if (array_key_exists($mKey, $aArray2)) {
if (is_array($mValue)) {
$aRecursiveDiff = arrayRecursiveDiff($mValue, $aArray2[$mKey]);
if (count($aRecursiveDiff)) {
$aReturn[$mKey] = $aRecursiveDiff;
}
} else {
if ($mValue != $aArray2[$mKey]) {
$aReturn[$mKey] = $mValue;
}
}
} else {
$aReturn[$mKey] = $mValue;
}
}
return $aReturn;
}
// small helper to either print success or the
// diff array
function check_result($array_diff)
{
if(empty($array_diff)){
echo 'success' . PHP_EOL;
} else {
echo 'array diff' . PHP_EOL;
print_r($array_diff);
}
}
$input_from_form_updated = array_replace_recursive(non_form_keys($input_from_db), $input_from_form);
$result = update_options($input_from_db, $input_from_form_updated);
$array_diff = arrayRecursiveDiff($result, $must_be_result);
check_result($array_diff);