diff --git a/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php new file mode 100644 index 0000000..e76fd70 --- /dev/null +++ b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php @@ -0,0 +1,51 @@ +> hook service id => service ids it cannot work without + */ + private const HOOK_REQUIREMENTS = [ + DatabaseSchemaHook::class => [ + SchemaBuilderInterface::class, + // core's own schema contribution; without it the event yields every other package's + // tables but none of core's + BuildSchemaSubscriber::class, + ], + ]; + + public function process(ContainerBuilder $container): void + { + foreach (self::HOOK_REQUIREMENTS as $hookId => $requiredServiceIds) { + if (!$container->hasDefinition($hookId)) { + continue; + } + + foreach ($requiredServiceIds as $serviceId) { + if (!$container->has($serviceId)) { + $container->removeDefinition($hookId); + + break; + } + } + } + } +} diff --git a/src/bundle/IbexaTestCoreBundle.php b/src/bundle/IbexaTestCoreBundle.php index a0bbb49..435b6d4 100644 --- a/src/bundle/IbexaTestCoreBundle.php +++ b/src/bundle/IbexaTestCoreBundle.php @@ -9,6 +9,7 @@ namespace Ibexa\Bundle\Test\Core; use Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\PersistenceCheckCompilerPass; +use Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\RemoveUnsatisfiableHooksPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -18,5 +19,6 @@ final class IbexaTestCoreBundle extends Bundle public function build(ContainerBuilder $container): void { $container->addCompilerPass(new PersistenceCheckCompilerPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RemoveUnsatisfiableHooksPass()); } } diff --git a/src/bundle/Resources/config/services.php b/src/bundle/Resources/config/services.php index 654a94a..26607ed 100644 --- a/src/bundle/Resources/config/services.php +++ b/src/bundle/Resources/config/services.php @@ -17,21 +17,16 @@ use Ibexa\Contracts\Test\Core\Bootstrapper\HooksExecutorInterface; use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeIndexAfterFixturesHook; use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeSearchIndexHook; -use Ibexa\Contracts\Test\Core\Bootstrapper\SchemaFilesProviderInterface; use Ibexa\Test\Core\Bootstrapper\FixtureKernelMethodProvider; use Ibexa\Test\Core\Bootstrapper\FixtureParameterProvider; use Ibexa\Test\Core\Bootstrapper\FixtureProviderChain; use Ibexa\Test\Core\Bootstrapper\HooksExecutor; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesKernelMethodProvider; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesParameterProvider; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesProviderChain; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->parameters() // null (not []): an unset/unconfigured parameter must be distinguishable from a consumer // deliberately configuring an empty list, since only the latter should stop the fallback - // chain in *ProviderChain — see SchemaFilesProviderInterface's docblock. - ->set('ibexa.test.schema_files', null) + // chain in FixtureProviderChain — see FixtureProviderInterface's docblock. ->set('ibexa.test.fixture_files', null); $services = $containerConfigurator->services(); @@ -56,17 +51,6 @@ $services->set(DefaultFixtureProvider::class); - $services->set(SchemaFilesKernelMethodProvider::class) - ->arg('$kernel', service('kernel')) - ->tag(SchemaFilesProviderInterface::TAG, ['priority' => 0]); - - $services->set(SchemaFilesParameterProvider::class) - ->arg('$schemaFiles', '%ibexa.test.schema_files%') - ->tag(SchemaFilesProviderInterface::TAG, ['priority' => 100]); - - $services->set(SchemaFilesProviderChain::class) - ->arg('$providers', tagged_iterator(SchemaFilesProviderInterface::TAG)); - $services->set(FixtureKernelMethodProvider::class) ->arg('$kernel', service('kernel')) ->tag(FixtureProviderInterface::TAG, ['priority' => 0]); @@ -79,7 +63,6 @@ ->arg('$providers', tagged_iterator(FixtureProviderInterface::TAG)); $services->set(DatabaseSchemaHook::class) - ->arg('$provider', service(SchemaFilesProviderChain::class)) ->tag(HookInterface::TAG, ['priority' => DatabaseSchemaHook::PRIORITY]); $services->set(FixtureHook::class) diff --git a/src/contracts/Bootstrapper/DatabaseSchemaHook.php b/src/contracts/Bootstrapper/DatabaseSchemaHook.php index f2d728a..94097f3 100644 --- a/src/contracts/Bootstrapper/DatabaseSchemaHook.php +++ b/src/contracts/Bootstrapper/DatabaseSchemaHook.php @@ -8,14 +8,18 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Tests\Core\Repository\LegacySchemaImporter; +use Doctrine\DBAL\Connection; +use Ibexa\Contracts\DoctrineSchema\Builder\SchemaBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Imports the legacy raw-SQL schema files exposed by {@see SchemaFilesProviderInterface}. + * Installs the database schema built by {@see SchemaBuilderInterface::buildSchema()}, i.e. from + * every registered bundle's own SchemaBuilderEvent subscriber. Requires `DoctrineSchemaBundle` and + * `IbexaRepositoryInstallerBundle`; removed from the container when either is missing, by + * {@see \Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\RemoveUnsatisfiableHooksPass}. * - * Enabled by default; pass `[self::OPTION_LOAD_SCHEMA => false]` as this hook's own options (keyed - * by its own service id in the bootstrap options array) to skip it. + * Enabled by default; pass `[self::OPTION_LOAD_SCHEMA => false]` as this hook's own options to + * skip it. */ final class DatabaseSchemaHook implements HookInterface { @@ -27,16 +31,14 @@ final class DatabaseSchemaHook implements HookInterface public const OPTION_LOAD_SCHEMA = 'load_schema'; - private SchemaFilesProviderInterface $provider; + private SchemaBuilderInterface $schemaBuilder; - private LegacySchemaImporter $schemaImporter; + private Connection $connection; - public function __construct( - SchemaFilesProviderInterface $provider, - LegacySchemaImporter $schemaImporter - ) { - $this->provider = $provider; - $this->schemaImporter = $schemaImporter; + public function __construct(SchemaBuilderInterface $schemaBuilder, Connection $connection) + { + $this->schemaBuilder = $schemaBuilder; + $this->connection = $connection; } public function configureOptions(OptionsResolver $resolver): void @@ -52,8 +54,11 @@ public function __invoke(array $options): void return; } - foreach ($this->provider->getSchemaFiles() ?? [] as $file) { - $this->schemaImporter->importSchema($file); + $schema = $this->schemaBuilder->buildSchema(); + $platform = $this->connection->getDatabasePlatform(); + + foreach ($schema->toSql($platform) as $sql) { + $this->connection->executeStatement($sql); } } } diff --git a/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php b/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php deleted file mode 100644 index 7f7a6f7..0000000 --- a/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -|null null if this provider has nothing to contribute — the caller - * should fall back to another provider, not treat this the same - * as a deliberately empty list - */ - public function getSchemaFiles(): ?iterable; -} diff --git a/src/contracts/IbexaTestKernel.php b/src/contracts/IbexaTestKernel.php index bc91560..603645e 100644 --- a/src/contracts/IbexaTestKernel.php +++ b/src/contracts/IbexaTestKernel.php @@ -12,7 +12,9 @@ use Doctrine\DBAL\Connection; use FOS\JsRoutingBundle\FOSJsRoutingBundle; use Ibexa\Bundle\Core\IbexaCoreBundle; +use Ibexa\Bundle\DoctrineSchema\DoctrineSchemaBundle; use Ibexa\Bundle\LegacySearchEngine\IbexaLegacySearchEngineBundle; +use Ibexa\Bundle\RepositoryInstaller\IbexaRepositoryInstallerBundle; use Ibexa\Contracts\Core\Persistence\TransactionHandler; use Ibexa\Contracts\Core\Repository; use Ibexa\Contracts\Core\Test\IbexaTestKernelInterface; @@ -61,6 +63,15 @@ * {@see \Ibexa\Contracts\Test\Core\Bootstrapper\HooksExecutorInterface} and the built-in hooks it * registers) must `yield new IbexaTestCoreBundle();` from its own registerBundles() override. * + * It does register {@see \Ibexa\Bundle\DoctrineSchema\DoctrineSchemaBundle} and + * {@see \Ibexa\Bundle\RepositoryInstaller\IbexaRepositoryInstallerBundle}, which together make + * SchemaBuilderEvent usable: the former provides + * {@see \Ibexa\Contracts\DoctrineSchema\Builder\SchemaBuilderInterface}, the latter carries core's + * own BuildSchemaSubscriber (it lives there rather than in IbexaCoreBundle), without which the + * event yields every other package's tables but none of core's. They are a pair — + * IbexaRepositoryInstallerBundle::build() throws if DoctrineSchemaBundle is absent. A subclass must + * therefore NOT yield either of them again; Symfony rejects two bundles with the same name. + * * ## Exposing your services * * To add services to the test Kernel and make them available in tests via IbexaKernelTestCase::getServiceByClassName(), @@ -144,6 +155,8 @@ public function registerBundles(): iterable { yield new SecurityBundle(); yield new IbexaCoreBundle(); + yield new DoctrineSchemaBundle(); + yield new IbexaRepositoryInstallerBundle(); yield new IbexaLegacySearchEngineBundle(); yield new JMSTranslationBundle(); yield new FOSJsRoutingBundle(); diff --git a/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php b/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php deleted file mode 100644 index 252b25c..0000000 --- a/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php +++ /dev/null @@ -1,35 +0,0 @@ -kernel = $kernel; - } - - public function getSchemaFiles(): ?iterable - { - if (!$this->kernel instanceof IbexaTestKernelInterface) { - return null; - } - - return $this->kernel->getSchemaFiles(); - } -} diff --git a/src/lib/Bootstrapper/SchemaFilesParameterProvider.php b/src/lib/Bootstrapper/SchemaFilesParameterProvider.php deleted file mode 100644 index 172dcef..0000000 --- a/src/lib/Bootstrapper/SchemaFilesParameterProvider.php +++ /dev/null @@ -1,36 +0,0 @@ -|null - */ - private ?iterable $schemaFiles; - - /** - * @param iterable|null $schemaFiles - */ - public function __construct(?iterable $schemaFiles) - { - $this->schemaFiles = $schemaFiles; - } - - public function getSchemaFiles(): ?iterable - { - return $this->schemaFiles; - } -} diff --git a/src/lib/Bootstrapper/SchemaFilesProviderChain.php b/src/lib/Bootstrapper/SchemaFilesProviderChain.php deleted file mode 100644 index e187cbd..0000000 --- a/src/lib/Bootstrapper/SchemaFilesProviderChain.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - private iterable $providers; - - /** - * @param iterable $providers priority-sorted - */ - public function __construct(iterable $providers) - { - $this->providers = $providers; - } - - public function getSchemaFiles(): ?iterable - { - foreach ($this->providers as $provider) { - $schemaFiles = $provider->getSchemaFiles(); - if ($schemaFiles !== null) { - return $schemaFiles; - } - } - - return null; - } -} diff --git a/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php b/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php deleted file mode 100644 index f94fb3f..0000000 --- a/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php +++ /dev/null @@ -1,85 +0,0 @@ -providerReturning(null), - $this->providerReturning(['a.sql']), - ]); - - self::assertSame(['a.sql'], $chain->getSchemaFiles()); - } - - public function testStopsAtFirstNonNullResultEvenIfEmpty(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning([]), - $this->providerReturning(['never-reached.sql']), - ]); - - self::assertSame([], $chain->getSchemaFiles()); - } - - public function testTriesProvidersInTheGivenOrder(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning(['first.sql']), - $this->providerReturning(['second.sql']), - ]); - - self::assertSame(['first.sql'], $chain->getSchemaFiles()); - } - - public function testReturnsNullWhenNoProviderHasAnAnswer(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning(null), - $this->providerReturning(null), - ]); - - self::assertNull($chain->getSchemaFiles()); - } - - /** - * @param iterable|null $schemaFiles - */ - private function providerReturning(?iterable $schemaFiles): SchemaFilesProviderInterface - { - return new class($schemaFiles) implements SchemaFilesProviderInterface { - /** - * @var iterable|null - */ - private ?iterable $schemaFiles; - - /** - * @param iterable|null $schemaFiles - */ - public function __construct(?iterable $schemaFiles) - { - $this->schemaFiles = $schemaFiles; - } - - public function getSchemaFiles(): ?iterable - { - return $this->schemaFiles; - } - }; - } -}