Skip to content

PoC: Hook + Bootstrapper for shared integration test setup - #38

Draft
Steveb-p wants to merge 12 commits into
4.6from
kernel-test-bootstrap-v2
Draft

PoC: Hook + Bootstrapper for shared integration test setup#38
Steveb-p wants to merge 12 commits into
4.6from
kernel-test-bootstrap-v2

Conversation

@Steveb-p

Copy link
Copy Markdown
Collaborator
🎫 Issue N/A

Description:

Supersedes #30 — same goal (kill the copy-pasted bootstrap logic across packages), cleaner split this time.

Bootstrapper now only does infra: resolve/boot the kernel, create the DB, optionally run doctrine:schema:update. Everything else — schema import, fixture import, search index purge — moved into HookInterface implementations tagged ibexa.test.bootstrapper.hook, run by HooksExecutor in priority order. SchemaHook/FixtureHook were actually empty stubs before (the tagging was wired up but nothing implemented them yet); they're real now, each gated by its own option key (schema, fixtures) read off the options array Bootstrapper forwards untouched. Added PurgeIndexHook (purge_index, default false) to finish that bit instead of leaving it as dead inline code.

The point of tagging instead of hardcoding: a downstream bundle can contribute its own hook without touching this package at all. ibexa/migrations does exactly that in its own PR — registers a MigrationHook from its own test kernel, and it just gets picked up by the same tagged_iterator.

Steveb-p added 3 commits May 13, 2026 12:55
Wires SchemaHook, FixtureHook, and the new PurgeIndexHook into the
HooksExecutor via tagged services, and simplifies Bootstrapper to just
resolve/boot the kernel, prep the database, and delegate everything else
to the hook pipeline. Each hook reads its own option key from the
bootstrap options array, so a downstream bundle (e.g. ibexa/migrations)
can contribute its own hook without any changes needed here.
@Steveb-p

Copy link
Copy Markdown
Collaborator Author

Two companion PRs depend on this branch via a temporary dependencies.json (removed once this merges):

Migrations is not a concept ibexa/test-core knows about - this method
only existed as a leftover from the pre-Hook PoC, where Bootstrapper
called it directly. Its only caller is now ibexa/migrations' own
MigrationHook, so it belongs on that package's own test kernel instead
(see companion commit in ibexa/migrations).
It was only there to resolve the (now fully-removed) migration-specific
class references that used to live inline in Bootstrapper.php. Nothing
in this package depends on a proprietary Ibexa package anymore.
EXPOSED_SERVICES_BY_CLASS/_BY_ID picked up an `array` type hint in the
original PoC commit, even though this package's composer.json still
declares php ^7.4 || ^8.0 support. Typed class constants only exist
since PHP 8.3.
GlobFileLoader only tracks matched files as a cache-invalidation resource;
it needs a configured LoaderResolver to actually parse them, which this
extension never set up. It silently never loaded services.php, meaning
none of the Hook/Bootstrapper services (added when services.yaml became
services.php) were ever registered in any consuming kernel - caught by
actually running the two showcase suites end to end.
Without it, this bundle's extension (and therefore every Hook service)
never made it into any consuming kernel's container - the underlying
reason the previous commit's loader bug went unnoticed for so long.
Simplified TestKernel accordingly: it was manually re-registering this
bundle as a workaround for the exact gap now fixed at the source.
HooksExecutor and its interface alias were private and referenced by
nothing inside the container graph (Bootstrapper fetches them from
outside, via the test service container) - Symfony's compiler pruned
them as unused before framework.test's public-everything mechanism
ever got a chance to preserve them. Marked both public explicitly.

Separately, SchemaHook/FixtureHook typed their $kernel argument against
the concrete Ibexa\Contracts\Test\Core\IbexaTestKernel class, which does
not autowire: Symfony only resolves the synthetic "kernel" service by
the interfaces it implements (KernelInterface, HttpKernelInterface, or -
as used here - IbexaTestKernelInterface), never by an intermediate parent
class. Retyped against IbexaTestKernelInterface and wired $kernel
explicitly via service('kernel') to sidestep the ambiguity entirely.
This file was copied from ibexa/core's own Legacy fixture data in 2023
and never touched since; that original copy (and every migrations
snapshot test calibrated against it) uses lowercase "Anonymous users".
Surfaced by actually running ibexa/migrations' full suite against this
package's shared kernel.
Each HookInterface implementation now declares its options through a
configureOptions(OptionsResolver $resolver) method (mirroring
Symfony\Component\Form\FormTypeInterface), so an unknown key or wrong
type throws instead of being silently ignored. The top-level options
array passed to Bootstrapper/HooksExecutor is keyed by each hook's own
service id (not its FQCN), so two differently-configured instances of
the same hook class wouldn't collide on one options slot.

HooksExecutor is wired via tagged_iterator() with an explicit index
attribute (falling back to service id, since no hook sets that
attribute) rather than tagged_locator(): Symfony's ServiceLocatorTagPass
ksort()s its service map before building the locator, which silently
broke the tag-priority execution order hooks rely on (schema before
fixtures). Verified against Symfony's own DI source before landing on
this, and confirmed the correct order empirically.

Added symfony/options-resolver as an explicit require (was only ever
resolved transitively before).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant