From 5292202fbe94964706d16d24f23a3eb3c32cb5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 8 Sep 2026 14:20:19 +0200 Subject: [PATCH 1/3] Rebuild DatabaseSchemaHook on SchemaBuilderInterface instead of a raw-file reimplementation Previously imported schema.yaml files listed by SchemaFilesProviderInterface via LegacySchemaImporter -- a test-only mechanism that never actually dispatched SchemaBuilderEvent, so any package's own event subscriber (e.g. one deriving its schema from Doctrine ORM entity mappings instead of a schema.yaml file) was never exercised by integration tests at all. Now calls SchemaBuilderInterface::buildSchema() directly -- the same production code ibexa:install's legacy path uses -- and applies the resulting Schema's SQL directly, since the test database is always freshly created. No per-package schema file list needed. Only registered when DoctrineSchemaBundle is actually present (detected in IbexaTestCoreBundle::build(), which runs against the real, shared container -- unlike Extension::load(), which runs against a temporary per-extension copy MergeExtensionConfigurationPass uses to avoid cross-extension leakage, so sibling bundles' extensions never show up there). Not registered by IbexaTestKernel by default; a consuming package must register it itself to use this hook. --- .../IbexaTestCoreExtension.php | 10 ++++++ src/bundle/IbexaTestCoreBundle.php | 10 ++++++ src/bundle/Resources/config/services.php | 5 --- .../config/services/database_schema_hook.php | 20 ++++++++++++ .../Bootstrapper/DatabaseSchemaHook.php | 32 ++++++++++++------- 5 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 src/bundle/Resources/config/services/database_schema_hook.php diff --git a/src/bundle/DependencyInjection/IbexaTestCoreExtension.php b/src/bundle/DependencyInjection/IbexaTestCoreExtension.php index dd07942..1f4ec33 100644 --- a/src/bundle/DependencyInjection/IbexaTestCoreExtension.php +++ b/src/bundle/DependencyInjection/IbexaTestCoreExtension.php @@ -36,5 +36,15 @@ public function load(array $configs, ContainerBuilder $container): void new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.php'); + + // DatabaseSchemaHook autowires SchemaBuilderInterface, provided by DoctrineSchemaBundle — + // see IbexaTestCoreBundle::build(), where its presence is actually detected (that runs + // against the real, shared container; this method's own $container is a temporary, + // per-extension copy MergeExtensionConfigurationPass uses to avoid cross-extension + // leakage, so sibling bundles' extensions never show up in hasExtension() from here). + if ($container->hasParameter('ibexa_test_core.has_doctrine_schema_bundle') + && $container->getParameter('ibexa_test_core.has_doctrine_schema_bundle')) { + $loader->load('services/database_schema_hook.php'); + } } } diff --git a/src/bundle/IbexaTestCoreBundle.php b/src/bundle/IbexaTestCoreBundle.php index a0bbb49..b8ec2d9 100644 --- a/src/bundle/IbexaTestCoreBundle.php +++ b/src/bundle/IbexaTestCoreBundle.php @@ -18,5 +18,15 @@ final class IbexaTestCoreBundle extends Bundle public function build(ContainerBuilder $container): void { $container->addCompilerPass(new PersistenceCheckCompilerPass(), PassConfig::TYPE_AFTER_REMOVING); + + // DatabaseSchemaHook autowires SchemaBuilderInterface, provided by DoctrineSchemaBundle — + // only registered here (not IbexaTestCoreExtension::load()) since all bundles' extensions + // are registered on the real container before any bundle's build() runs, but each + // extension's own load() runs against a temporary, per-extension container copy where + // sibling extensions never show up in hasExtension(). + $container->setParameter( + 'ibexa_test_core.has_doctrine_schema_bundle', + $container->hasExtension('ibexa_doctrine_schema') + ); } } diff --git a/src/bundle/Resources/config/services.php b/src/bundle/Resources/config/services.php index 654a94a..025a66a 100644 --- a/src/bundle/Resources/config/services.php +++ b/src/bundle/Resources/config/services.php @@ -8,7 +8,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Ibexa\Contracts\Test\Core\Bootstrapper\DatabaseSchemaHook; use Ibexa\Contracts\Test\Core\Bootstrapper\DefaultFixtureProvider; use Ibexa\Contracts\Test\Core\Bootstrapper\DefaultSchemaFilesProvider; use Ibexa\Contracts\Test\Core\Bootstrapper\FixtureHook; @@ -78,10 +77,6 @@ $services->set(FixtureProviderChain::class) ->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) ->arg('$provider', service(FixtureProviderChain::class)) ->tag(HookInterface::TAG, ['priority' => FixtureHook::PRIORITY]); diff --git a/src/bundle/Resources/config/services/database_schema_hook.php b/src/bundle/Resources/config/services/database_schema_hook.php new file mode 100644 index 0000000..e33b5d5 --- /dev/null +++ b/src/bundle/Resources/config/services/database_schema_hook.php @@ -0,0 +1,20 @@ +services() + ->set(DatabaseSchemaHook::class) + ->autowire() + ->private() + ->tag(HookInterface::TAG, ['priority' => DatabaseSchemaHook::PRIORITY]); +}; diff --git a/src/contracts/Bootstrapper/DatabaseSchemaHook.php b/src/contracts/Bootstrapper/DatabaseSchemaHook.php index f2d728a..3699aa9 100644 --- a/src/contracts/Bootstrapper/DatabaseSchemaHook.php +++ b/src/contracts/Bootstrapper/DatabaseSchemaHook.php @@ -8,11 +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 by dispatching the same event-driven SchemaBuilderEvent + * `ibexa:install` itself uses for its legacy path — every registered package's own subscriber + * (e.g. a package's own BuildSchemaSubscriber) contributes its own tables via + * {@see SchemaBuilderInterface::buildSchema()} — then applies the resulting schema directly, since + * the test database is always freshly created (no existing schema to diff against). No + * per-package schema file list is needed, so {@see SchemaFilesProviderInterface} is unused by + * this hook. * * 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. @@ -27,16 +34,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 +57,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); } } } From cf1d81fb60d37cfd69f7e774183cd645276fbdb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 8 Sep 2026 20:23:05 +0200 Subject: [PATCH 2/3] Require IbexaRepositoryInstallerBundle too, and drop the now-unused schema-file provider chain Registering only DoctrineSchemaBundle isn't enough: core's own schema contribution comes from IbexaRepositoryInstallerBundle's BuildSchemaSubscriber, not IbexaCoreBundle, so without it the SchemaBuilderEvent produces every package's tables but none of core's. Verified against ibexa/cart, which failed on a missing ezcontentclass_attribute_ml until that bundle was added. SchemaFilesProviderInterface and its whole chain (SchemaFilesKernelMethodProvider, SchemaFilesParameterProvider, SchemaFilesProviderChain, and the ibexa.test.schema_files parameter) existed solely to feed DatabaseSchemaHook, which no longer reads them -- removed. DefaultSchemaFilesProvider stays: IbexaTestKernel::getSchemaFiles() is mandated by IbexaTestKernelInterface and consumed by ibexa/core's own IbexaKernelTestTrait. --- .../RemoveUnsatisfiableHooksPass.php | 51 +++++++++++ .../IbexaTestCoreExtension.php | 10 --- src/bundle/IbexaTestCoreBundle.php | 12 +-- src/bundle/Resources/config/services.php | 22 ++--- .../config/services/database_schema_hook.php | 20 ----- .../Bootstrapper/DatabaseSchemaHook.php | 15 ++-- .../SchemaFilesProviderInterface.php | 29 ------- .../SchemaFilesKernelMethodProvider.php | 35 -------- .../SchemaFilesParameterProvider.php | 36 -------- .../Bootstrapper/SchemaFilesProviderChain.php | 45 ---------- .../SchemaFilesProviderChainTest.php | 85 ------------------- 11 files changed, 64 insertions(+), 296 deletions(-) create mode 100644 src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php delete mode 100644 src/bundle/Resources/config/services/database_schema_hook.php delete mode 100644 src/contracts/Bootstrapper/SchemaFilesProviderInterface.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesParameterProvider.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesProviderChain.php delete mode 100644 tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php 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/DependencyInjection/IbexaTestCoreExtension.php b/src/bundle/DependencyInjection/IbexaTestCoreExtension.php index 1f4ec33..dd07942 100644 --- a/src/bundle/DependencyInjection/IbexaTestCoreExtension.php +++ b/src/bundle/DependencyInjection/IbexaTestCoreExtension.php @@ -36,15 +36,5 @@ public function load(array $configs, ContainerBuilder $container): void new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.php'); - - // DatabaseSchemaHook autowires SchemaBuilderInterface, provided by DoctrineSchemaBundle — - // see IbexaTestCoreBundle::build(), where its presence is actually detected (that runs - // against the real, shared container; this method's own $container is a temporary, - // per-extension copy MergeExtensionConfigurationPass uses to avoid cross-extension - // leakage, so sibling bundles' extensions never show up in hasExtension() from here). - if ($container->hasParameter('ibexa_test_core.has_doctrine_schema_bundle') - && $container->getParameter('ibexa_test_core.has_doctrine_schema_bundle')) { - $loader->load('services/database_schema_hook.php'); - } } } diff --git a/src/bundle/IbexaTestCoreBundle.php b/src/bundle/IbexaTestCoreBundle.php index b8ec2d9..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,15 +19,6 @@ final class IbexaTestCoreBundle extends Bundle public function build(ContainerBuilder $container): void { $container->addCompilerPass(new PersistenceCheckCompilerPass(), PassConfig::TYPE_AFTER_REMOVING); - - // DatabaseSchemaHook autowires SchemaBuilderInterface, provided by DoctrineSchemaBundle — - // only registered here (not IbexaTestCoreExtension::load()) since all bundles' extensions - // are registered on the real container before any bundle's build() runs, but each - // extension's own load() runs against a temporary, per-extension container copy where - // sibling extensions never show up in hasExtension(). - $container->setParameter( - 'ibexa_test_core.has_doctrine_schema_bundle', - $container->hasExtension('ibexa_doctrine_schema') - ); + $container->addCompilerPass(new RemoveUnsatisfiableHooksPass()); } } diff --git a/src/bundle/Resources/config/services.php b/src/bundle/Resources/config/services.php index 025a66a..26607ed 100644 --- a/src/bundle/Resources/config/services.php +++ b/src/bundle/Resources/config/services.php @@ -8,6 +8,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Ibexa\Contracts\Test\Core\Bootstrapper\DatabaseSchemaHook; use Ibexa\Contracts\Test\Core\Bootstrapper\DefaultFixtureProvider; use Ibexa\Contracts\Test\Core\Bootstrapper\DefaultSchemaFilesProvider; use Ibexa\Contracts\Test\Core\Bootstrapper\FixtureHook; @@ -16,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(); @@ -55,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]); @@ -77,6 +62,9 @@ $services->set(FixtureProviderChain::class) ->arg('$providers', tagged_iterator(FixtureProviderInterface::TAG)); + $services->set(DatabaseSchemaHook::class) + ->tag(HookInterface::TAG, ['priority' => DatabaseSchemaHook::PRIORITY]); + $services->set(FixtureHook::class) ->arg('$provider', service(FixtureProviderChain::class)) ->tag(HookInterface::TAG, ['priority' => FixtureHook::PRIORITY]); diff --git a/src/bundle/Resources/config/services/database_schema_hook.php b/src/bundle/Resources/config/services/database_schema_hook.php deleted file mode 100644 index e33b5d5..0000000 --- a/src/bundle/Resources/config/services/database_schema_hook.php +++ /dev/null @@ -1,20 +0,0 @@ -services() - ->set(DatabaseSchemaHook::class) - ->autowire() - ->private() - ->tag(HookInterface::TAG, ['priority' => DatabaseSchemaHook::PRIORITY]); -}; diff --git a/src/contracts/Bootstrapper/DatabaseSchemaHook.php b/src/contracts/Bootstrapper/DatabaseSchemaHook.php index 3699aa9..94097f3 100644 --- a/src/contracts/Bootstrapper/DatabaseSchemaHook.php +++ b/src/contracts/Bootstrapper/DatabaseSchemaHook.php @@ -13,16 +13,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Installs the database schema by dispatching the same event-driven SchemaBuilderEvent - * `ibexa:install` itself uses for its legacy path — every registered package's own subscriber - * (e.g. a package's own BuildSchemaSubscriber) contributes its own tables via - * {@see SchemaBuilderInterface::buildSchema()} — then applies the resulting schema directly, since - * the test database is always freshly created (no existing schema to diff against). No - * per-package schema file list is needed, so {@see SchemaFilesProviderInterface} is unused by - * this hook. + * 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 { 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/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; - } - }; - } -} From 8601c8acbff30a5ba5ceb363582c42523df5ec1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 9 Sep 2026 14:49:42 +0200 Subject: [PATCH 3/3] Registered the two SchemaBuilderEvent bundles in IbexaTestKernel Every kernel extending IbexaTestKernel needs DoctrineSchemaBundle (for SchemaBuilderInterface) and IbexaRepositoryInstallerBundle (for core's own BuildSchemaSubscriber) once DatabaseSchemaHook builds the schema from the event, so registering them here rather than in each of the ~50 consuming packages keeps this from being a breaking change for all of them. Both live in packages test-core already requires: doctrine-schema, and core itself, which is where IbexaRepositoryInstallerBundle lives. They are registered as a pair because IbexaRepositoryInstallerBundle::build() throws without DoctrineSchemaBundle. --- src/contracts/IbexaTestKernel.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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();