예제 #1
0
 /**
  * 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);
 }