12
12
namespace Symfony \Component \Debug ;
13
13
14
14
use Psr \Log \LoggerInterface ;
15
- use Symfony \Component \Debug \Exception \ClassNotFoundException ;
16
15
use Symfony \Component \Debug \Exception \ContextErrorException ;
17
16
use Symfony \Component \Debug \Exception \FatalErrorException ;
18
- use Symfony \Component \Debug \Exception \UndefinedFunctionException ;
19
- use Composer \Autoload \ClassLoader as ComposerClassLoader ;
20
- use Symfony \Component \ClassLoader as SymfonyClassLoader ;
21
- use Symfony \Component \ClassLoader \DebugClassLoader ;
17
+ use Symfony \Component \Debug \FatalErrorHandler \UndefinedFunctionFatalErrorHandler ;
18
+ use Symfony \Component \Debug \FatalErrorHandler \ClassNotFoundFatalErrorHandler ;
22
19
23
20
/**
24
21
* ErrorHandler.
@@ -160,6 +157,14 @@ public function handleFatal()
160
157
$ this ->handleFatalError ($ error );
161
158
}
162
159
160
+ public function getFatalErrorHandlers ()
161
+ {
162
+ return array (
163
+ new UndefinedFunctionFatalErrorHandler (),
164
+ new ClassNotFoundFatalErrorHandler (),
165
+ );
166
+ }
167
+
163
168
private function handleFatalError ($ error )
164
169
{
165
170
// get current exception handler
@@ -171,210 +176,11 @@ private function handleFatalError($error)
171
176
$ message = sprintf ('%s: %s in %s line %d ' , $ level , $ error ['message ' ], $ error ['file ' ], $ error ['line ' ]);
172
177
$ exception = new FatalErrorException ($ message , 0 , $ error ['type ' ], $ error ['file ' ], $ error ['line ' ]);
173
178
174
- if ($ ex = $ this ->handleUndefinedFunctionError ($ error , $ exception )) {
175
- return $ exceptionHandler [0 ]->handle ($ ex );
176
- }
177
-
178
- if ($ ex = $ this ->handleClassNotFoundError ($ error , $ exception )) {
179
- return $ exceptionHandler [0 ]->handle ($ ex );
180
- }
181
-
182
- $ exceptionHandler [0 ]->handle ($ exception );
183
- }
184
- }
185
-
186
- private function handleUndefinedFunctionError ($ error , $ exception )
187
- {
188
- $ messageLen = strlen ($ error ['message ' ]);
189
- $ notFoundSuffix = '() ' ;
190
- $ notFoundSuffixLen = strlen ($ notFoundSuffix );
191
- if ($ notFoundSuffixLen > $ messageLen ) {
192
- return ;
193
- }
194
-
195
- if (0 !== substr_compare ($ error ['message ' ], $ notFoundSuffix , -$ notFoundSuffixLen )) {
196
- return ;
197
- }
198
-
199
- $ prefix = 'Call to undefined function ' ;
200
- $ prefixLen = strlen ($ prefix );
201
- if (0 !== strpos ($ error ['message ' ], $ prefix )) {
202
- return ;
203
- }
204
-
205
- $ fullyQualifiedFunctionName = substr ($ error ['message ' ], $ prefixLen , -$ notFoundSuffixLen );
206
- if (false !== $ namespaceSeparatorIndex = strrpos ($ fullyQualifiedFunctionName , '\\' )) {
207
- $ functionName = substr ($ fullyQualifiedFunctionName , $ namespaceSeparatorIndex + 1 );
208
- $ namespacePrefix = substr ($ fullyQualifiedFunctionName , 0 , $ namespaceSeparatorIndex );
209
- $ message = sprintf (
210
- 'Attempted to call function "%s" from namespace "%s" in %s line %d. ' ,
211
- $ functionName ,
212
- $ namespacePrefix ,
213
- $ error ['file ' ],
214
- $ error ['line ' ]
215
- );
216
- } else {
217
- $ functionName = $ fullyQualifiedFunctionName ;
218
- $ message = sprintf (
219
- 'Attempted to call function "%s" from the global namespace in %s line %d. ' ,
220
- $ functionName ,
221
- $ error ['file ' ],
222
- $ error ['line ' ]
223
- );
224
- }
225
-
226
- $ candidates = array ();
227
- foreach (get_defined_functions () as $ type => $ definedFunctionNames ) {
228
- foreach ($ definedFunctionNames as $ definedFunctionName ) {
229
- if (false !== $ namespaceSeparatorIndex = strrpos ($ definedFunctionName , '\\' )) {
230
- $ definedFunctionNameBasename = substr ($ definedFunctionName , $ namespaceSeparatorIndex + 1 );
231
- } else {
232
- $ definedFunctionNameBasename = $ definedFunctionName ;
233
- }
234
-
235
- if ($ definedFunctionNameBasename === $ functionName ) {
236
- $ candidates [] = '\\' .$ definedFunctionName ;
237
- }
238
- }
239
- }
240
-
241
- if ($ candidates ) {
242
- $ message .= ' Did you mean to call: ' .implode (', ' , array_map (function ($ val ) {
243
- return '" ' .$ val .'" ' ;
244
- }, $ candidates )).'? ' ;
245
- }
246
-
247
- return new UndefinedFunctionException ($ message , $ exception );
248
- }
249
-
250
- private function handleClassNotFoundError ($ error , $ exception )
251
- {
252
- $ messageLen = strlen ($ error ['message ' ]);
253
- $ notFoundSuffix = '" not found ' ;
254
- $ notFoundSuffixLen = strlen ($ notFoundSuffix );
255
- if ($ notFoundSuffixLen > $ messageLen ) {
256
- return ;
257
- }
258
-
259
- if (0 !== substr_compare ($ error ['message ' ], $ notFoundSuffix , -$ notFoundSuffixLen )) {
260
- return ;
261
- }
262
-
263
- foreach (array ('class ' , 'interface ' , 'trait ' ) as $ typeName ) {
264
- $ prefix = ucfirst ($ typeName ).' " ' ;
265
- $ prefixLen = strlen ($ prefix );
266
- if (0 !== strpos ($ error ['message ' ], $ prefix )) {
267
- continue ;
268
- }
269
-
270
- $ fullyQualifiedClassName = substr ($ error ['message ' ], $ prefixLen , -$ notFoundSuffixLen );
271
- if (false !== $ namespaceSeparatorIndex = strrpos ($ fullyQualifiedClassName , '\\' )) {
272
- $ className = substr ($ fullyQualifiedClassName , $ namespaceSeparatorIndex + 1 );
273
- $ namespacePrefix = substr ($ fullyQualifiedClassName , 0 , $ namespaceSeparatorIndex );
274
- $ message = sprintf (
275
- 'Attempted to load %s "%s" from namespace "%s" in %s line %d. Do you need to "use" it from another namespace? ' ,
276
- $ typeName ,
277
- $ className ,
278
- $ namespacePrefix ,
279
- $ error ['file ' ],
280
- $ error ['line ' ]
281
- );
282
- } else {
283
- $ className = $ fullyQualifiedClassName ;
284
- $ message = sprintf (
285
- 'Attempted to load %s "%s" from the global namespace in %s line %d. Did you forget a use statement for this %s? ' ,
286
- $ typeName ,
287
- $ className ,
288
- $ error ['file ' ],
289
- $ error ['line ' ],
290
- $ typeName
291
- );
292
- }
293
-
294
- if ($ classes = $ this ->getClassCandidates ($ className )) {
295
- $ message .= sprintf (' Perhaps you need to add a use statement for one of the following class: %s. ' , implode (', ' , $ classes ));
296
- }
297
-
298
- return new ClassNotFoundException ($ message , $ exception );
299
- }
300
- }
301
-
302
- /**
303
- * Tries to guess the full namespace for a given class name.
304
- *
305
- * By default, it looks for PSR-0 classes registered via a Symfony or a Composer
306
- * autoloader (that should cover all common cases).
307
- *
308
- * @param string $class A class name (without its namespace)
309
- *
310
- * @return array An array of possible fully qualified class names
311
- */
312
- private function getClassCandidates ($ class )
313
- {
314
- if (!is_array ($ functions = spl_autoload_functions ())) {
315
- return array ();
316
- }
317
-
318
- // find Symfony and Composer autoloaders
319
- $ classes = array ();
320
- foreach ($ functions as $ function ) {
321
- if (!is_array ($ function )) {
322
- continue ;
323
- }
324
-
325
- // get class loaders wrapped by DebugClassLoader
326
- if ($ function [0 ] instanceof DebugClassLoader && method_exists ($ function [0 ], 'getClassLoader ' )) {
327
- $ function [0 ] = $ function [0 ]->getClassLoader ();
328
- }
329
-
330
- if ($ function [0 ] instanceof ComposerClassLoader || $ function [0 ] instanceof SymfonyClassLoader) {
331
- foreach ($ function [0 ]->getPrefixes () as $ paths ) {
332
- foreach ($ paths as $ path ) {
333
- $ classes = array_merge ($ classes , $ this ->findClassInPath ($ function [0 ], $ path , $ class ));
334
- }
179
+ foreach ($ this ->getFatalErrorHandlers () as $ handler ) {
180
+ if ($ ex = $ handler ->handleError ($ error , $ exception )) {
181
+ return $ exceptionHandler [0 ]->handle ($ ex );
335
182
}
336
183
}
337
184
}
338
-
339
- return $ classes ;
340
- }
341
-
342
- private function findClassInPath ($ loader , $ path , $ class )
343
- {
344
- if (!$ path = realpath ($ path )) {
345
- continue ;
346
- }
347
-
348
- $ classes = array ();
349
- $ filename = $ class .'.php ' ;
350
- foreach (new \RecursiveIteratorIterator (new \RecursiveDirectoryIterator ($ path ), \RecursiveIteratorIterator::LEAVES_ONLY ) as $ file ) {
351
- if ($ filename == $ file ->getFileName () && $ class = $ this ->convertFileToClass ($ loader , $ path , $ file ->getPathName ())) {
352
- $ classes [] = $ class ;
353
- }
354
- }
355
-
356
- return $ classes ;
357
- }
358
-
359
- private function convertFileToClass ($ loader , $ path , $ file )
360
- {
361
- // We cannot use the autoloader here as most of them use require; but if the class
362
- // is not found, the new autoloader call will require the file again leading to a
363
- // "cannot redeclare class" error.
364
- require_once $ file ;
365
-
366
- $ file = str_replace (array ($ path .'/ ' , '.php ' ), array ('' , '' ), $ file );
367
-
368
- // is it a namespaced class?
369
- $ class = str_replace ('/ ' , '\\' , $ file );
370
- if (class_exists ($ class , false ) || interface_exists ($ class , false ) || (function_exists ('trait_exists ' ) && trait_exists ($ class , false ))) {
371
- return $ class ;
372
- }
373
-
374
- // is it a PEAR-like class name instead?
375
- $ class = str_replace ('/ ' , '_ ' , $ file );
376
- if (class_exists ($ class , false ) || interface_exists ($ class , false ) || (function_exists ('trait_exists ' ) && trait_exists ($ class , false ))) {
377
- return $ class ;
378
- }
379
185
}
380
186
}
0 commit comments