From 068242800a0d1e4f5e9a4fbf281a5b051f53ec0b Mon Sep 17 00:00:00 2001 From: butschster Date: Thu, 10 Aug 2023 20:30:19 +0400 Subject: [PATCH] Changes behavior for loading system bootloaders. Now system bootloaders init only after Environment is bound to the container --- src/Boot/src/AbstractKernel.php | 7 ++-- .../Attributes/AttributesBootloader.php | 9 +++-- .../src/Bootloader/TokenizerBootloader.php | 15 ++++--- .../TokenizerListenerBootloader.php | 7 +--- src/Tokenizer/src/Config/TokenizerConfig.php | 11 ++++-- tests/Framework/BaseTestCase.php | 19 ++++++++- ...ibutesWithoutAnnotationsBootloaderTest.php | 39 ++++++++++++------- .../Tokenizer/TokenizerBootloaderTest.php | 4 ++ .../Dispatcher/ConsoleDispatcherTest.php | 10 +++-- tests/Framework/Http/ControllerTest.php | 3 +- .../Framework/Tokenizer/Config/ConfigTest.php | 28 +++++++++++++ tests/app/src/Command/DeadCommand.php | 4 +- tests/app/src/Controller/TestController.php | 2 +- tests/app/src/TestApp.php | 22 +++++++++++ 14 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 tests/Framework/Tokenizer/Config/ConfigTest.php diff --git a/src/Boot/src/AbstractKernel.php b/src/Boot/src/AbstractKernel.php index 8d45f486e..b40aab518 100644 --- a/src/Boot/src/AbstractKernel.php +++ b/src/Boot/src/AbstractKernel.php @@ -87,8 +87,6 @@ protected function __construct( $this->finalizer = new Finalizer(); $container->bindSingleton(FinalizerInterface::class, $this->finalizer); - - $this->bootloader->bootload($this->defineSystemBootloaders()); } /** @@ -162,13 +160,14 @@ public function run(?EnvironmentInterface $environment = null): ?self $environment ??= new Environment(); $this->container->bindSingleton(EnvironmentInterface::class, $environment); - $this->fireCallbacks($this->runningCallbacks); - try { // will protect any against env overwrite action $this->container->runScope( [EnvironmentInterface::class => $environment], function (): void { + $this->bootloader->bootload($this->defineSystemBootloaders()); + $this->fireCallbacks($this->runningCallbacks); + $this->bootload(); $this->bootstrap(); diff --git a/src/Framework/Bootloader/Attributes/AttributesBootloader.php b/src/Framework/Bootloader/Attributes/AttributesBootloader.php index e0409de0f..fc3aae442 100644 --- a/src/Framework/Bootloader/Attributes/AttributesBootloader.php +++ b/src/Framework/Bootloader/Attributes/AttributesBootloader.php @@ -18,6 +18,7 @@ use Spiral\Attributes\Psr16CachedReader; use Spiral\Attributes\ReaderInterface; use Spiral\Boot\Bootloader\Bootloader; +use Spiral\Boot\EnvironmentInterface; use Spiral\Config\ConfiguratorInterface; class AttributesBootloader extends Bootloader @@ -32,13 +33,13 @@ public function __construct( ) { } - public function init(): void + public function init(EnvironmentInterface $env): void { $this->config->setDefaults( AttributesConfig::CONFIG, [ 'annotations' => [ - 'support' => true, + 'support' => $env->get('SUPPORT_ANNOTATIONS', true), ], ], ); @@ -68,7 +69,9 @@ private function initReader( $reader = new Psr16CachedReader($reader, $cache); } - if ($config->isAnnotationsReaderEnabled()) { + $supportAnnotations = $config->isAnnotationsReaderEnabled(); + + if ($supportAnnotations) { if (!\interface_exists(DoctrineReaderInterface::class)) { throw new InitializationException( 'Doctrine annotations reader is not available, please install "doctrine/annotations" package', diff --git a/src/Tokenizer/src/Bootloader/TokenizerBootloader.php b/src/Tokenizer/src/Bootloader/TokenizerBootloader.php index b4ad211ae..4114329ae 100644 --- a/src/Tokenizer/src/Bootloader/TokenizerBootloader.php +++ b/src/Tokenizer/src/Bootloader/TokenizerBootloader.php @@ -6,6 +6,7 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Boot\DirectoriesInterface; +use Spiral\Boot\EnvironmentInterface; use Spiral\Config\ConfiguratorInterface; use Spiral\Config\Patch\Append; use Spiral\Core\BinderInterface; @@ -47,7 +48,7 @@ public function __construct( ) { } - public function init(BinderInterface $binder, DirectoriesInterface $dirs): void + public function init(BinderInterface $binder, DirectoriesInterface $dirs, EnvironmentInterface $env): void { $binder->bindInjector(ClassLocator::class, ClassLocatorInjector::class); $binder->bindInjector(EnumLocator::class, EnumLocatorInjector::class); @@ -65,7 +66,11 @@ public function init(BinderInterface $binder, DirectoriesInterface $dirs): void 'tests', 'migrations', ], - ] + 'cache' => [ + 'directory' => $dirs->get('runtime') . 'cache/listeners', + 'enabled' => \filter_var($env->get('TOKENIZER_CACHE_TARGETS', false), \FILTER_VALIDATE_BOOL), + ], + ], ); } @@ -76,7 +81,7 @@ public function addDirectory(string $directory): void { $this->config->modify( TokenizerConfig::CONFIG, - new Append('directories', null, $directory) + new Append('directories', null, $directory), ); } @@ -88,13 +93,13 @@ public function addScopedDirectory(string $scope, string $directory): void if (!isset($this->config->getConfig(TokenizerConfig::CONFIG)['scopes'][$scope])) { $this->config->modify( TokenizerConfig::CONFIG, - new Append('scopes', $scope, []) + new Append('scopes', $scope, []), ); } $this->config->modify( TokenizerConfig::CONFIG, - new Append('scopes.' . $scope, null, $directory) + new Append('scopes.' . $scope, null, $directory), ); } } diff --git a/src/Tokenizer/src/Bootloader/TokenizerListenerBootloader.php b/src/Tokenizer/src/Bootloader/TokenizerListenerBootloader.php index 07d73832f..29593117a 100644 --- a/src/Tokenizer/src/Bootloader/TokenizerListenerBootloader.php +++ b/src/Tokenizer/src/Bootloader/TokenizerListenerBootloader.php @@ -111,12 +111,9 @@ private function makeCachedLoader( // but not read from there. return $factory->make($classLoader, [ 'memory' => $factory->make(Memory::class, [ - 'directory' => $config->getCacheDirectory() ?? $dirs->get('runtime') . 'cache/listeners', + 'directory' => $config->getCacheDirectory(), ]), - 'readCache' => \filter_var( - $env->get('TOKENIZER_CACHE_TARGETS', $config->isCacheEnabled()), - \FILTER_VALIDATE_BOOL - ), + 'readCache' => $config->isCacheEnabled(), ]); } diff --git a/src/Tokenizer/src/Config/TokenizerConfig.php b/src/Tokenizer/src/Config/TokenizerConfig.php index bd15a6ec2..121606414 100644 --- a/src/Tokenizer/src/Config/TokenizerConfig.php +++ b/src/Tokenizer/src/Config/TokenizerConfig.php @@ -81,6 +81,11 @@ public function getScope(string $scope): array ]; } + public function getScopes(): array + { + return $this->config['scopes'] ?? []; + } + /** * Check if tokenizer listeners cache is enabled. */ @@ -102,16 +107,16 @@ public function getCacheDirectory(): ?string public function isLoadClassesEnabled(): bool { - return (bool) ($this->config['load']['classes'] ?? true); + return (bool)($this->config['load']['classes'] ?? true); } public function isLoadEnumsEnabled(): bool { - return (bool) ($this->config['load']['enums'] ?? false); + return (bool)($this->config['load']['enums'] ?? false); } public function isLoadInterfacesEnabled(): bool { - return (bool) ($this->config['load']['interfaces'] ?? false); + return (bool)($this->config['load']['interfaces'] ?? false); } } diff --git a/tests/Framework/BaseTestCase.php b/tests/Framework/BaseTestCase.php index 500e20613..7f878d3a5 100644 --- a/tests/Framework/BaseTestCase.php +++ b/tests/Framework/BaseTestCase.php @@ -6,9 +6,14 @@ use Spiral\App\TestApp; use Spiral\Core\Container; +use Spiral\Testing\Traits\InteractsWithCore; abstract class BaseTestCase extends \Spiral\Testing\TestCase { + use InteractsWithCore; + + private array $disabledBootloaders = []; + public function rootDirectory(): string { return \realpath(__DIR__.'/../'); @@ -19,7 +24,19 @@ public function createAppInstance(Container $container = new Container()): TestA return TestApp::create( $this->defineDirectories($this->rootDirectory()), false - ); + )->disableBootloader(...$this->disabledBootloaders); + } + + public function withDisabledBootloaders(string ... $bootloader): self + { + $this->disabledBootloaders = $bootloader; + + return $this; + } + + public function getTestEnvVariables(): array + { + return [...static::ENV, ...$this->getEnvVariablesFromConfig()]; } protected function tearDown(): void diff --git a/tests/Framework/Bootloader/Attributes/AttributesWithoutAnnotationsBootloaderTest.php b/tests/Framework/Bootloader/Attributes/AttributesWithoutAnnotationsBootloaderTest.php index d4989e3c8..20642ebf5 100644 --- a/tests/Framework/Bootloader/Attributes/AttributesWithoutAnnotationsBootloaderTest.php +++ b/tests/Framework/Bootloader/Attributes/AttributesWithoutAnnotationsBootloaderTest.php @@ -5,30 +5,39 @@ namespace Framework\Bootloader\Attributes; use Spiral\Attributes\AttributeReader; +use Spiral\Attributes\Composite\SelectiveReader; +use Spiral\Attributes\Psr16CachedReader; use Spiral\Attributes\ReaderInterface; -use Spiral\Bootloader\Attributes\AttributesConfig; -use Spiral\Bootloader\Attributes\Factory; -use Spiral\Config\ConfiguratorInterface; +use Spiral\Testing\Attribute\Env; use Spiral\Tests\Framework\BaseTestCase; final class AttributesWithoutAnnotationsBootloaderTest extends BaseTestCase { - protected function setUp(): void + public const MAKE_APP_ON_STARTUP = false; + + #[Env('SUPPORT_ANNOTATIONS', 'false')] + public function testReaderBindingWithoutCache(): void { - $this->beforeInit(function (ConfiguratorInterface $configurator) { - $configurator->setDefaults(AttributesConfig::CONFIG, [ - 'annotations' => [ - 'support' => false, - ], - ]); - }); - - parent::setUp(); + $this + ->withDisabledBootloaders(\Spiral\Cache\Bootloader\CacheBootloader::class) + ->initApp($this->getTestEnvVariables()); + + $this->assertContainerBoundAsSingleton(ReaderInterface::class, AttributeReader::class); } - public function testReaderBinding(): void + #[Env('SUPPORT_ANNOTATIONS', 'false')] + public function testReaderBindingWithtCache(): void { + $this->initApp($this->getTestEnvVariables()); - $this->assertContainerBoundAsSingleton(ReaderInterface::class, AttributeReader::class); + $this->assertContainerBoundAsSingleton(ReaderInterface::class, Psr16CachedReader::class); + } + + #[Env('SUPPORT_ANNOTATIONS', 'true')] + public function testSelectiveReaderBinding(): void + { + $this->initApp($this->getTestEnvVariables()); + + $this->assertContainerBoundAsSingleton(ReaderInterface::class, SelectiveReader::class); } } diff --git a/tests/Framework/Bootloader/Tokenizer/TokenizerBootloaderTest.php b/tests/Framework/Bootloader/Tokenizer/TokenizerBootloaderTest.php index 99d6f46bb..21ea17a23 100644 --- a/tests/Framework/Bootloader/Tokenizer/TokenizerBootloaderTest.php +++ b/tests/Framework/Bootloader/Tokenizer/TokenizerBootloaderTest.php @@ -63,6 +63,10 @@ public function testConfig(): void 'tests', 'migrations', ], + 'cache' => [ + 'directory' => $this->getDirectoryByAlias('runtime') .'cache/listeners', + 'enabled' => true + ] ] ); } diff --git a/tests/Framework/Dispatcher/ConsoleDispatcherTest.php b/tests/Framework/Dispatcher/ConsoleDispatcherTest.php index 301de58ae..d83c51f3c 100644 --- a/tests/Framework/Dispatcher/ConsoleDispatcherTest.php +++ b/tests/Framework/Dispatcher/ConsoleDispatcherTest.php @@ -51,8 +51,9 @@ public function testException(): void ); $result = $output->fetch(); - $this->assertStringContainsString('undefined', $result); + $this->assertStringContainsString('This command is dead', $result); $this->assertStringContainsString('DeadCommand.php', $result); + $this->assertStringNotContainsString('throw new \InvalidArgumentException(\'This command is dead\');', $result); $this->assertNotEquals(0, $serveResult); } @@ -70,8 +71,9 @@ public function testExceptionVerbose(): void ); $result = $output->fetch(); - $this->assertStringContainsString('undefined', $result); + $this->assertStringContainsString('This command is dead', $result); $this->assertStringContainsString('DeadCommand.php', $result); + $this->assertStringNotContainsString('throw new \InvalidArgumentException(\'This command is dead\');', $result); $this->assertNotEquals(0, $serveResult); } @@ -90,9 +92,9 @@ public function testExceptionDebug(): void ); $result = $output->fetch(); - $this->assertStringContainsString('undefined', $result); + $this->assertStringContainsString('This command is dead', $result); $this->assertStringContainsString('DeadCommand.php', $result); - $this->assertStringContainsString('$undefined', $result); + $this->assertStringContainsString('throw new \InvalidArgumentException(\'This command is dead\');', $result); $this->assertNotEquals(0, $serveResult); } } diff --git a/tests/Framework/Http/ControllerTest.php b/tests/Framework/Http/ControllerTest.php index 8affb3842..e3ba2d24c 100644 --- a/tests/Framework/Http/ControllerTest.php +++ b/tests/Framework/Http/ControllerTest.php @@ -67,6 +67,7 @@ public function testPayloadActionBad(): void public function test500(): void { - $this->getHttp()->get('/error')->assertStatus(500); + $this->getHttp()->get('/error') + ->assertStatus(500); } } diff --git a/tests/Framework/Tokenizer/Config/ConfigTest.php b/tests/Framework/Tokenizer/Config/ConfigTest.php new file mode 100644 index 000000000..4fb9858b0 --- /dev/null +++ b/tests/Framework/Tokenizer/Config/ConfigTest.php @@ -0,0 +1,28 @@ +getContainer()->get(TokenizerConfig::class); + + $this->assertFalse($config->isCacheEnabled()); + } + + #[Env('TOKENIZER_CACHE_TARGETS', 'true')] + public function testCacheFromEnvEnabled(): void + { + $config = $this->getContainer()->get(TokenizerConfig::class); + + $this->assertTrue($config->isCacheEnabled()); + } +} diff --git a/tests/app/src/Command/DeadCommand.php b/tests/app/src/Command/DeadCommand.php index 4a72b0a89..0fc764b2a 100644 --- a/tests/app/src/Command/DeadCommand.php +++ b/tests/app/src/Command/DeadCommand.php @@ -4,14 +4,16 @@ namespace Spiral\App\Command; +use Spiral\Console\Attribute\AsCommand; use Spiral\Console\Command; +#[AsCommand(name: 'dead')] class DeadCommand extends Command { public const NAME = 'dead'; public function perform(): void { - echo $undefined; + throw new \InvalidArgumentException('This command is dead'); } } diff --git a/tests/app/src/Controller/TestController.php b/tests/app/src/Controller/TestController.php index 8fd52c676..3e2afb5bb 100644 --- a/tests/app/src/Controller/TestController.php +++ b/tests/app/src/Controller/TestController.php @@ -46,7 +46,7 @@ public function input(InputScope $i) public function error(): void { - echo $undefined; + throw new \InvalidArgumentException('Invalid argument'); } public function route(RouteInterface $route) diff --git a/tests/app/src/TestApp.php b/tests/app/src/TestApp.php index 5542e16b2..99575c3ce 100644 --- a/tests/app/src/TestApp.php +++ b/tests/app/src/TestApp.php @@ -16,6 +16,8 @@ class TestApp extends Kernel implements \Spiral\Testing\TestableKernelInterface { + private array $disabledBootloaders = []; + public const LOAD = [ TokenizerListenerBootloader::class, @@ -98,6 +100,16 @@ class TestApp extends Kernel implements \Spiral\Testing\TestableKernelInterface RoutesBootloader::class, ]; + protected function defineBootloaders(): array + { + $bootloaders = static::LOAD; + + // filter out disabled bootloaders + return \array_filter($bootloaders, function (string $bootloader): bool { + return !\in_array($bootloader, $this->disabledBootloaders, true); + }); + } + public function getContainer(): Container { return $this->container; @@ -112,4 +124,14 @@ public function getRegisteredBootloaders(): array { return $this->bootloader->getClasses(); } + + /** + * @param class-string<\Spiral\Boot\Bootloader\Bootloader> ...$bootloader + */ + public function disableBootloader(string ...$bootloader): self + { + $this->disabledBootloaders = \array_merge($this->disabledBootloaders, $bootloader); + + return $this; + } }