Compare commits

...

49 Commits

Author SHA1 Message Date
Fabien Potencier
4a3dd4ef3f Merge branch '2.4' into 2.5
* 2.4:
  [Debug] fixed class lookup when using PSR-0 with a target dir
  fixed standalone tests
  fixed standalone tests
  [Validator] fixed component standalone tests
  fixed standalone component tests depending on Validator and Form
  fixed some composer.json to make standalone component tests pass
  [SecurityBundle] fixed tests when used in standalone

Conflicts:
	src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php
	src/Symfony/Component/Validator/composer.json
2014-09-28 17:22:14 +02:00
Fabien Potencier
17f9a22782 [Debug] fixed class lookup when using PSR-0 with a target dir 2014-09-28 17:03:21 +02:00
Fabien Potencier
df13b35100 Merge branch '2.4' into 2.5
* 2.4:
  fixed CS
  [Process] fixed some volatile tests
  [HttpKernel] fixed a volatile test
  [HttpFoundation] fixed some volatile tests
  [Tests] PHPUnit Optimizations
  Use getPathname() instead of string casting to get BinaryFileReponse file path

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php
	src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php
	src/Symfony/Component/ClassLoader/Tests/ApcUniversalClassLoaderTest.php
	src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php
	src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
	src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
	src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
	src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php
	src/Symfony/Component/Process/Tests/AbstractProcessTest.php
	src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
	src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
	src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php
	src/Symfony/Component/Security/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php
	src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php
	src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php
	src/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php
	src/Symfony/Component/Validator/Constraints/ChoiceValidator.php
	src/Symfony/Component/Validator/Constraints/CollectionValidator.php
	src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php
	src/Symfony/Component/Validator/Tests/Constraints/IsbnValidatorTest.php
	src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
	src/Symfony/Component/Yaml/Parser.php
2014-09-22 11:14:18 +02:00
Fabien Potencier
234c79a3bd Merge branch '2.3' into 2.4
* 2.3:
  fixed CS
  [Process] fixed some volatile tests
  [HttpKernel] fixed a volatile test
  [HttpFoundation] fixed some volatile tests
  Use getPathname() instead of string casting to get BinaryFileReponse file path

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php
	src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php
	src/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.php
	src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
	src/Symfony/Component/ClassLoader/Tests/ApcUniversalClassLoaderTest.php
	src/Symfony/Component/Config/Definition/ReferenceDumper.php
	src/Symfony/Component/Config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
	src/Symfony/Component/Console/Application.php
	src/Symfony/Component/Console/Tests/ApplicationTest.php
	src/Symfony/Component/Filesystem/Exception/IOException.php
	src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php
	src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
	src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php
	src/Symfony/Component/HttpKernel/Tests/Fragment/RoutableFragmentRendererTest.php
	src/Symfony/Component/HttpKernel/Tests/Profiler/Mock/RedisMock.php
	src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
	src/Symfony/Component/PropertyAccess/PropertyAccessor.php
	src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
	src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
	src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
	src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php
	src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
	src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
	src/Symfony/Component/Validator/Constraints/CollectionValidator.php
	src/Symfony/Component/Validator/Tests/ExecutionContextTest.php
2014-09-22 10:51:05 +02:00
Fabien Potencier
993f4c9cfc fixed CS 2014-09-22 10:32:35 +02:00
Endre Fejes
23bc0fb8a3 [Debug] Restoring error handler before assertions 2014-09-19 22:54:50 +02:00
Endre Fejes
36a7e5e0d6 Unit test fixes 2014-09-19 07:23:43 +02:00
Fabien Potencier
189da713c1 Merge branch '2.4' into 2.5
* 2.4:
  added missing test
  fixed CS
  [HttpFoundation] Remove content-related headers if content is empty
  bumped Symfony version to 2.4.8
  removed defaults from PHPUnit configuration

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-09 11:05:48 +02:00
Fabien Potencier
3e0f14c5ab Merge branch '2.4' into 2.5
* 2.4:
  updated VERSION for 2.4.7
  updated CHANGELOG for 2.4.7
  bumped Symfony version to 2.3.18
  updated VERSION for 2.3.17
  update CONTRIBUTORS for 2.3.17
  updated CHANGELOG for 2.3.17
  added XSD to PHPUnit configuration
  add missing docblock for ProcessBuilder::addEnvironmentVariables()
  bug #11319 [HttpKernel] Ensure the storage exists before purging it in ProfilerTest
  [Translation] Added unescaping of ids in PoFileLoader
  updated italian translation for validation messages
  [DomCrawler] Fix docblocks and formatting.
  [DomCrawler] Remove the query string and the anchor of the uri of a link
  Simplified the Travis test command
  Remove Expression Language services when the component is unavailable
  [Console] Make sure formatter is the same

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2014-07-08 14:21:33 +02:00
Nicolas Grekas
b24abbba99 [Debug] work-around https://bugs.php.net/61272 2014-06-11 20:54:02 +02:00
Nicolas Grekas
80fa7316f3 [Debug] simplify code path to remove potential blank pages 2014-06-11 12:05:25 +02:00
Nicolas Grekas
4ebbb9592c [Debug] fix wrong case mismatch exception 2014-06-02 09:03:03 +02:00
Fabien Potencier
7fd8006e46 minor #10988 [Debug] preserve modified error level (nicolas-grekas)
This PR was merged into the 2.5 branch.

Discussion
----------

[Debug] preserve modified error level

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none
| License       | MIT
| Doc PR        | none

Minor edge case, but still a bug fix.
Replaces https://github.com/symfony/symfony/pull/10978

Commits
-------

e40b717 [Debug] preserve modified error level
2014-05-25 16:44:21 +02:00
Nicolas Grekas
3002c206bc [Debug] throw even in stacking mode to preserve code paths 2014-05-25 12:15:31 +02:00
Nicolas Grekas
4a463783a3 [Debug] preserve modified error level 2014-05-25 11:36:54 +02:00
Nicolas Grekas
560cda449b [Debug] better ouf of memory error handling 2014-05-21 14:34:28 +02:00
Nicolas Grekas
2e8dc2e20c [Debug] cleanup interfaces before 2.5-final 2014-05-20 09:56:04 +02:00
Nicolas Grekas
b293ddb67a [Debug] enhance perf of DebugClassLoader 2014-05-08 10:31:54 +02:00
Nicolas Grekas
e25750cda2 [Debug] fix handling deprecated warnings and stacked errors turned into exceptions 2014-05-05 07:50:37 +00:00
Fabien Potencier
e5edd3ae91 Merge branch '2.4'
* 2.4:
  [Debug] fix ErrorHandlerTest when context is not an array
2014-04-29 08:56:05 +02:00
Fabien Potencier
0f428217e4 Merge branch '2.4'
* 2.4:
  [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292
  Allow File instance to be passed to BinaryFileResponse
  Add upgrade instructions for the LoggerInterface
  fixed CS
  Removed strict check when found variables inside a translation
  [ExpressionLanguage] Test for the non-strict in_array check in parsePrimaryExpression in Parser.php
  Strict in_array check in Parser.php
  Updated Serbian latin validation translation

Conflicts:
	src/Symfony/Component/Debug/ErrorHandler.php
2014-04-28 19:44:51 +02:00
Nicolas Grekas
0aaf712be0 [Debug] less intrusive work around for https://bugs.php.net/54275 2014-04-28 18:08:18 +02:00
Nicolas Grekas
79bb38a360 [Debug] fix #10771 DebugClassLoader can't load PSR4 libs 2014-04-24 17:48:28 +02:00
Nicolas Grekas
88a4b49c96 [Debug] Handled errors 2014-04-19 10:12:16 +02:00
Fabien Potencier
e242aa8295 Merge branch '2.4'
* 2.4:
  fixed types in phpdocs
  fixed types in phpdocs

Conflicts:
	src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php
	src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
	src/Symfony/Component/Serializer/Encoder/JsonEncoder.php
	src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
	src/Symfony/Component/Validator/Mapping/ClassMetadata.php
	src/Symfony/Component/Validator/Mapping/ElementMetadata.php
	src/Symfony/Component/Validator/Mapping/MemberMetadata.php
	src/Symfony/Component/Validator/MetadataFactoryInterface.php
2014-04-16 12:36:21 +02:00
Fabien Potencier
c3e530107b made {@inheritdoc} annotations consistent across the board 2014-04-16 10:09:16 +02:00
Fabien Potencier
a6ae0150dc Merge branch '2.4'
* 2.4:
  made types consistent with those defined in Hack
  made {@inheritdoc} annotations consistent across the board
  made {@inheritdoc} annotations consistent across the board
  fixed types in phpdocs
  [Debug] Fixed ClassNotFoundFatalErrorHandler on windows.
  made phpdoc types consistent with those defined in Hack
  Add support Thai translations
  [Validator] Add missing czech translations
  made types consistent with those defined in Hack
  removed extra/unsupported arguments
  [HttpKernel] fixed an error message
  [TwigBundle] removed undefined argument
  [Translation] Make IcuDatFileLoader/IcuResFileLoader::load invalid resource compatible with HHVM.

Conflicts:
	src/Symfony/Component/Console/Helper/TableHelper.php
	src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
	src/Symfony/Component/Form/FormError.php
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
	src/Symfony/Component/Process/ProcessPipes.php
	src/Symfony/Component/PropertyAccess/PropertyAccessor.php
	src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php
	src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
	src/Symfony/Component/Translation/Dumper/FileDumper.php
	src/Symfony/Component/Validator/ConstraintViolation.php
	src/Symfony/Component/Validator/Constraints/EmailValidator.php
	src/Symfony/Component/Validator/ExecutionContextInterface.php
	src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php
2014-04-16 10:08:40 +02:00
Fabien Potencier
5eec1e0a1f Merge branch '2.4'
* 2.4:
  Check headers sent before sending PHP response
  Fix issue symfony/symfony#10345 '[FrameworkBundle][Console] container:debug --parameter="" not working anymore'
  Fixed ACE domain checks on UrlValidator (#10031)
  handle array root element
2014-03-26 19:07:49 +01:00
Nicolas Grekas
efb365b644 [Debug] sync with deprecation in DebugClassLoader 2014-03-21 10:31:50 +01:00
Nicolas Grekas
3d839f44bd [WebProfilerBundle] enhance logs display 2014-03-18 10:04:14 +01:00
Nicolas Grekas
cff0a2e884 [Debug] add a screaming mode to ErrorHandler 2014-03-17 14:44:56 +00:00
Fabien Potencier
8b0839bb30 Merge branch '2.4'
* 2.4:
  [Form][2.3] Fixes empty file-inputs getting treated as extra field.
  changed some PHPUnit assertions to more specific ones
  fixed Kernel::stripComments() normalizing new-lines
  added a BC comment
  Update FileLoader to fix issue #10339
  bumped Symfony version to 2.3.12
  updated VERSION for 2.3.11
  update CONTRIBUTORS for 2.3.11
  updated CHANGELOG for 2.3.11
  Throw exception when unable to normalize embedded object
  Fixed evaluation of short circuit operators
  Follow-up to #10312: Fixed minor performance related issues in Yaml\Inline.
  [2.4][HttpKernel] Fix issue #10209 When the profiler has `only_exception` option activated and a subrequest throw an exception, the parent profile cannot be found.
2014-03-03 13:53:01 +01:00
Andrew Moore
d0dc588a46 Create ExceptionHandlerInterface to allow third party exception handlers' to handle fatal errors 2014-02-28 09:48:11 -05:00
Nicolas Grekas
34a8ad8509 [Debug] enhance non-PSR-0 compatibility for case mismatch test 2014-02-24 14:11:02 +01:00
Nicolas Grekas
57ad6f754e [Debug] error stacking+fatal screaming+case testing 2014-02-16 11:08:14 +01:00
Fabien Potencier
cb1965864a Merge branch '2.4'
* 2.4:
  fixed various inconsistencies
  reduced recursion when building DumperPrefixCollection
  renamed variables - making next change more readable
  removing dead code.
  [ExpressionLanguage] added some tests for the built-in constant() function
  [ExpressionLanguage] added some documentation about functions
  [DomCrawler] Fixed filterXPath() chaining
  [DomCrawler] Fixed incorrect handling of image inputs
2014-02-11 14:52:21 +01:00
Fabien Potencier
429b394c16 Merge branch '2.4'
* 2.4:
  added missing @deprecated tag
  Fixed recursion level incrementing in FlattenException::flattenArgs().
  [HttpKernel] fixed wrong reference in TraceableEventDispatcher

Conflicts:
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
2014-02-04 12:21:37 +01:00
Fabien Potencier
a2c111a569 [Debug] fixed case differences between PHP and HHVM (classes are case-insensitive anyway in PHP) 2014-02-03 11:14:27 +01:00
Fabien Potencier
c3cfefd38b [Debug] made order of suggestions predictable in error messages 2014-02-03 11:14:18 +01:00
Fabien Potencier
ffad875704 Merge branch '2.4'
* 2.4:
  udpated LICENSE year
  update year on licenses
  rundown and typo fix
  [Process] Fix #9861 : Revert TTY mode
  [Form] Update minimal requirement in composer.json
  Fix Empty translations with Qt files
  [Console] Fixed command name guessing if an alternative is an alias.
  Update UPGRADE-2.3.md to account for #9388
  [WebProfilerBundle] Fixed profiler toolbar icons for XHTML.
  [BrowserKit] Throw exception on invalid cookie expiration timestamp
  [Propel1Bridge][ModelChoiceList] add exception message for invalid classes
2014-01-07 14:29:57 +01:00
Fabien Potencier
ac63c60169 Merge branch '2.4'
* 2.4:
  removed unneeded use statements
  [DoctrineBridge] Fixed an issue with DoctrineParserCache
  removed unneeded use statements
  Prepend Child Bundle paths before the parent
  [Routing] add unit tests for Symfony\Component\Routing\RequestContext class
2014-01-01 10:16:05 +01:00
Fabien Potencier
5d95110638 Merge branch '2.4'
* 2.4:
  fixed typos
  Fixed deprecated method calls
  Add testing against HHVM at Travis-CI
  Fixed typo
  Fixed typo
  Fixed typo
  fixed acronyms
  [Validator] Fixed IBAN validator with 0750447346 value

Conflicts:
	src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
2013-12-28 22:40:48 +01:00
Fabien Potencier
dca6697bea Merge branch '2.4'
* 2.4:
  fixed CS
  fixed a typo
  fixed CS for lambdas
  [Yaml] fixed some license headers
  Fixes message value for objects
  Check for hour, minute & second validity
  avoid tables to have apparently long blank line breaks and be too far appart for long nested array params
  fixed various typos
  [Filesystem] Fixed mirror for symlinks
  [Validator] Removed duplicated test for IBAN in data provider
2013-12-28 09:12:58 +01:00
Grégoire Pineau
a1dd7dc6d7 [Debug] Added UndefinedMethodFatalErrorHandler 2013-12-16 11:52:16 +01:00
Fabien Potencier
9566ace63d Merge branch '2.4'
* 2.4:
  fixed CS
  fixed CS
2013-12-12 17:10:54 +01:00
Fabien Potencier
0000f11398 Merge branch '2.4'
* 2.4:
  [Debug] fixed tests
  ErrorHandlerTest: restore_error_handler() on assertion failure
  Fixed typo
  [validator] throw an exception if isn't an instance of ConstraintValidatorInterface.
  Reset the box model to content-box in the web debug toolbar
  [FrameworkBundle] Allowed "0" as a checkbox value in the php template.
  raising delta on stopwatch as travis really breaks with less than 35
  Switched to correct gender of "Token"
  bumped Symfony version to 2.4.1
  updated VERSION for 2.4.0
  updated CHANGELOG for 2.4.0
  fixed typos in several translations
  [HttpKernel] use static late binding when dumping out container

Conflicts:
	src/Symfony/Component/HttpKernel/Kernel.php
2013-12-12 17:07:18 +01:00
Fabien Potencier
b7682104fa Merge branch '2.4'
* 2.4:
  updated Composer suggested packages
  updated VERSION for 2.2.11
  update CONTRIBUTORS for 2.2.11
  updated CHANGELOG for 2.2.11
  Fixed typo in phpdoc
  Default form.csrf_protection.enabled to csrf_protection.enabled
  Handled the scenario when no entity manager is passed with closure query builder.
  Enabled csrf_protection by default if form.csrf_protection is enabled
  [HttpKernel] made a small optimization to Bundle initialization
  minor optimalization at bundle initialization
  [EventDispatcher] tweaked README
  removed observer pattern, in favour of mediator
  [DoctrineBridge] normalized class names in the ORM type guesser
  Fix `extract` method to avoid recalculating count() for each iteration.
  [Debug] ensured that a fatal PHP error is actually fatal after being handled by our error handler
  use the correct class name to retrieve mapped class' metadata and repository
  [WebProfilerBundle] Fixed js escaping in time.html.twig
2013-12-03 15:52:29 +01:00
Fabien Potencier
9e718c0517 Merge branch '2.4'
* 2.4:
  [Debug] fixed unit tests
  Avoid notice from being *eaten* by fatal error.
  Teardown used wrong property
  Modified guessDefaultEscapingStrategy to not escape txt templates
  Fix DateType for 32bits computers.
  Fixed the registration of validation.xml file when the form is disabled
  fixed lexing expression ending with spaces
  Fixes #9633, Removed dependency to Symfony\Bundle\FrameworkBundle\Tests\TestCase
  [Validator] Replaced inexistent interface.
  [HttpKernel] Fix profiler event-listener usage outside request stack context
  When getting the session's id, check if the session is not closed
  Fix undefined offset when formatting namespace suggestions
  Adjusting CacheClear Warmup method to namespaced kernels
2013-11-28 11:27:35 +01:00
Fabien Potencier
598ba50d67 updated version to 2.5 2013-11-24 21:17:07 +01:00
25 changed files with 903 additions and 267 deletions

View File

@@ -1,6 +1,13 @@
CHANGELOG
=========
2.5.0
-----
* added ExceptionHandler::setHandler()
* added UndefinedMethodFatalErrorHandler
* deprecated DummyException
2.4.0
-----

View File

@@ -14,46 +14,72 @@ namespace Symfony\Component\Debug;
/**
* Autoloader checking if the class is really defined in the file found.
*
* The ClassLoader will wrap all registered autoloaders providing a
* findFile method and will throw an exception if a file is found but does
* The ClassLoader will wrap all registered autoloaders
* and will throw an exception if a file is found but does
* not declare the class.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Nicolas Grekas <p@tchwork.com>
*
* @api
*/
class DebugClassLoader
{
private $classFinder;
private $classLoader;
private $isFinder;
private $wasFinder;
private static $caseCheck;
/**
* Constructor.
*
* @param object $classFinder
* @param callable|object $classLoader
*
* @api
* @deprecated since 2.5, passing an object is deprecated and support for it will be removed in 3.0
*/
public function __construct($classFinder)
public function __construct($classLoader)
{
$this->classFinder = $classFinder;
$this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');
if ($this->wasFinder) {
$this->classLoader = array($classLoader, 'loadClass');
$this->isFinder = true;
} else {
$this->classLoader = $classLoader;
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
}
if (!isset(self::$caseCheck)) {
self::$caseCheck = false !== stripos(PHP_OS, 'win') ? (false !== stripos(PHP_OS, 'darwin') ? 2 : 1) : 0;
}
}
/**
* Gets the wrapped class loader.
*
* @return object a class loader instance
* @return callable|object a class loader
*
* @deprecated since 2.5, returning an object is deprecated and support for it will be removed in 3.0
*/
public function getClassLoader()
{
return $this->classFinder;
if ($this->wasFinder) {
return $this->classLoader[0];
} else {
return $this->classLoader;
}
}
/**
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
* Wraps all autoloaders
*/
public static function enable()
{
// Ensures we don't hit https://bugs.php.net/42098
class_exists(__NAMESPACE__.'\ErrorHandler', true);
if (!is_array($functions = spl_autoload_functions())) {
return;
}
@@ -63,8 +89,8 @@ class DebugClassLoader
}
foreach ($functions as $function) {
if (is_array($function) && !$function[0] instanceof self && method_exists($function[0], 'findFile')) {
$function = array(new static($function[0]), 'loadClass');
if (!is_array($function) || !$function[0] instanceof self) {
$function = array(new static($function), 'loadClass');
}
spl_autoload_register($function);
@@ -86,7 +112,7 @@ class DebugClassLoader
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof self) {
$function[0] = $function[0]->getClassLoader();
$function = $function[0]->getClassLoader();
}
spl_autoload_register($function);
@@ -99,10 +125,14 @@ class DebugClassLoader
* @param string $class A class name to resolve to file
*
* @return string|null
*
* @deprecated Deprecated since 2.5, to be removed in 3.0.
*/
public function findFile($class)
{
return $this->classFinder->findFile($class);
if ($this->wasFinder) {
return $this->classLoader[0]->findFile($class);
}
}
/**
@@ -116,16 +146,79 @@ class DebugClassLoader
*/
public function loadClass($class)
{
if ($file = $this->classFinder->findFile($class)) {
require $file;
ErrorHandler::stackErrors();
if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) {
try {
if ($this->isFinder) {
if ($file = $this->classLoader[0]->findFile($class)) {
require $file;
}
} else {
call_user_func($this->classLoader, $class);
$file = false;
}
} catch (\Exception $e) {
ErrorHandler::unstackErrors();
throw $e;
}
ErrorHandler::unstackErrors();
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if ($exists) {
$refl = new \ReflectionClass($class);
$name = $refl->getName();
if ($name !== $class && 0 === strcasecmp($name, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
}
}
if ($file) {
if (!$exists) {
if (false !== strpos($class, '/')) {
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
}
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
}
if (self::$caseCheck && preg_match('#([/\\\\][a-zA-Z_\x7F-\xFF][a-zA-Z0-9_\x7F-\xFF]*)+\.(php|hh)$#D', $file, $tail)) {
$tail = $tail[0];
$real = $refl->getFilename();
if (2 === self::$caseCheck) {
// realpath() on MacOSX doesn't normalize the case of characters
$cwd = getcwd();
$basename = strrpos($real, '/');
chdir(substr($real, 0, $basename));
$basename = substr($real, $basename + 1);
// glob() patterns are case-sensitive even if the underlying fs is not
if (!in_array($basename, glob($basename.'*', GLOB_NOSORT), true)) {
$real = getcwd().'/';
$h = opendir('.');
while (false !== $f = readdir($h)) {
if (0 === strcasecmp($f, $basename)) {
$real .= $f;
break;
}
}
closedir($h);
}
chdir($cwd);
}
if ( 0 === substr_compare($real, $tail, -strlen($tail), strlen($tail), true)
&& 0 !== substr_compare($real, $tail, -strlen($tail), strlen($tail), false)
) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $real));
}
}
return true;
}

View File

@@ -11,11 +11,13 @@
namespace Symfony\Component\Debug;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
@@ -24,6 +26,7 @@ use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstantin Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ErrorHandler
{
@@ -42,7 +45,7 @@ class ErrorHandler
E_ERROR => 'Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_PARSE => 'Parse',
E_PARSE => 'Parse Error',
);
private $level;
@@ -56,6 +59,10 @@ class ErrorHandler
*/
private static $loggers = array();
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
/**
* Registers the error handler.
*
@@ -102,7 +109,7 @@ class ErrorHandler
* Sets a logger for the given channel.
*
* @param LoggerInterface $logger A logger interface
* @param string $channel The channel associated with the logger (deprecation or emergency)
* @param string $channel The channel associated with the logger (deprecation, emergency or scream)
*/
public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
{
@@ -110,113 +117,181 @@ class ErrorHandler
}
/**
* @throws ContextErrorException When error_reporting returns error
* @throws \ErrorException When error_reporting returns error
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
{
if (0 === $this->level) {
return false;
}
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
if (isset(self::$loggers['deprecation'])) {
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);
return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);
return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
}
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
}
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
return true;
}
} elseif ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
$c = $context; // Whatever the signature of the method,
unset($c['GLOBALS'], $context); // $context is always a reference in 5.3
$context = $c;
}
return true;
$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
if ($context && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
// Checking for class existence is a work around for https://bugs.php.net/42098
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context);
} else {
$exception = new \ErrorException($exception, 0, $level, $file, $line);
}
if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
// We temporarily re-enable display_errors to prevent any blank page related to this bug.
$exception->errorHandlerCanary = new ErrorHandlerCanary();
}
throw $exception;
}
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
// make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
require __DIR__.'/Exception/ContextErrorException.php';
}
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException')) {
require __DIR__.'/Exception/FlattenException.php';
}
if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) {
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
} else {
switch ($level) {
case E_USER_ERROR:
case E_RECOVERABLE_ERROR:
$logLevel = LogLevel::ERROR;
break;
if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) {
unset($context['GLOBALS']);
}
case E_WARNING:
case E_USER_WARNING:
$logLevel = LogLevel::WARNING;
break;
$exception = new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);
// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$exceptionHandler[0]->handle($exception);
if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
require __DIR__.'/Exception/DummyException.php';
default:
$logLevel = LogLevel::NOTICE;
break;
}
// we must stop the PHP script execution, as the exception has
// already been dealt with, so, let's throw an exception that
// will be caught by a dummy exception handler
set_exception_handler(function (\Exception $e) use ($exceptionHandler) {
if (!$e instanceof DummyException) {
// happens if our dummy exception is caught by a
// catch-all from user code, in which case, let's the
// current handler handle this "new" exception
call_user_func($exceptionHandler, $e);
}
});
throw new DummyException();
self::$loggers['scream']->log($logLevel, $message, array(
'type' => $level,
'file' => $file,
'line' => $line,
'scream' => error_reporting(),
));
}
}
return false;
}
public function handleFatal()
/**
* Configure the error handler for delayed handling.
* Ensures also that non-catchable fatal errors are never silenced.
*
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
* PHP has a compile stage where it behaves unusually. To workaround it,
* we plug an error handler that only stacks errors for later.
*
* The most important feature of this is to prevent
* autoloading until unstackErrors() is called.
*/
public static function stackErrors()
{
if (null === $error = error_get_last()) {
return;
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
}
/**
* Unstacks stacked errors and forwards to the regular handler
*/
public static function unstackErrors()
{
$level = array_pop(self::$stackedErrorLevels);
if (null !== $level) {
$e = error_reporting($level);
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
// If the user changed the error level, do not overwrite it
error_reporting($e);
}
}
if (empty(self::$stackedErrorLevels)) {
$errors = self::$stackedErrors;
self::$stackedErrors = array();
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
if ($errorHandler) {
foreach ($errors as $e) {
call_user_func_array($errorHandler, $e);
}
}
}
}
public function handleFatal()
{
$this->reservedMemory = '';
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
gc_collect_cycles();
$error = error_get_last();
// get current exception handler
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();
try {
while (self::$stackedErrorLevels) {
static::unstackErrors();
}
} catch (\Exception $exception) {
if ($exceptionHandler) {
call_user_func($exceptionHandler, $exception);
return;
}
if ($this->displayErrors) {
ini_set('display_errors', 1);
}
throw $exception;
}
if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) {
return;
}
if (isset(self::$loggers['emergency'])) {
$fatal = array(
'type' => $type,
'type' => $error['type'],
'file' => $error['file'],
'line' => $error['line'],
);
self::$loggers['emergency']->emerg($error['message'], $fatal);
self::$loggers['emergency']->emergency($error['message'], $fatal);
}
if (!$this->displayErrors) {
return;
}
// get current exception handler
$exceptionHandler = set_exception_handler(function () {});
restore_exception_handler();
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$this->handleFatalError($exceptionHandler[0], $error);
if ($this->displayErrors && $exceptionHandler) {
$this->handleFatalError($exceptionHandler, $error);
}
}
@@ -231,22 +306,64 @@ class ErrorHandler
{
return array(
new UndefinedFunctionFatalErrorHandler(),
new UndefinedMethodFatalErrorHandler(),
new ClassNotFoundFatalErrorHandler(),
);
}
private function handleFatalError(ExceptionHandler $exceptionHandler, array $error)
private function handleFatalError($exceptionHandler, array $error)
{
// Let PHP handle any further error
set_error_handler('var_dump', 0);
ini_set('display_errors', 1);
$level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type'];
$message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']);
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
$exception = new OutOfMemoryException($message, 0, $error['type'], $error['file'], $error['line'], 3, false);
} else {
$exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3, true);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($ex = $handler->handleError($error, $exception)) {
return $exceptionHandler->handle($ex);
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($e = $handler->handleError($error, $exception)) {
$exception = $e;
break;
}
}
}
$exceptionHandler->handle($exception);
try {
call_user_func($exceptionHandler, $exception);
} catch (\Exception $e) {
// The handler failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Private class used to work around https://bugs.php.net/54275
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ErrorHandlerCanary
{
private static $displayErrors = null;
public function __construct()
{
if (null === self::$displayErrors) {
self::$displayErrors = ini_set('display_errors', 1);
}
}
public function __destruct()
{
if (null !== self::$displayErrors) {
ini_set('display_errors', self::$displayErrors);
self::$displayErrors = null;
}
}
}

View File

@@ -28,5 +28,6 @@ class ClassNotFoundException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@@ -12,9 +12,9 @@
namespace Symfony\Component\Debug\Exception;
/**
* Used to stop execution of a PHP script after handling a fatal error.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 2.5, to be removed in 3.0.
*/
class DummyException extends \ErrorException
{

View File

@@ -14,8 +14,58 @@ namespace Symfony\Component\Debug\Exception;
/**
* Fatal Error Exception.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstanton Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class FatalErrorException extends \ErrorException
{
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
if (null !== $traceOffset) {
if (function_exists('xdebug_get_function_stack')) {
$trace = xdebug_get_function_stack();
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
}
foreach ($trace as &$frame) {
if (!isset($frame['type'])) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (isset($frame['class'])) {
$frame['type'] = '::';
}
} elseif ('dynamic' === $frame['type']) {
$frame['type'] = '->';
} elseif ('static' === $frame['type']) {
$frame['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (!$traceArgs) {
unset($frame['params'], $frame['args']);
} elseif (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
unset($frame['params']);
}
}
unset($frame);
$trace = array_reverse($trace);
} else {
$trace = array();
}
$this->setTrace($trace);
}
}
protected function setTrace($trace)
{
$traceReflector = new \ReflectionProperty('Exception', 'trace');
$traceReflector->setAccessible(true);
$traceReflector->setValue($this, $trace);
}
}

View File

@@ -172,36 +172,7 @@ class FlattenException
public function setTraceFromException(\Exception $exception)
{
$trace = $exception->getTrace();
if ($exception instanceof FatalErrorException) {
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
foreach ($trace as $i => $frame) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type'])) {
$trace[$i]['type'] = '??';
}
if ('dynamic' === $trace[$i]['type']) {
$trace[$i]['type'] = '->';
} elseif ('static' === $trace[$i]['type']) {
$trace[$i]['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (isset($frame['params']) && !isset($frame['args'])) {
$trace[$i]['args'] = $frame['params'];
unset($trace[$i]['params']);
}
}
} else {
$trace = array_slice(array_reverse($trace), 1);
}
}
$this->setTrace($trace, $exception->getFile(), $exception->getLine());
$this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
}
public function setTrace($trace, $file, $line)

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class OutOfMemoryException extends FatalErrorException
{
}

View File

@@ -28,5 +28,6 @@ class UndefinedFunctionException extends FatalErrorException
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Undefined Method Exception.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\Debug;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
@@ -28,11 +29,15 @@ if (!defined('ENT_SUBSTITUTE')) {
* available, the Response content is always HTML.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ExceptionHandler
{
private $debug;
private $charset;
private $handler;
private $caughtBuffer;
private $caughtLength;
public function __construct($debug = true, $charset = 'UTF-8')
{
@@ -56,6 +61,65 @@ class ExceptionHandler
return $handler;
}
/**
* Sets a user exception handler.
*
* @param callable $handler An handler that will be called on Exception
*
* @return callable|null The previous exception handler if any
*/
public function setHandler($handler)
{
if (null !== $handler && !is_callable($handler)) {
throw new \LogicException('The exception handler must be a valid PHP callable.');
}
$old = $this->handler;
$this->handler = $handler;
return $old;
}
/**
* Sends a response for the given Exception.
*
* To be as fail-safe as possible, the exception is first handled
* by our simple exception handler, then by the user exception handler.
* The latter takes precedence and any output from the former is cancelled,
* if and only if nothing bad happens in this handling path.
*/
public function handle(\Exception $exception)
{
if (null === $this->handler || $exception instanceof OutOfMemoryException) {
$this->failSafeHandle($exception);
return;
}
$caughtLength = $this->caughtLength = 0;
ob_start(array($this, 'catchOutput'));
$this->failSafeHandle($exception);
while (null === $this->caughtBuffer && ob_end_flush()) {
// Empty loop, everything is in the condition
}
if (isset($this->caughtBuffer[0])) {
ob_start(array($this, 'cleanOutput'));
echo $this->caughtBuffer;
$caughtLength = ob_get_length();
}
$this->caughtBuffer = null;
try {
call_user_func($this->handler, $exception);
$this->caughtLength = $caughtLength;
} catch (\Exception $e) {
if (!$caughtLength) {
// All handlers failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Sends a response for the given Exception.
*
@@ -63,15 +127,15 @@ class ExceptionHandler
* this method will use it to create and send the response. If not,
* it will fallback to plain PHP functions.
*
* @param \Exception $exception An \Exception instance
*
* @see sendPhpResponse
* @see createResponse
*/
public function handle(\Exception $exception)
private function failSafeHandle(\Exception $exception)
{
if (class_exists('Symfony\Component\HttpFoundation\Response')) {
$this->createResponse($exception)->send();
if (class_exists('Symfony\Component\HttpFoundation\Response', false)) {
$response = $this->createResponse($exception);
$response->sendHeaders();
$response->sendContent();
} else {
$this->sendPhpResponse($exception);
}
@@ -315,4 +379,30 @@ EOF;
return implode(', ', $result);
}
/**
* @internal
*/
public function catchOutput($buffer)
{
$this->caughtBuffer = $buffer;
return '';
}
/**
* @internal
*/
public function cleanOutput($buffer)
{
if ($this->caughtLength) {
// use substr_replace() instead of substr() for mbstring overloading resistance
$cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
if (isset($cleanBuffer[0])) {
$buffer = $cleanBuffer;
}
}
return $buffer;
}
}

View File

@@ -103,14 +103,23 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader && method_exists($function[0], 'getClassLoader')) {
$function[0] = $function[0]->getClassLoader();
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
// Since 2.5, returning an object from DebugClassLoader::getClassLoader() is @deprecated
if (is_object($function)) {
$function = array($function);
}
if (!is_array($function)) {
continue;
}
}
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
foreach ($function[0]->getPrefixes() as $paths) {
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
foreach ($paths as $path) {
$classes = array_merge($classes, $this->findClassInPath($path, $class));
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
}
}
}
@@ -122,10 +131,11 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
/**
* @param string $path
* @param string $class
* @param string $prefix
*
* @return array
*/
private function findClassInPath($path, $class)
private function findClassInPath($path, $class, $prefix)
{
if (!$path = realpath($path)) {
return array();
@@ -134,7 +144,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
$classes = array();
$filename = $class.'.php';
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName())) {
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
$classes[] = $class;
}
}
@@ -145,27 +155,38 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
/**
* @param string $path
* @param string $file
* @param string $prefix
*
* @return string|null
*/
private function convertFileToClass($path, $file)
private function convertFileToClass($path, $file, $prefix)
{
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file);
$pearClass = str_replace('\\', '_', $namespacedClass);
$candidates = array(
// namespaced class
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
// namespaced class (with target dir)
$namespacedClassTargetDir = $prefix.str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
// PEAR class
str_replace('\\', '_', $namespacedClass),
// PEAR class (with target dir)
str_replace('\\', '_', $namespacedClassTargetDir),
);
// We cannot use the autoloader here as most of them use require; but if the class
// is not found, the new autoloader call will require the file again leading to a
// "cannot redeclare class" error.
if (!$this->classExists($namespacedClass) && !$this->classExists($pearClass)) {
require_once $file;
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
if ($this->classExists($namespacedClass)) {
return $namespacedClass;
}
require_once $file;
if ($this->classExists($pearClass)) {
return $pearClass;
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
}

View File

@@ -80,6 +80,7 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
}
if ($candidates) {
sort($candidates);
$message .= ' Did you mean to call: '.implode(', ', array_map(function ($val) {
return '"'.$val.'"';
}, $candidates)).'?';

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;
/**
* ErrorHandler for undefined methods.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
if (!$matches) {
return;
}
$className = $matches[1];
$methodName = $matches[2];
$message = sprintf('Attempted to call method "%s" on class "%s" in %s line %d.', $methodName, $className, $error['file'], $error['line']);
$candidates = array();
foreach (get_class_methods($className) as $definedMethodName) {
$lev = levenshtein($methodName, $definedMethodName);
if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
$candidates[] = $definedMethodName;
}
}
if ($candidates) {
sort($candidates);
$message .= sprintf(' Did you mean to call: "%s"?', implode('", "', $candidates));
}
return new UndefinedMethodException($message, $exception);
}
}

View File

@@ -12,46 +12,150 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\ContextErrorException;
class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var int Error reporting level before running tests.
*/
private $errorReporting;
private $loader;
protected function setUp()
{
$this->errorReporting = error_reporting(E_ALL | E_STRICT);
$this->loader = new ClassLoader();
spl_autoload_register(array($this->loader, 'loadClass'));
spl_autoload_register(array($this->loader, 'loadClass'), true, true);
DebugClassLoader::enable();
}
protected function tearDown()
{
DebugClassLoader::disable();
spl_autoload_unregister(array($this->loader, 'loadClass'));
error_reporting($this->errorReporting);
}
public function testIdempotence()
{
DebugClassLoader::enable();
DebugClassLoader::enable();
$functions = spl_autoload_functions();
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof DebugClassLoader) {
$reflClass = new \ReflectionClass($function[0]);
$reflProp = $reflClass->getProperty('classFinder');
$reflProp = $reflClass->getProperty('classLoader');
$reflProp->setAccessible(true);
$this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0]));
DebugClassLoader::disable();
return;
}
}
DebugClassLoader::disable();
$this->fail('DebugClassLoader did not register');
}
public function testUnsilencing()
{
ob_start();
$bak = array(
ini_set('log_errors', 0),
ini_set('display_errors', 1),
);
// See below: this will fail with parse error
// but this should not be @-silenced.
@class_exists(__NAMESPACE__.'\TestingUnsilencing', true);
ini_set('log_errors', $bak[0]);
ini_set('display_errors', $bak[1]);
$output = ob_get_clean();
$this->assertStringMatchesFormat('%aParse error%a', $output);
}
public function testStacking()
{
// the ContextErrorException must not be loaded to test the workaround
// for https://bugs.php.net/65322.
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
ErrorHandler::register();
try {
// Trigger autoloading + E_STRICT at compile time
// which in turn triggers $errorHandler->handle()
// that again triggers autoloading for ContextErrorException.
// Error stacking works around the bug above and everything is fine.
eval('
namespace '.__NAMESPACE__.';
class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
');
$this->fail('ContextErrorException expected');
} catch (\ErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
restore_exception_handler();
$this->assertEquals(E_STRICT, $exception->getSeverity());
$this->assertStringStartsWith(__FILE__, $exception->getFile());
$this->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage());
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
/**
* @expectedException \RuntimeException
*/
public function testNameCaseMismatch()
{
class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
}
/**
* @expectedException \RuntimeException
*/
public function testFileCaseMismatch()
{
if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
$this->markTestSkipped('Can only be run on case insensitive filesystems');
}
class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
}
/**
* @expectedException \RuntimeException
*/
public function testPsr4CaseMismatch()
{
class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
}
public function testNotPsr0()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
}
public function testNotPsr0Bis()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
}
public function testClassAlias()
{
$this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
}
}
class ClassLoader
@@ -60,7 +164,27 @@ class ClassLoader
{
}
public function getClassMap()
{
return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
}
public function findFile($class)
{
if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
eval('-- parse error --');
} elseif (__NAMESPACE__.'\TestingStacking' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
} elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
} elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) {
return __DIR__.'/Fixtures/CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
return __DIR__.'/Fixtures/psr4/Psr4CaseMismatch.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
return __DIR__.'/Fixtures/reallyNotPsr0.php';
} elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
return __DIR__.'/Fixtures/notPsr0Bis.php';
}
}
}

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\Debug\Tests;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\DummyException;
use Symfony\Component\Debug\Exception\ContextErrorException;
/**
* ErrorHandlerTest
@@ -44,104 +44,35 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
error_reporting($this->errorReporting);
}
public function testCompileTimeError()
{
// the ContextErrorException must not be loaded to test the workaround
// for https://bugs.php.net/bug.php?id=65322.
if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
$this->markTestSkipped('The ContextErrorException class is already loaded.');
}
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
// the following code forces some PHPUnit classes to be loaded
// so that they will be available in the exception handler
// as they won't be autoloaded by PHP
class_exists('PHPUnit_Framework_MockObject_Invocation_Object');
$this->assertInstanceOf('stdClass', new \stdClass());
$this->assertEquals(1, 1);
$this->assertStringStartsWith('foo', 'foobar');
$this->assertArrayHasKey('bar', array('bar' => 'foo'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_STRICT, $exception->getSeverity());
$that->assertEquals(2, $exception->getLine());
$that->assertStringStartsWith('Runtime Notice: Declaration of _CompileTimeError::foo() should be compatible with', $exception->getMessage());
$that->assertArrayHasKey('bar', $exception->getContext());
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck))
;
ErrorHandler::register();
set_exception_handler(array($exceptionHandler, 'handle'));
// dummy variable to check for in error handler.
$bar = 123;
// trigger compile time error
try {
eval(<<<'PHP'
class _BaseCompileTimeError { function foo() {} }
class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} }
PHP
);
} catch (DummyException $e) {
// if an exception is thrown, the test passed
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
restore_error_handler();
restore_exception_handler();
}
public function testNotice()
{
$exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle'));
set_exception_handler(array($exceptionHandler, 'handle'));
$that = $this;
$exceptionCheck = function ($exception) use ($that) {
$that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception);
$that->assertEquals(E_NOTICE, $exception->getSeverity());
$that->assertEquals(__LINE__ + 44, $exception->getLine());
$that->assertEquals(__FILE__, $exception->getFile());
$that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$that->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$that->assertEquals(__FILE__, $trace[0]['file']);
$that->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$that->assertEquals('handle', $trace[0]['function']);
$that->assertEquals('->', $trace[0]['type']);
$that->assertEquals(__FILE__, $trace[1]['file']);
$that->assertEquals(__CLASS__, $trace[1]['class']);
$that->assertEquals('triggerNotice', $trace[1]['function']);
$that->assertEquals('::', $trace[1]['type']);
$that->assertEquals(__CLASS__, $trace[2]['class']);
$that->assertEquals('testNotice', $trace[2]['function']);
$that->assertEquals('->', $trace[2]['type']);
};
$exceptionHandler->expects($this->once())
->method('handle')
->will($this->returnCallback($exceptionCheck));
ErrorHandler::register();
try {
self::triggerNotice($this);
} catch (DummyException $e) {
$this->fail('ContextErrorException expected');
} catch (ContextErrorException $exception) {
// if an exception is thrown, the test passed
restore_error_handler();
$this->assertEquals(E_NOTICE, $exception->getSeverity());
$this->assertEquals(__FILE__, $exception->getFile());
$this->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
$this->assertArrayHasKey('foobar', $exception->getContext());
$trace = $exception->getTrace();
$this->assertEquals(__FILE__, $trace[0]['file']);
$this->assertEquals('Symfony\Component\Debug\ErrorHandler', $trace[0]['class']);
$this->assertEquals('handle', $trace[0]['function']);
$this->assertEquals('->', $trace[0]['type']);
$this->assertEquals(__FILE__, $trace[1]['file']);
$this->assertEquals(__CLASS__, $trace[1]['class']);
$this->assertEquals('triggerNotice', $trace[1]['function']);
$this->assertEquals('::', $trace[1]['type']);
$this->assertEquals(__CLASS__, $trace[2]['class']);
$this->assertEquals('testNotice', $trace[2]['function']);
$this->assertEquals('->', $trace[2]['type']);
} catch (\Exception $e) {
restore_error_handler();
@@ -203,12 +134,12 @@ PHP
restore_error_handler();
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
$this->assertFalse($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$handler = ErrorHandler::register(E_DEPRECATED);
$this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
$this->assertFalse($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
@@ -231,7 +162,29 @@ PHP
$handler = ErrorHandler::register(E_USER_DEPRECATED);
$handler->setLogger($logger);
$handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array());
$this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));
restore_error_handler();
$logger = $this->getMock('Psr\Log\LoggerInterface');
$that = $this;
$logArgCheck = function ($level, $message, $context) use ($that) {
$that->assertEquals('Undefined variable: undefVar', $message);
$that->assertArrayHasKey('type', $context);
$that->assertEquals($context['type'], E_NOTICE);
};
$logger
->expects($this->once())
->method('log')
->will($this->returnCallback($logArgCheck))
;
$handler = ErrorHandler::register(E_NOTICE);
$handler->setLogger($logger, 'scream');
unset($undefVar);
@$undefVar++;
restore_error_handler();
} catch (\Exception $e) {
@@ -251,10 +204,12 @@ PHP
$m = new \ReflectionMethod($handler, 'handleFatalError');
$m->setAccessible(true);
$m->invoke($handler, $exceptionHandler, $error);
$m->invoke($handler, array($exceptionHandler, 'handle'), $error);
restore_error_handler();
$this->assertInstanceof($class, $exceptionHandler->e);
$this->assertSame($translatedMessage, $exceptionHandler->e->getMessage());
// class names are case insensitive and PHP/HHVM do not return the same
$this->assertSame(strtolower($translatedMessage), strtolower($exceptionHandler->e->getMessage()));
$this->assertSame($error['type'], $exceptionHandler->e->getSeverity());
$this->assertSame($error['file'], $exceptionHandler->e->getFile());
$this->assertSame($error['line'], $exceptionHandler->e->getLine());

View File

@@ -114,7 +114,6 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
$this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
$this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
}
/**
@@ -160,13 +159,13 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(array(
'message' => 'test',
'class' => 'Exception',
'trace' => array(array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => array()
'args' => array(),
)),
)
),
), $flattened->toArray());
}
@@ -202,7 +201,7 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
'line' => 123,
'function' => 'test',
'args' => array(
unserialize('O:14:"BogusTestClass":0:{}')
unserialize('O:14:"BogusTestClass":0:{}'),
),
),
),
@@ -211,9 +210,9 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(
'message' => 'test',
'class' => 'Exception',
'trace' => array(
array(
'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '',
'file' => 'foo.php', 'line' => 123,
@@ -224,12 +223,12 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
'file' => __FILE__, 'line' => 123,
'args' => array(
array(
'incomplete-object', 'BogusTestClass'
'incomplete-object', 'BogusTestClass',
),
),
)
),
),
)
),
), $flattened->toArray());
}
}

View File

@@ -25,7 +25,8 @@ class UndefinedFunctionFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
// class names are case insensitive and PHP/HHVM do not return the same
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
class UndefinedMethodFatalErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideUndefinedMethodData
*/
public function testUndefinedMethod($error, $translatedMessage)
{
$handler = new UndefinedMethodFatalErrorHandler();
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
$this->assertInstanceof('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception);
$this->assertSame($translatedMessage, $exception->getMessage());
$this->assertSame($error['type'], $exception->getSeverity());
$this->assertSame($error['file'], $exception->getFile());
$this->assertSame($error['line'], $exception->getLine());
}
public function provideUndefinedMethodData()
{
return array(
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::what()',
),
'Attempted to call method "what" on class "SplObjectStorage" in foo.php line 12.',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::walid()',
),
'Attempted to call method "walid" on class "SplObjectStorage" in foo.php line 12. Did you mean to call: "valid"?',
),
array(
array(
'type' => 1,
'line' => 12,
'file' => 'foo.php',
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
),
'Attempted to call method "offsetFet" on class "SplObjectStorage" in foo.php line 12. Did you mean to call: "offsetGet", "offsetSet", "offsetUnset"?',
),
);
}
}

View File

@@ -0,0 +1,3 @@
<?php
class_alias('Symfony\Component\Debug\Tests\Fixtures\NotPSR0bis', 'Symfony\Component\Debug\Tests\Fixtures\ClassAlias');

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class CaseMismatch
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class NotPSR0bis
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class PSR4CaseMismatch
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class NotPSR0
{
}

View File

@@ -33,7 +33,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "2.5-dev"
}
}
}