4
\$\begingroup\$

I am dealing with a system which sends any unexpected errors back to me in a string, for example:

Error Type - Database
Error Message - Error Executing Database Something is wrong.
Error Detail - [Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '1'.

Now to create a better UI I am expected to deal with these errors, so I thought about using explode(), and found this user's response in the PHP Manual which helped me delimit the results with two delimiters instead of just one:

Then I needed to get a little deeper into the code because I wanted $key => $val pairs, so I came across this answer on StackOverflow.

Then I combined both and came across this solution:

function multiexplode ($delimiters,$string) {

    $ready = str_replace($delimiters, $delimiters[0], $string);

    list($err_typ, $err_typ_cont,
         $err_mess, $err_mess_cont,
         $err_det, $err_det_cont) = explode($delimiters[0], $ready);

    $result[ trim($err_typ) ] = trim($err_typ_cont);
    $result[ trim($err_mess) ] = trim($err_mess_cont);
    $result[ trim($err_det) ] = trim($err_det_cont);
    return  $result;
}

Then I call the function like this:

 multiexplode(array("-", PHP_EOL), $errorMessage)

The response seems to be exactly what I needed:

Array
(
    [Error Type] => Database
    [Error Message] => Error Executing Database Something is wrong.
    [Error Detail] => [Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '1'.
)

My question comes with PHP's best practices, and if there would be a more reliable or a more efficient approach I could take into solving this problem?

\$\endgroup\$
  • \$\begingroup\$ which delimeters are you using? I presume not the same from that PHP UCN (i.e. ",",".","|",":") \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Nov 7 '17 at 20:29
  • \$\begingroup\$ @SamOnela No, I am using "-", PHP_EOL The - to separate key from value, and PHP_EOL to create new item from string's new line \$\endgroup\$ – Samuel Nov 7 '17 at 20:29
3
\$\begingroup\$

A couple of thoughts:

  • This function is designed to only deal with a very sepcific use case - parsing a formatted error string. For that reason I would recommend changing the function name to be more representative of what is actually happening. Perhaps something like parseErrorString() or similar.
  • Since you are dealing with a very specific use case here, why would you require the caller to pass in the delimiters? This function should fully encapsulate all the parsing logic.
  • Using an array for return, seems odd, particularly since you have spaces in your key names. I would consider returning a stdClass or similar object with type, message, detail properties.
  • Though not strictly required, I would consider using regular expressions here, as I think that can clean things up as well as allow the solution to be less fragile to changes in the string format.
  • Using PHP_EOL as line break may be fragile. PHP_EOL is good for working with strings within same environment, but may fail if your application is running in a different environment than the environment your string is produced in.
  • Your function assumes happy path execution. What if you get a poorly/unexpectedly formatted string? Perhaps you are validating this string somewhere else, but if not, you should do it here in your function.

A refactoring, with these thoughts in mind, might yield something like the following:

function parseErrorString($string) {
    if(empty($string) || !is_string($string)) {
        throw new InvalidArgumentException('Non-zero length string expected');
    }
    $lines = preg_split('/\R/', $string);
    $error = array_reduce(
        $lines,
        function (&$err, $line) {
            $regex = '/^Error (Type|Message|Detail) - (.*)$/';
            $matches = [];
            $result = preg_match($regex, $line, $matches);
            if ($result !== 1) {
                // no match.  Is this an error condition?
                // at the very least, do nothing
                return $err;
            }
            $err->{strtolower($matches[1])} = $matches[2];
            return $err;
        },
        new stdClass();
    );
    return $error;
}
\$\endgroup\$
  • \$\begingroup\$ I am getting this error: Fatal error</b>: Cannot use object of type stdClass as array on line <b>268</b> \$\endgroup\$ – Samuel Nov 7 '17 at 21:37
  • \$\begingroup\$ @Samuel code in CR answers isn't meant to be taken as-is (reviewers don't even have to supply code), it's meant to illustrate a point. \$\endgroup\$ – Mathieu Guindon Nov 8 '17 at 2:03
  • \$\begingroup\$ Okay, I was just trying to build off the code you offered; I was testing it to see how it would work. \$\endgroup\$ – Samuel Nov 8 '17 at 2:05
  • 1
    \$\begingroup\$ @Samuel Sorry have been writing too much javascript lately I guess, and had syntax error. \$\endgroup\$ – Mike Brant Nov 8 '17 at 15:26
  • \$\begingroup\$ It's alright... I've never been a javascript guy... \$\endgroup\$ – Samuel Nov 8 '17 at 15:36
2
\$\begingroup\$

This task seems simple enough to me. I only recommend that you limit the explosion performed on each line, then it won't matter if the delimiter is duplicated later in the line. Clean, reliable, fast.

Code: (Demo)

function parseErrorMessage($string) {
    foreach(explode(PHP_EOL, $string) as $line){  // split on end of lines
        list($label,$message)=explode(' - ',$line,2);  // limit the explosion to 2 elements
        $result[$label]=$message;  // preserve using first half and second half of line
    }
    return $result;
}

$string ="Error Type - Database
Error Message - Error Executing Database Something is wrong.
Error Detail - [Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '1'.";

print_r(parseErrorMessage($string));

Output:

Array
(
    [Error Type] => Database
    [Error Message] => Error Executing Database Something is wrong.
    [Error Detail] => [Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '1'.
)

If you are concerned that your error messsage substring may contain line breaks, then regex is better suited.

The code to follow will break the string into a 1-dimensional array of alternating labels and messages before pairing them with array_chunk() then assigning them as your desired key-value pairs with array_combine().

Code: (Demo)

function parseErrorMessage($string) {
    $pairs=array_chunk(preg_split('/\R*(Error \w+) - /',$string,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE),2);
    return array_combine(array_column($pairs,0),array_column($pairs,1));
}

$string ="Error Type - Database
Error Message - Error Executing Database Something is wrong.
Error Detail - [Macromedia]
[SQLServer JDBC Driver]
[SQLServer]Incorrect syntax near '1'.";

var_export(parseErrorMessage($string));

Output:

array (
  'Error Type' => 'Database',
  'Error Message' => 'Error Executing Database Something is wrong.',
  'Error Detail' => '[Macromedia]
[SQLServer JDBC Driver]
[SQLServer]Incorrect syntax near \'1\'.',
)
\$\endgroup\$
1
\$\begingroup\$

Per your comment:

I am using "-", PHP_EOL The - to separate key from value, and PHP_EOL to create new item from string's new line

This makes me worry about the cases where one of the lines contains a hyphen (i.e. -), which would yield an incomplete string - see this playground example for an illustration.

Mike's answer is good. I agree the name could be made specific for parsing the error message, and that regular expressions could be utilized, though explode() should suffice.

function parseErrorMessage ($string) {
    $lines = explode(PHP_EOL, $string);
    return array_reduce($lines, function($result, $line) {
        $lineParts = explode(' - ', $line);
        $key = array_shift($lineParts);
        $result[trim($key)] = join(' - ', $lineParts);
        return $result;        
    }, []);
}

This can be demonstrated in this playground example.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.