From 5e5580f90fa5feba79f3cd613d4d45cd2cddd1d8 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Wed, 20 May 2020 02:14:07 +0300 Subject: [PATCH 1/4] fixes#22 added DebugStaticConstructorLoader which adds workaround for DebugClassLoader hijacking --- README.md | 7 +- .../DebugStaticConstructorLoader.php | 188 ++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/StaticConstructorLoader/DebugStaticConstructorLoader.php diff --git a/README.md b/README.md index 6a8cb08..0d35346 100644 --- a/README.md +++ b/README.md @@ -187,9 +187,14 @@ provided in this library: ```php classLoader = $classLoader; + + // find Composer autoloader + $loaders = spl_autoload_functions(); + $otherLoaders = []; + $composerLoader = null; + foreach ($loaders as $loader) { + if (is_array($loader)) { + if ($loader[0] === $classLoader) { + $composerLoader = $loader; + break; + } + if ($loader[0] instanceof self) { + throw new StaticConstructorLoaderException(sprintf('%s already registered', self::class)); + } + } + $otherLoaders[] = $loader; + } + + if (!$composerLoader) { + throw new StaticConstructorLoaderException(sprintf('%s was not found in registered autoloaders', ClassLoader::class)); + } + + // unregister Composer autoloader and all preceding autoloaders + array_map('spl_autoload_unregister', array_merge($otherLoaders, [$composerLoader])); + + // restore the original queue order + $loadersToRestore = array_merge([[$this, 'loadClass']], array_reverse($otherLoaders)); + $flagTrue = array_fill(0, count($loadersToRestore), true); + array_map('spl_autoload_register', $loadersToRestore, $flagTrue, $flagTrue); + } + + public function loadClass($className): ?bool + { + return $this->classLoader->loadClass($className); + } + + public function getPrefixes() + { + return $this->classLoader->getPrefixes(); + } + + public function getPrefixesPsr4() + { + return $this->classLoader->getPrefixesPsr4(); + } + + public function getFallbackDirs() + { + return $this->classLoader->getFallbackDirs(); + } + + public function getFallbackDirsPsr4() + { + return $this->classLoader->getFallbackDirsPsr4(); + } + + public function getClassMap() + { + return $this->classLoader->getClassMap(); + } + + public function addClassMap(array $classMap) + { + $this->classLoader->addClassMap($classMap); + } + + public function add($prefix, $paths, $prepend = false) + { + $this->classLoader->add($prefix, $paths, $prepend); + } + + public function addPsr4($prefix, $paths, $prepend = false) + { + $this->classLoader->addPsr4($prefix, $paths, $prepend); + } + + public function set($prefix, $paths) + { + $this->classLoader->set($prefix, $paths); + } + + public function setPsr4($prefix, $paths) + { + $this->classLoader->setPsr4($prefix, $paths); + } + + public function setUseIncludePath($useIncludePath) + { + $this->classLoader->setUseIncludePath($useIncludePath); + } + + public function getUseIncludePath() + { + return $this->classLoader->getUseIncludePath(); + } + + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classLoader->setClassMapAuthoritative($classMapAuthoritative); + } + + public function isClassMapAuthoritative() + { + return $this->classLoader->isClassMapAuthoritative(); + } + + public function setApcuPrefix($apcuPrefix) + { + $this->classLoader->setApcuPrefix($apcuPrefix); + } + + public function getApcuPrefix() + { + return $this->classLoader->getApcuPrefix(); + } + + public function register($prepend = false) + { + $this->classLoader->register($prepend); + } + + public function unregister() + { + $this->classLoader->unregister(); + } + + public function findFile($class) + { + $path = $this->classLoader->findFile($class); + + if ( + class_exists(\Symfony\Component\ErrorHandler\DebugClassLoader::class, false) + || class_exists(\Symfony\Component\Debug\DebugClassLoader::class, false) + ) { + return $this->handleDebugClassLoader($class, $path); + } + + return $path; + } + + private function handleDebugClassLoader($class, $path) + { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $debugClassLoader = ($backtrace[2] ?? null); + + if ($path + && is_file($path) + && \in_array( + $debugClassLoader['class'] ?? null, + [ + \Symfony\Component\Debug\DebugClassLoader::class, + \Symfony\Component\ErrorHandler\DebugClassLoader::class + ], + true + ) + ) { + include $path; + + if ( + $class !== StaticConstructorInterface::class + && is_a($class, StaticConstructorInterface::class, true) + ) { + $class::__constructStatic(); + } + + return false; + } + + return $path; + } +} \ No newline at end of file From 94513e569e6317ea1c8b6e57354329aa4b5c1be2 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Wed, 10 Jun 2020 17:51:14 +0300 Subject: [PATCH 2/4] #20 added tests --- composer.json | 3 +- .../DebugStaticConstructorLoader.php | 9 +-- .../DebugStaticConstructorLoaderTest.php | 57 +++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 tests/StaticConstructorLoader/DebugStaticConstructorLoaderTest.php diff --git a/composer.json b/composer.json index b00f6e4..9d6ca66 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "myclabs/php-enum": "^1.0", "phpunit/phpunit": "^7.5", "phpbench/phpbench": "^0.16.9", - "vimeo/psalm": "^3.5" + "vimeo/psalm": "^3.5", + "symfony/error-handler": "^5.1" }, "autoload": { "psr-4": { diff --git a/src/StaticConstructorLoader/DebugStaticConstructorLoader.php b/src/StaticConstructorLoader/DebugStaticConstructorLoader.php index 2abe48a..36ac489 100644 --- a/src/StaticConstructorLoader/DebugStaticConstructorLoader.php +++ b/src/StaticConstructorLoader/DebugStaticConstructorLoader.php @@ -146,8 +146,8 @@ public function findFile($class) $path = $this->classLoader->findFile($class); if ( - class_exists(\Symfony\Component\ErrorHandler\DebugClassLoader::class, false) - || class_exists(\Symfony\Component\Debug\DebugClassLoader::class, false) + class_exists('Symfony\Component\ErrorHandler\DebugClassLoader', false) + || class_exists('Symfony\Component\Debug\DebugClassLoader', false) ) { return $this->handleDebugClassLoader($class, $path); } @@ -164,10 +164,7 @@ private function handleDebugClassLoader($class, $path) && is_file($path) && \in_array( $debugClassLoader['class'] ?? null, - [ - \Symfony\Component\Debug\DebugClassLoader::class, - \Symfony\Component\ErrorHandler\DebugClassLoader::class - ], + ['Symfony\Component\Debug\DebugClassLoader', 'Symfony\Component\ErrorHandler\DebugClassLoader'], true ) ) { diff --git a/tests/StaticConstructorLoader/DebugStaticConstructorLoaderTest.php b/tests/StaticConstructorLoader/DebugStaticConstructorLoaderTest.php new file mode 100644 index 0000000..4fcc394 --- /dev/null +++ b/tests/StaticConstructorLoader/DebugStaticConstructorLoaderTest.php @@ -0,0 +1,57 @@ +defaultLoader = \spl_autoload_functions()[0]; + } + + protected function tearDown() + { + DebugClassLoader::disable(); + } + + /** + * @runInSeparateProcess + */ + public function testClassLoadWithDefaultStaticConstrcutorLoader() + { + new StaticConstructorLoader($this->defaultLoader[0]); + $x = ChildOfAbstractEnumeration::$instance; + $this->assertInstanceOf(ChildOfAbstractEnumeration::class, $x); + } + + /** + * @runInSeparateProcess + */ + public function testClassLoadWithDefaultStaticConstrcutorLoaderAndSymfonyDebugLoader() + { + new StaticConstructorLoader($this->defaultLoader[0]); + (new DebugClassLoader($this->defaultLoader))::enable(); + $x = ChildOfAbstractEnumeration::$instance; + $this->assertNull($x); + } + + /** + * @runInSeparateProcess + */ + public function testClassLoadWithDebugStaticConstrcutorLoaderAndSymfonyDebugLoader() + { + new DebugStaticConstructorLoader($this->defaultLoader[0]); + (new DebugClassLoader($this->defaultLoader))::enable(); + $x = ChildOfAbstractEnumeration::$instance; + $this->assertInstanceOf(ChildOfAbstractEnumeration::class, $x); + } +} From c9b5c9c2fb1a272480db756eb245c5e17abe1984 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Wed, 10 Jun 2020 22:59:58 +0300 Subject: [PATCH 3/4] #20 downgraded symfony/error-handler to match 7.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9d6ca66..a7aa78b 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "phpunit/phpunit": "^7.5", "phpbench/phpbench": "^0.16.9", "vimeo/psalm": "^3.5", - "symfony/error-handler": "^5.1" + "symfony/error-handler": "^4.4" }, "autoload": { "psr-4": { From 1b23b147faa6bb7183e51f85ca6900fdfbe49616 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Wed, 10 Jun 2020 23:48:06 +0300 Subject: [PATCH 4/4] #20 added /** @codeCoverageIgnore */ --- .../DebugStaticConstructorLoader.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/StaticConstructorLoader/DebugStaticConstructorLoader.php b/src/StaticConstructorLoader/DebugStaticConstructorLoader.php index 36ac489..4a0648c 100644 --- a/src/StaticConstructorLoader/DebugStaticConstructorLoader.php +++ b/src/StaticConstructorLoader/DebugStaticConstructorLoader.php @@ -46,96 +46,115 @@ public function __construct(ClassLoader $classLoader) array_map('spl_autoload_register', $loadersToRestore, $flagTrue, $flagTrue); } + /** @codeCoverageIgnore */ public function loadClass($className): ?bool { return $this->classLoader->loadClass($className); } + /** @codeCoverageIgnore */ public function getPrefixes() { return $this->classLoader->getPrefixes(); } + /** @codeCoverageIgnore */ public function getPrefixesPsr4() { return $this->classLoader->getPrefixesPsr4(); } + /** @codeCoverageIgnore */ public function getFallbackDirs() { return $this->classLoader->getFallbackDirs(); } + /** @codeCoverageIgnore */ public function getFallbackDirsPsr4() { return $this->classLoader->getFallbackDirsPsr4(); } + /** @codeCoverageIgnore */ public function getClassMap() { return $this->classLoader->getClassMap(); } + /** @codeCoverageIgnore */ public function addClassMap(array $classMap) { $this->classLoader->addClassMap($classMap); } + /** @codeCoverageIgnore */ public function add($prefix, $paths, $prepend = false) { $this->classLoader->add($prefix, $paths, $prepend); } + /** @codeCoverageIgnore */ public function addPsr4($prefix, $paths, $prepend = false) { $this->classLoader->addPsr4($prefix, $paths, $prepend); } + /** @codeCoverageIgnore */ public function set($prefix, $paths) { $this->classLoader->set($prefix, $paths); } + /** @codeCoverageIgnore */ public function setPsr4($prefix, $paths) { $this->classLoader->setPsr4($prefix, $paths); } + /** @codeCoverageIgnore */ public function setUseIncludePath($useIncludePath) { $this->classLoader->setUseIncludePath($useIncludePath); } + /** @codeCoverageIgnore */ public function getUseIncludePath() { return $this->classLoader->getUseIncludePath(); } + /** @codeCoverageIgnore */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classLoader->setClassMapAuthoritative($classMapAuthoritative); } + /** @codeCoverageIgnore */ public function isClassMapAuthoritative() { return $this->classLoader->isClassMapAuthoritative(); } + /** @codeCoverageIgnore */ public function setApcuPrefix($apcuPrefix) { $this->classLoader->setApcuPrefix($apcuPrefix); } + /** @codeCoverageIgnore */ public function getApcuPrefix() { return $this->classLoader->getApcuPrefix(); } + /** @codeCoverageIgnore */ public function register($prepend = false) { $this->classLoader->register($prepend); } + /** @codeCoverageIgnore */ public function unregister() { $this->classLoader->unregister();