Skip to content

Added DebugStaticConstructorLoader #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,14 @@ provided in this library:
```php
<?php
use Dbalabka\StaticConstructorLoader\StaticConstructorLoader;
use Dbalabka\StaticConstructorLoader\DebugStaticConstructorLoader;

$composer = require_once(__DIR__ . '/vendor/autoload.php');
$loader = new StaticConstructorLoader($composer);
if ($_ENV['APP_ENV'] === 'dev') { // if running Symfony with dev-mode
$loader = new DebugStaticConstructorLoader(new StaticConstructorLoader($composer));
} else {
$loader = new StaticConstructorLoader($composer);
}
```
Also, it would be very helpful to have expression based properties initialization:
```php
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "^4.4"
},
"autoload": {
"psr-4": {
Expand Down
204 changes: 204 additions & 0 deletions src/StaticConstructorLoader/DebugStaticConstructorLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php declare(strict_types=1);

namespace Dbalabka\StaticConstructorLoader;

use Composer\Autoload\ClassLoader;
use Dbalabka\StaticConstructorLoader\Exception\StaticConstructorLoaderException;

class DebugStaticConstructorLoader extends ClassLoader /* extending for an contract */
{
/**
* @var ClassLoader
*/
private $classLoader;

public function __construct(ClassLoader $classLoader)
{
$this->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);
}

/** @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();
}

public function findFile($class)
{
$path = $this->classLoader->findFile($class);

if (
class_exists('Symfony\Component\ErrorHandler\DebugClassLoader', false)
|| class_exists('Symfony\Component\Debug\DebugClassLoader', 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', 'Symfony\Component\ErrorHandler\DebugClassLoader'],
true
)
) {
include $path;

if (
$class !== StaticConstructorInterface::class
&& is_a($class, StaticConstructorInterface::class, true)
) {
$class::__constructStatic();
}

return false;
}

return $path;
}
}
57 changes: 57 additions & 0 deletions tests/StaticConstructorLoader/DebugStaticConstructorLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);

namespace Dbalabka\Enumeration\Tests\StaticConstructorLoader;

use Dbalabka\StaticConstructorLoader\DebugStaticConstructorLoader;
use Dbalabka\StaticConstructorLoader\StaticConstructorLoader;
use Dbalabka\StaticConstructorLoader\Tests\Fixtures\ChildOfAbstractEnumeration;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\DebugClassLoader;

class DebugStaticConstructorLoaderTest extends TestCase
{
/** @var callable */
private $defaultLoader;

protected function setUp()
{
$this->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);
}
}