bnoeafk bnoeafk - 7 months ago 13
PHP Question

Recursion of multi dimension array using 2nd array for checking keys and type values

I have a JSON array consisting of something like the following:

$json =
Array (
[authmode] => ad
[usertype] => sam
[user] => BobJones
[server] => Array (
[0] => Array (
[ip] => 1.2.3.4
[title] => sony
[id] => Array (
[0] => 1.2.840.1.1.1
[1] => 1.2.840.1.1.2
)
)
[1] => Array (
[ip] => 10.20.30.40
[title] => panasonic
[id] => Array (
[0] => 1.2.840.1.1.10
[1] => 1.2.840.1.1.11
[2] => 1.2.840.1.1.12
[3] => 1.2.840.1.1.13
)
)
)
[recipient] => Array (
[0] => john@smith.org
[1] => jsmith@smith.com
)
[date] => 2014-12-31
[options] => Array (
[0] => alpha
[1] => beta
[2] => gamma
)
)


Some elements of this array are required, others are optional. The required elements I hold in a separate array, containing their keys and their value types:

$req =
Array (
'authmode' => 'string',
'usertype' => 'string',
'user' => 'string'
'server' => 'array',
'ip' => 'string',
'title' => 'string'
'id' => 'array'
)


I have made a function that uses recursion to check for the required keys and ensures that their value types match those in the $req array

function bob($key, $val, $arr){
echo '<br>Checking ' . $key . ' with value of ' . $val . ' within ' . $arr;
// is key found in base array?
if(array_key_exists($key, $arr)){
if(gettype($arr[$key]) == $val){
echo '... gettype reports that ' . $arr . '[' . $key . '] is a ' . $val . ' and is correct';
return true;
}
}

echo '<br>' . $key . ' not in base array, sending back to itself...';

// check arrays within this array
foreach ($key as $ele){
if(is_array($ele)){
echo '<br>' . $ele . ' is an array ....';
if(bob($key, $val, $ele)){
return true;
}
}
}

return false;
}


Finally, the manner in which I call all of this is as follows:

foreach ($reqKeys as $key => $val){
if (!bob($key, $val, $json)){
echo '<p>Failed on ' . $key . ' being a ' . $val . '</p>';
}
}


The function works just fine on the base array, but as soon as I start inspecting any subarrays (the [server] array for example), the function returns an error on the ip not being a string, of which it clearly is.

The response I'm getting is:

Checking authmode with value of string within Array... gettype reports that Array[authmode] is a string and is correct
Checking usertype with value of string within Array... gettype reports that Array[usertype] is a string and is correct
Checking user with value of string within Array... gettype reports that Array[user] is a string and is correct
Checking server with value of array within Array... gettype reports that Array[server] is a array and is correct
Checking ip with value of string within Array
ip not in base array, sending back to itself...
Failed on ip being a string


Any thoughts or comments on a better way of doing things?

Answer

Since you want to do more complex comparisons than just type, you should use the strategy pattern and pass validation callbacks in $req.

$req will look like this:

$req = [
    'authmode' => 'is_string',
    'usertype' => 'is_string',
    'user' => 'is_string',
    'server' => function ($servers) {
        // check is `server` is an array
        if (!is_array($servers)) {
            return false;
        }

        foreach ($servers as $server) {
            // check conditions for each server.id, server.ip and server.title
            if (
                !is_array($server['id']) ||
                !is_string($server['ip']) ||
                !is_string($server['title'])
            ) {
                return false;
            }

            // validate all server.id's 
            foreach ($server['id'] as $id) {
                if (!is_string($id)) {
                    return false;
                }
            }
        }

        return true;
    }
];

Because $arr['server'] is an array, you have to validate its contents using your own function, as you cannot easily refer to them.

This makes our validation function very simple:

function isPropertyValid($key, $isValid, $arr) {
    if (!isset($arr[$key])) {
        return false;
    }

    return isset($arr[$key]) && $isValid($arr[$key]);
}

You no longer need recursion there which is great, as it would introduce some complications.

You might just as well throw in a function that validates all requirements instead of doing it one by one:

function isArrayValid($requirements, $arr) {
    foreach ($requirements as $key => $validator) {
        if (!isPropertyValid($key, $validator, $arr)) {
            return false;
        }
    }

    return true;
}

I've put together a working example here.