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?
3 Answers 3
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,detailproperties. - 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_EOLas line break may be fragile.PHP_EOLis 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;
}
-
\$\begingroup\$ I am getting this error:
Fatal error</b>: Cannot use object of type stdClass as arrayon line <b>268</b>\$\endgroup\$sam– sam2017年11月07日 21:37:59 +00:00Commented Nov 7, 2017 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– Mathieu Guindon2017年11月08日 02:03:11 +00:00Commented Nov 8, 2017 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\$sam– sam2017年11月08日 02:05:23 +00:00Commented Nov 8, 2017 at 2:05
-
1\$\begingroup\$ @Samuel Sorry have been writing too much javascript lately I guess, and had syntax error. \$\endgroup\$Mike Brant– Mike Brant2017年11月08日 15:26:11 +00:00Commented Nov 8, 2017 at 15:26
-
\$\begingroup\$ It's alright... I've never been a javascript guy... \$\endgroup\$sam– sam2017年11月08日 15:36:55 +00:00Commented Nov 8, 2017 at 15:36
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\'.',
)
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\$"-", PHP_EOLThe-to separate key from value, andPHP_EOLto create new item from string's new line \$\endgroup\$