8000 Developer friendly Class Not Found and Undefined Function errors. · symfony/symfony@6671945 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6671945

Browse files
simensenfabpot
authored andcommitted
Developer friendly Class Not Found and Undefined Function errors.
1 parent 1d86ea1 commit 6671945

File tree

5 files changed

+394
-4
lines changed

5 files changed

+394
-4
lines changed

src/Symfony/Component/Debug/ErrorHandler.php

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Component\Debug;
1313

14-
use Symfony\Component\Debug\Exception\FatalErrorException;
15-
use Symfony\Component\Debug\Exception\ContextErrorException;
1614
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Debug\Exception\ClassNotFoundException;
16+
use Symfony\Component\Debug\Exception\ContextErrorException;
17+
use Symfony\Component\Debug\Exception\FatalErrorException;
18+
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
1719

1820
/**
1921
* ErrorHandler.
@@ -41,6 +43,11 @@ class ErrorHandler
4143
E_PARSE => 'Parse',
4244
);
4345

46+
private $classNameToUseStatementSuggestions = array(
47+
'Request' => 'Symfony\Component\HttpFoundation\Request',
48+
'Response' => 'Symfony\Component\HttpFoundation\Response',
49+
);
50+
4451
private $level;
4552

4653
private $reservedMemory;
@@ -152,15 +159,158 @@ public function handleFatal()
152159
return;
153160
}
154161

162+
$this->handleFatalError($error);
163+
}
164+
165+
public function handleFatalError($error)
166+
{
155167
// get current exception handler
156168
$exceptionHandler = set_exception_handler(function() {});
157169
restore_exception_handler();
158170

159171
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
160-
$level = isset($this->levels[$type]) ? $this->levels[$type] : $type;
172+
$level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type'];
161173
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
162-
$exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']);
174+
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']);
175+
176+
if ($this->handleUndefinedFunctionError($exceptionHandler[0], $error, $exception)) {
177+
return;
178+
}
179+
180+
if ($this->handleClassNotFoundError($exceptionHandler[0], $error, $exception)) {
181+
return;
182+
}
183+
163184
$exceptionHandler[0]->handle($exception);
164185
}
165186
}
187+
188+
private function handleUndefinedFunctionError($exceptionHandler, $error, $exception)
189+
{
190+
$messageLen = strlen($error['message']);
191+
$notFoundSuffix = "()";
192+
$notFoundSuffixLen = strlen($notFoundSuffix);
193+
if ($notFoundSuffixLen > $messageLen) {
194+
return false;
195+
}
196+
197+
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
198+
return false;
199+
}
200+
201+
$prefix = "Call to undefined function ";
202+
$prefixLen = strlen($prefix);
203+
if (0 !== strpos($error['message'], $prefix)) {
204+
return false;
205+
}
206+
207+
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
208+
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
209+
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
210+
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
211+
$message = sprintf(
212+
"Attempted to call function '%s' from namespace '%s' in %s line %d.",
213+
$functionName,
214+
$namespacePrefix,
215+
$error['file'],
216+
$error['line']
217+
);
218+
} else {
219+
$functionName = $fullyQualifiedFunctionName;
220+
$message = sprintf(
221+
"Attempted to call function '%s' from the global namespace in %s line %d.",
222+
$functionName,
223+
$error['file'],
224+
$error['line']
225+
);
226+
}
227+
228+
$candidates = array();
229+
foreach (get_defined_functions() as $type => $definedFunctionNames) {
230+
foreach ($definedFunctionNames as $definedFunctionName) {
231+
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
232+
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
233+
} else {
234+
$definedFunctionNameBasename = $definedFunctionName;
235+
}
236+
237+
if ($definedFunctionNameBasename === $functionName) {
238+
$candidates[] = '\\'.$definedFunctionName;
239+
}
240+
}
241+
}
242+
243+
if ($candidates) {
244+
$message .= " Did you mean to call: " . implode(", ", array_map(function ($val) {
245+
return "'".$val."'";
246+
}, $candidates)). "?";
247+
}
248+
249+
$exceptionHandler->handle(new UndefinedFunctionException(
250+
$message,
251+
$exception
252+
));
253+
254+
return true;
255+
}
256+
257+
private function handleClassNotFoundError($exceptionHandler, $error, $exception)
258+
{
259+
$messageLen = strlen($error['message']);
260+
$notFoundSuffix = "' not found";
261+
$notFoundSuffixLen = strlen($notFoundSuffix);
262+
if ($notFoundSuffixLen > $messageLen) {
263+
return false;
264+
}
265+
266+
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
267+
return false;
268+
}
269+
270+
foreach (array("class", "interface", "trait") as $typeName) {
271+
$prefix = ucfirst($typeName)." '";
272+
$prefixLen = strlen($prefix);
273+
if (0 !== strpos($error['message'], $prefix)) {
274+
continue;
275+
}
276+
277+
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
278+
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
279+
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
280+
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
281+
$message = sprintf(
282+
10000 "Attempted to load %s '%s' from namespace '%s' in %s line %d. Do you need to 'use' it from another namespace?",
283+
$typeName,
284+
$className,
285+
$namespacePrefix,
286+
$error['file'],
287+
$error['line']
288+
);
289+
} else {
290+
$className = $fullyQualifiedClassName;
291+
$message = sprintf(
292+
"Attempted to load %s '%s' from the global namespace in %s line %d. Did you forget a use statement for this %s?",
293+
$typeName,
294+
$className,
295+
$error['file'],
296+
$error['line'],
297+
$typeName
298+
);
299+
}
300+
301+
if (isset($this->classNameToUseStatementSuggestions[$className])) {
302+
$message .= sprintf(
303+
" Perhaps you need to add 'use %s' at the top of this file?",
304+
$this->classNameToUseStatementSuggestions[$className]
305+
);
306+
}
307+
308+
$exceptionHandler->handle(new ClassNotFoundException(
309+
$message,
310+
$exception
311+
));
312+
313+
return true;
314+
}
315+
}
166316
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Debug\Exception;
13+
14+
/**
15+
* Class (or Trait or Interface) Not Found Exception.
16+
*
17+
* @author Konstanton Myakshin <koc-dp@yandex.ru>
18+
*/
19+
class ClassNotFoundException extends \ErrorException
20+
{
21+
public function __construct($message, \ErrorException $previous)
22+
{
23+
parent::__construct(
24+
$message,
25+
$previous->getCode(),
26+
$previous->getSeverity(),
27+
$previous->getFile(),
28+
$previous->getLine(),
29+
$previous->getPrevious()
30+
);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Debug\Exception;
13+
14+
/**
15+
* Un 1241 defined Function Exception.
16+
*
17+
* @author Konstanton Myakshin <koc-dp@yandex.ru>
18+
*/
19+
class UndefinedFunctionException extends \ErrorException
20+
{
21+
public function __construct($message, \ErrorException $previous)
22+
{
23+
parent::__construct(
24+
$message,
25+
$previous->getCode(),
26+
$previous->getSeverity(),
27+
$previous->getFile(),
28+
$previous->getLine(),
29+
$previous->getPrevious()
30+
);
31+
}
32+
}

0 commit comments

Comments
 (0)
0