/** * Parses, and alters, the errLine, errFile and message given. * * This includes adding syntax highlighting, removing duplicate * information we already have, and making the error easier to * read. */ private function improveErrorMessage($ex, $code, $message, $errLine, $errFile, $root, &$stackTrace) { // change these to change where the source file is come from $srcErrFile = $errFile; $srcErrLine = $errLine; $altInfo = null; $stackSearchI = 0; $skipStackFirst = function (&$stackTrace) { $skipFirst = true; foreach ($stackTrace as $i => $trace) { if ($skipFirst) { $skipFirst = false; } else { if ($trace && isset($trace['file']) && isset($trace['line'])) { return array($trace['file'], $trace['line'], $i); } } } return array(null, null, null); }; /* * This is for calling a function that doesn't exist. * * The message contains a long description of where this takes * place, even though we are already told this through line and * file info. So we cut it out. */ if ($code === 1) { if (strpos($message, " undefined method ") !== false || strpos($message, " undefined function ") !== false) { $matches = array(); preg_match('/\\b[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*((->|::)[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)?\\(\\)$/', $message, $matches); /* * undefined function or method call */ if ($matches) { list($className, $type, $functionName) = ErrorHandler::splitFunction($matches[0]); if ($stackTrace && isset($stackTrace[1]) && $stackTrace[1]['args']) { $numArgs = count($stackTrace[1]['args']); for ($i = 0; $i < $numArgs; $i++) { $args[] = ErrorHandler::newArgument("_"); } } $message = preg_replace('/\\b[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*((->|::)[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)?\\(\\)$/', ErrorHandler::syntaxHighlightFunction($className, $type, $functionName, $args), $message); } } else { if ($message === 'Using $this when not in object context') { $message = 'Using <span class="syntax-variable">$this</span> outside object context'; /* * Class not found error. */ } else { if (strpos($message, "Class ") !== false && strpos($message, "not found") !== false) { $matches = array(); preg_match('/\'(\\\\)?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*((\\\\)?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)+\'/', $message, $matches); if (count($matches) > 0) { // lose the 'quotes' $className = $matches[0]; $className = substr($className, 1, strlen($className) - 2); $message = preg_replace('/\'(\\\\)?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*((\\\\)?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)+\'/', "<span class='syntax-class'>{$className}</span>", $message); } } } } } else { if ($code === 2) { if (strpos($message, "Missing argument ") === 0) { $message = preg_replace('/, called in .*$/', '', $message); $matches = array(); preg_match(ErrorHandler::REGEX_METHOD_OR_FUNCTION_END, $message, $matches); if ($matches) { $argumentMathces = array(); preg_match('/^Missing argument ([0-9]+)/', $message, $argumentMathces); $highlightArg = count($argumentMathces) === 2 ? (int) $argumentMathces[1] - 1 : null; $numHighlighted = 0; $altInfo = ErrorHandler::syntaxHighlightFunctionMatch($matches[0], $stackTrace, $highlightArg, $numHighlighted); if ($numHighlighted > 0) { $message = preg_replace('/^Missing argument ([0-9]+)/', 'Missing arguments ', $message); } if ($altInfo) { $message = preg_replace(ErrorHandler::REGEX_METHOD_OR_FUNCTION_END, $altInfo, $message); list($srcErrFile, $srcErrLine, $stackSearchI) = $skipStackFirst($stackTrace); } } } else { if (strpos($message, 'require(') === 0 || strpos($message, 'include(') === 0) { $endI = strpos($message, '):'); if ($endI) { // include( is the same length $requireLen = strlen('require('); /* * +2 to include the ): at the end of the string */ $postMessage = substr($message, $endI + 2); $postMessage = str_replace('failed to open stream: No ', 'no ', $postMessage); $message = substr_replace($message, $postMessage, $endI + 2); /* * If this string is in there, and where we think it should be, * swap it with a shorter message. */ $replaceBit = 'failed to open stream: No '; if (strpos($message, $replaceBit) === $endI + 2) { $message = substr_replace($message, 'no ', $endI + 2, strlen($replaceBit)); } /* * Now put the string highlighting in there. */ $match = substr($message, $requireLen, $endI - $requireLen); $newString = "<span class='syntax-string'>'{$match}'</span>),"; $message = substr_replace($message, $newString, $requireLen, $endI - $requireLen + 2); } } } /* * Unexpected symbol errors. * For example 'unexpected T_OBJECT_OPERATOR'. * * This swaps the 'T_WHATEVER' for the symbolic representation. */ } else { if ($code === 4) { if ($message === "syntax error, unexpected T_ENCAPSED_AND_WHITESPACE") { $message = "syntax error, string is not closed"; } else { $semiColonError = false; if (strpos($message, 'syntax error,') === 0 && $errLine > 2) { $lines = ErrorHandler::getFileContents($errFile); $line = $lines[$errLine - 1]; if (preg_match(ErrorHandler::REGEX_MISSING_SEMI_COLON_FOLLOWING_LINE, $line) !== 0) { $content = rtrim(join("\n", array_slice($lines, 0, $errLine - 1))); if (strrpos($content, ';') !== strlen($content) - 1) { $message = "Missing semi-colon"; $errLine--; $srcErrLine = $errLine; $semiColonError = true; } } } if ($semiColonError) { $matches = array(); $num = preg_match('/\\bunexpected ([A-Z_]+|\\$end)\\b/', $message, $matches); if ($num > 0) { $match = $matches[0]; $newSymbol = ErrorHandler::phpSymbolToDescription(str_replace('unexpected ', '', $match)); $message = str_replace($match, "unexpected {$newSymbol}", $message); } $matches = array(); $num = preg_match('/, expecting ([A-Z_]+|\\$end)( or ([A-Z_]+|\\$end))*/', $message, $matches); if ($num > 0) { $match = $matches[0]; $newMatch = str_replace(", expecting ", '', $match); $symbols = explode(' or ', $newMatch); foreach ($symbols as $i => $sym) { $symbols[$i] = ErrorHandler::phpSymbolToDescription($sym); } $newMatch = join(', or ', $symbols); $message = str_replace($match, ", expecting {$newMatch}", $message); } } } /** * Undefined Variable, add syntax highlighting and make variable from 'foo' too '$foo'. */ } else { if ($code === 8) { if (strpos($message, "Undefined variable:") !== false) { $matches = array(); preg_match(ErrorHandler::REGEX_VARIABLE, $message, $matches); if (count($matches) > 0) { $message = 'Undefined variable <span class="syntax-variable">$' . $matches[0] . '</span>'; } } /** * Invalid type given. */ } else { if ($code === 4096) { if (strpos($message, 'must be an ')) { $message = preg_replace('/, called in .*$/', '', $message); $matches = array(); preg_match(ErrorHandler::REGEX_METHOD_OR_FUNCTION, $message, $matches); if ($matches) { $argumentMathces = array(); preg_match('/^Argument ([0-9]+)/', $message, $argumentMathces); $highlightArg = count($argumentMathces) === 2 ? (int) $argumentMathces[1] - 1 : null; $fun = ErrorHandler::syntaxHighlightFunctionMatch($matches[0], $stackTrace, $highlightArg); if ($fun) { $message = str_replace('passed to ', 'calling ', $message); $message = preg_replace(ErrorHandler::REGEX_METHOD_OR_FUNCTION, $fun, $message); $prioritizeCaller = true; /* * scalars not supported. */ $scalarType = null; if (!ErrorHandler::$IS_SCALAR_TYPE_HINTING_SUPPORTED) { foreach (ErrorHandler::$SCALAR_TYPES as $scalar) { if (stripos($message, "must be an instance of {$scalar},") !== false) { $scalarType = $scalar; break; } } } if ($scalarType !== null) { $message = preg_replace('/^Argument [0-9]+ calling /', 'Incorrect type hinting for ', $message); $message = preg_replace('/ must be an instance of ' . ErrorHandler::REGEX_PHP_IDENTIFIER . '\\b.*$/', ", {$scalarType} is not supported", $message); $prioritizeCaller = false; } else { $message = preg_replace('/ must be an (instance of )?' . ErrorHandler::REGEX_PHP_IDENTIFIER . '\\b/', '', $message); if (preg_match('/, none given$/', $message)) { $message = preg_replace('/^Argument /', 'Missing argument ', $message); $message = preg_replace('/, none given$/', '', $message); } else { $message = preg_replace('/^Argument /', 'Incorrect argument ', $message); } } if ($prioritizeCaller) { list($srcErrFile, $srcErrLine, $stackSearchI) = $skipStackFirst($stackTrace); } } } } } } } } } if ($stackTrace !== null) { $isEmpty = count($stackTrace) === 0; if ($isEmpty) { array_unshift($stackTrace, array('line' => $errLine, 'file' => $errFile)); } else { if (count($stackTrace) > 0 && (!isset($stackTrace[0]['line']) || $stackTrace[0]['line'] !== $errLine)) { array_unshift($stackTrace, array('line' => $errLine, 'file' => $errFile)); } } if ($stackTrace && !$isEmpty) { $ignoreCommons = false; $len = count($stackTrace); /* * The code above can prioritize a location in the stack trace, * this is 'stackSearchI'. So we should start our search from there, * and work down the stack. * * This is built in a way so that when it reaches the end, it'll loop * back round to the beginning, and check the traces we didn't check * last time. * * If stackSearchI was not altered, then it just searches from top * through to the bottom. */ for ($i = $stackSearchI; $i < $stackSearchI + $len; $i++) { $trace =& $stackTrace[$i % $len]; if (isset($trace['file']) && isset($trace['line'])) { list($type, $_) = $this->getFolderType($root, $trace['file']); if ($type !== ErrorHandler::FILE_TYPE_IGNORE) { if ($type === ErrorHandler::FILE_TYPE_APPLICATION) { $srcErrLine = $trace['line']; $srcErrFile = $trace['file']; break; } else { if (!$ignoreCommons) { $srcErrLine = $trace['line']; $srcErrFile = $trace['file']; $ignoreCommons = true; } } } } } } } return array($message, $srcErrFile, $srcErrLine, $altInfo); }