bug #21527 [Config] Fix conditional class existence checks (nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[Config] Fix conditional class existence checks

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

Commits
-------

686af61 [Config] Fix conditional class existence checks
This commit is contained in:
Nicolas Grekas 2017-02-05 11:14:32 +01:00
commit a736458fb5
3 changed files with 58 additions and 10 deletions

View File

@ -28,8 +28,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
private $resource;
private $existsStatus;
private static $checkingLevel = 0;
private static $throwingAutoloader;
private static $autoloadLevel = 0;
private static $existsCache = array();
/**
@ -68,12 +67,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
if (null !== $exists = &self::$existsCache[$this->resource]) {
$exists = $exists || class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} elseif (self::EXISTS_KO_WITH_THROWING_AUTOLOADER === $this->existsStatus) {
if (null === self::$throwingAutoloader) {
$signalingException = new \ReflectionException();
self::$throwingAutoloader = function () use ($signalingException) { throw $signalingException; };
}
if (!self::$checkingLevel++) {
spl_autoload_register(self::$throwingAutoloader);
if (!self::$autoloadLevel++) {
spl_autoload_register('Symfony\Component\Config\Resource\ClassExistenceResource::throwOnRequiredClass');
}
try {
@ -81,8 +76,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
} catch (\ReflectionException $e) {
$exists = false;
} finally {
if (!--self::$checkingLevel) {
spl_autoload_unregister(self::$throwingAutoloader);
if (!--self::$autoloadLevel) {
spl_autoload_unregister('Symfony\Component\Config\Resource\ClassExistenceResource::throwOnRequiredClass');
}
}
} else {
@ -115,4 +110,40 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
{
list($this->resource, $this->existsStatus) = unserialize($serialized);
}
/**
* @throws \ReflectionException When $class is not found and is required
*/
private static function throwOnRequiredClass($class)
{
$e = new \ReflectionException("Class $class does not exist");
$trace = $e->getTrace();
$autoloadFrame = array(
'function' => 'spl_autoload_call',
'args' => array($class),
);
$i = 1 + array_search($autoloadFrame, $trace, true);
if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
case 'is_a':
case 'is_subclass_of':
case 'class_exists':
case 'class_implements':
case 'class_parents':
case 'trait_exists':
case 'defined':
case 'interface_exists':
case 'method_exists':
case 'property_exists':
case 'is_callable':
return;
}
}
throw $e;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Component\Config\Tests\Fixtures\Resource;
if (!class_exists(MissingClass::class)) {
class ConditionalClass
{
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Config\Tests\Resource;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Config\Tests\Fixtures\Resource\ConditionalClass;
class ClassExistenceResourceTest extends \PHPUnit_Framework_TestCase
{
@ -71,4 +72,11 @@ EOF
spl_autoload_unregister($autoloader);
}
}
public function testConditionalClass()
{
$res = new ClassExistenceResource(ConditionalClass::class, ClassExistenceResource::EXISTS_KO_WITH_THROWING_AUTOLOADER);
$this->assertFalse($res->isFresh(0));
}
}