diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index c1939566..7001ac87 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -23,6 +23,31 @@ // Include Composer's autoloader. require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Note the stub prefixes registered just below are `OCA\OpenRegister\Db\` and +// `...\Service\` — neither covers `...\Contract\`, so once hydra-gates stops +// declaring it nothing else in this app resolves it. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // Register OCP/NCU classes from nextcloud/ocp package. // nextcloud/ocp has no autoload section in its composer.json, so we register it manually. spl_autoload_register(function (string $class): void { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4174546e..7783ea29 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,6 +23,32 @@ // Include Composer's autoloader require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Loaded here, immediately after the autoloader and before the OpenRegister +// stubs below, for the same reason those stubs are loaded early: what is +// declared first wins, and the contract must exist before anything implementing +// it is declared. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // OpenRegister test stubs. The real OCA\OpenRegister\Db\ObjectEntity has // __call magic getters that PHPUnit cannot configure on a mock, so the unit // tests use the explicit stub in tests/Stubs/. It is loaded HERE, BEFORE