From 517f65078de2312441f6d4bfbe3092f21faff830 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 14 Aug 2026 20:59:25 +0200 Subject: [PATCH 01/14] refactor(deps): inject OpenRegister instead of looking it up (ADR-083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 file(s) reached OpenRegister through $this->container->get(...) on an UNCONDITIONAL path — no availability check, no degrading catch. The dependency was announced nowhere: not in the constructor, not in the use block, not in any type. It appeared mid-method, as a string. Now constructor-injected and typed, so the dependency is visible to a reader and to tooling. Behaviour is unchanged: the same object, from the same container, resolved at construction instead of at first use. ContainerInterface is dropped only where nothing else used it. Deliberately NOT converted, because they are correct as written (ADR-083 rule 1's exception): lookups behind isInstalled()/getInstalledApps(), and lookups whose catch degrades rather than rethrows. Verified per file: php -l clean, and gate-66's lookup check reports zero remaining findings for each file changed. gate-66 for this app: 23 -> 8. --- lib/Controller/ContactpersonenController.php | 33 +++---- .../OrganisationMembersController.php | 6 +- .../UserProfileUpdatedEventListener.php | 28 +++--- lib/Service/GebruikSyncService.php | 9 +- lib/Service/OrganizationSyncService.php | 91 ++++++++----------- 5 files changed, 73 insertions(+), 94 deletions(-) diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index babdef7b..1d171984 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -35,6 +35,9 @@ use OCP\Security\ISecureRandom; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Db\MagicMapper; +use OCA\OpenRegister\Service\OrganisationService; /** * Controller for managing contactpersonen and their user accounts. @@ -152,6 +155,9 @@ public function __construct( ContainerInterface $container, ISecureRandom $secureRandom, LoggerInterface $logger, + private readonly ObjectService $objectService, + private readonly MagicMapper $magicMapper, + private readonly OrganisationService $organisationService, ) { parent::__construct(appName: $appName, request: $request); $this->settingsService = $settingsService; @@ -201,7 +207,6 @@ public function getContactpersonen(string $organisationId): JSONResponse { try { // Get object service. - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); // Search for contactpersonen belonging to this organisation. // Use a more generic search that doesn't require specific register/schema. @@ -212,7 +217,7 @@ public function getContactpersonen(string $organisationId): JSONResponse { // Let ObjectService resolve the schema. ]; - $contactpersonen = $objectService->searchObjectsPaginated($searchParams); + $contactpersonen = $this->objectService->searchObjectsPaginated($searchParams); // Enhance with user information. // @@ -308,7 +313,6 @@ private function checkOrganisationReadPermission(\OCP\IUser $currentUser, string $callerOrgUuid = null; try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); $callerOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $currentUser->getUID()); } catch (\Exception $e) { $this->logger->warning( @@ -404,10 +408,9 @@ public function convertToUser(string $contactPersonId): JSONResponse { try { // Get object service. - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); // Find the contactpersoon object — bind to current tenant. - $contactPersonObject = $objectService->find( + $contactPersonObject = $this->objectService->find( id: $contactPersonId, register: 'voorzieningen', schema: 'contactpersoon', @@ -533,8 +536,7 @@ public function convertToUser(string $contactPersonId): JSONResponse { // Save using MagicMapper directly to bypass schema validation. // This avoids "Unresolved reference" errors when schema references can't be resolved. - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactPersonObject); + $this->magicMapper->update($contactPersonObject); $this->logger->info( 'ContactpersonenController: Updated contactpersoon with username', @@ -942,8 +944,6 @@ private function checkGroupUpdatePermission(\OCP\IUser $currentUser, string $use */ private function verifyCrossTenantScope(\OCP\IUser $currentUser, string $username): ?JSONResponse { try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $targetOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $username); $callerOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $currentUser->getUID()); @@ -988,7 +988,7 @@ private function verifyCrossTenantScope(\OCP\IUser $currentUser, string $usernam * @spec openspec/changes/method-decomposition/tasks.md#task-5 */ private function resolveContactOrganisation(object $objectService, string $username): ?string { - $results = $objectService->searchObjectsPaginated( + $results = $this->objectService->searchObjectsPaginated( ['username' => $username, '_limit' => 1, '_schema' => 'contactpersoon'] ); @@ -1203,8 +1203,7 @@ public function getUserInfo(string $contactPersonId): JSONResponse { } try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $contactObject = $objectService->find( + $contactObject = $this->objectService->find( id: $contactPersonId, register: 'voorzieningen', schema: 'contactpersoon' @@ -1597,10 +1596,8 @@ public function getMe(): JSONResponse { // Get organisation data from OpenRegister. try { - $organisationService = $this->container->get('OCA\OpenRegister\Service\OrganisationService'); - // Get active organisation. - $activeOrg = $organisationService->getActiveOrganisation(); + $activeOrg = $this->organisationService->getActiveOrganisation(); if ($activeOrg !== null) { $response['organisations']['active'] = [ 'uuid' => $activeOrg->getUuid(), @@ -1611,7 +1608,7 @@ public function getMe(): JSONResponse { } // Get all user organisations. - $userOrgs = $organisationService->getUserOrganisations(); + $userOrgs = $this->organisationService->getUserOrganisations(); foreach ($userOrgs as $org) { $response['organisations']['all'][] = [ 'uuid' => $org->getUuid(), @@ -1728,15 +1725,13 @@ private function enrichMeWithContactPersonData( string $userEmail, ): void { try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $searchParams = [ 'username' => $userId, '_limit' => 1, '_schema' => 'contactpersoon', ]; - $contactpersonen = $objectService->searchObjectsPaginated($searchParams); + $contactpersonen = $this->objectService->searchObjectsPaginated($searchParams); if (empty($contactpersonen['results']) === false) { $contactPerson = $contactpersonen['results'][0]; diff --git a/lib/Controller/OrganisationMembersController.php b/lib/Controller/OrganisationMembersController.php index 92ebd0aa..3cfa94d3 100644 --- a/lib/Controller/OrganisationMembersController.php +++ b/lib/Controller/OrganisationMembersController.php @@ -52,8 +52,8 @@ use OCP\IRequest; use OCP\IUserManager; use OCP\IUserSession; -use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use OCA\OpenRegister\Service\OrganisationService; /** * Beheerder-gated grant/revoke of organisation membership for an existing @@ -87,8 +87,8 @@ public function __construct( private readonly IUserSession $userSession, private readonly IGroupManager $groupManager, private readonly IUserManager $userManager, - private readonly ContainerInterface $container, private readonly LoggerInterface $logger, + private readonly OrganisationService $organisationService, ) { parent::__construct(appName: Application::APP_ID, request: $request); }//end __construct() @@ -266,6 +266,6 @@ private function authorizeMaintainer(string $organisationUuid): ?JSONResponse { * @throws \Throwable When OpenRegister is unavailable. */ private function getOrganisationService(): \OCA\OpenRegister\Service\OrganisationService { - return $this->container->get('OCA\OpenRegister\Service\OrganisationService'); + return $this->organisationService; }//end getOrganisationService() }//end class diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index 54ecc1c7..e95d9b3c 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -25,6 +25,11 @@ use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Db\SchemaMapper; +use OCA\OpenRegister\Db\RegisterMapper; +use OCA\OpenRegister\Service\Object\SaveObject\MetadataHydrationHandler; +use OCA\OpenRegister\Db\MagicMapper; /** * Syncs user profile changes to the corresponding contactpersoon object. @@ -56,6 +61,11 @@ class UserProfileUpdatedEventListener implements IEventListener { */ public function __construct( private readonly ContainerInterface $container, + private readonly ObjectService $objectService, + private readonly SchemaMapper $schemaMapper, + private readonly RegisterMapper $registerMapper, + private readonly MetadataHydrationHandler $metadataHydrationHandler, + private readonly MagicMapper $magicMapper, ) { }//end __construct() @@ -128,7 +138,6 @@ public function handle(Event $event): void { * @spec openspec/specs/method-decomposition/spec.md */ private function syncToContactPerson(UserProfileUpdatedEvent $event, LoggerInterface $logger): void { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); $settingsService = $this->container->get(SettingsService::class); $voorzieningenConfig = $settingsService->getVoorzieningenConfig(); @@ -283,15 +292,11 @@ private function persistContactPersonPatch( int $schema, LoggerInterface $logger, ): void { - $schemaMapper = $this->container->get('OCA\OpenRegister\Db\SchemaMapper'); - $registerMapper = $this->container->get('OCA\OpenRegister\Db\RegisterMapper'); - $metaHydrationHandler = $this->container->get('OCA\OpenRegister\Service\Object\SaveObject\MetadataHydrationHandler'); - $schemaEntity = null; $registerEntity = null; try { - $schemaEntity = $schemaMapper->find(id: $schema, _rbac: false, _multitenancy: false); - $registerEntity = $registerMapper->find(id: $register, _rbac: false, _multitenancy: false); + $schemaEntity = $this->schemaMapper->find(id: $schema, _rbac: false, _multitenancy: false); + $registerEntity = $this->registerMapper->find(id: $register, _rbac: false, _multitenancy: false); } catch (\Exception $e) { $logger->warning( '[UserProfileUpdatedEventListener] Could not load schema/register entities for _name hydration', @@ -302,7 +307,7 @@ private function persistContactPersonPatch( } if ($schemaEntity !== null) { - $metaHydrationHandler->hydrateObjectMetadata(entity: $contactPerson, schema: $schemaEntity); + $this->metadataHydrationHandler->hydrateObjectMetadata(entity: $contactPerson, schema: $schemaEntity); $logger->debug( '[UserProfileUpdatedEventListener] Regenerated _name metadata', [ @@ -313,8 +318,7 @@ private function persistContactPersonPatch( // Pass register and schema so the magic mapper route is triggered and the // per-schema magic table is updated (not just the blob table). - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update(entity: $contactPerson, register: $registerEntity, schema: $schemaEntity); + $this->magicMapper->update(entity: $contactPerson, register: $registerEntity, schema: $schemaEntity); }//end persistContactpersoonPatch() @@ -341,7 +345,7 @@ private function findContactPerson( // 1. Search by username = userId, scoped to the user's organisation (multitenancy). // This prevents updating a contactpersoon from a different organisation when. // Multiple records share the same username across orgs. - $results = $objectService->searchObjects( + $results = $this->objectService->searchObjects( query: ['@self' => $selfQuery, 'username' => $userId, '_limit' => 5], _rbac: false, _multitenancy: true @@ -381,7 +385,7 @@ private function findContactPerson( // Use _search for case-insensitive matching, then verify the email field in PHP. // Scoped to user's organisation via multitenancy to avoid cross-org matches. - $results = $objectService->searchObjects( + $results = $this->objectService->searchObjects( query: ['@self' => $selfQuery, '_search' => $emailCandidate, '_limit' => 5], _rbac: false, _multitenancy: true diff --git a/lib/Service/GebruikSyncService.php b/lib/Service/GebruikSyncService.php index 36182dd3..8d096347 100644 --- a/lib/Service/GebruikSyncService.php +++ b/lib/Service/GebruikSyncService.php @@ -29,6 +29,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use OCA\OpenRegister\Service\ObjectService; /** * Service for synchronizing and processing Gebruik (Usage) objects. @@ -86,6 +87,7 @@ public function __construct( LoggerInterface $logger, SettingsService $settingsService, ContainerInterface $container, + private readonly ObjectService $objectService, ) { $this->logger = $logger; $this->settingsService = $settingsService; @@ -324,7 +326,6 @@ private function processAmefElements(ObjectEntity $gebruikObject): array { * @return array Array of found ObjectEntity objects. */ private function searchAmefElementsByIds(array $ids, string $register, string $schema): array { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); $foundElements = []; foreach ($ids as $id) { @@ -340,7 +341,7 @@ private function searchAmefElementsByIds(array $ids, string $register, string $s '_limit' => 5, ]; - $elements = $objectService->searchObjects($query); + $elements = $this->objectService->searchObjects($query); $foundElements = array_merge($foundElements, $elements); } catch (Exception $e) { $this->logger->warning( @@ -515,8 +516,6 @@ private function resolveLatestEligibleStatus(array $statusDates, string $gebruik */ private function updateGebruikObject(ObjectEntity $gebruikObject, array $updatedData): void { try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - // Get voorzieningenConfig to find the correct register and schema. $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); $register = $voorzieningenConfig['register'] ?? ''; @@ -527,7 +526,7 @@ private function updateGebruikObject(ObjectEntity $gebruikObject, array $updated } // Update the object. - $objectService->saveObject( + $this->objectService->saveObject( object: $updatedData, register: (int)$register, schema: (int)$gebruikSchema, diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 287341c1..b87d0b87 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -26,6 +26,8 @@ use OCP\IDBConnection; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use OCA\OpenRegister\Db\OrganisationMapper; +use OCA\OpenRegister\Db\MagicMapper; /** * Service for synchronizing organizations and contact persons. @@ -130,6 +132,9 @@ public function __construct( private IDBConnection $db, private readonly ContactPersonHandler $contactpersonHandler, ContainerInterface $container, + private readonly ObjectService $objectService, + private readonly OrganisationMapper $organisationMapper, + private readonly MagicMapper $magicMapper, ) { $this->organisationService = $organisationService; $this->contactPersonService = $contactPersonService; @@ -380,7 +385,6 @@ public function performOrganizationsSync(int $batchSize = 50, int $maxExecutionS $rows = $qb->executeQuery()->fetchAll(); - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); if ($objectService instanceof ObjectService === false) { $this->logger->error('OrganizationSync: could not resolve ObjectService'); return $stats; @@ -400,7 +404,7 @@ public function performOrganizationsSync(int $batchSize = 50, int $maxExecutionS } try { - $object = $objectService->find( + $object = $this->objectService->find( id: $row['uuid'], register: $register, schema: $organizationSchema, @@ -505,7 +509,6 @@ public function performContactSync(int $batchSize = 100, int $maxExecutionSecond $this->logger->info('ContactSync: processing ' . count($contacts) . ' contacts with existing NC accounts'); - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); if ($objectService instanceof ObjectService === false) { $this->logger->error('ContactSync: could not resolve ObjectService'); return $stats; @@ -524,7 +527,7 @@ public function performContactSync(int $batchSize = 100, int $maxExecutionSecond } try { - $contactEntity = $objectService->find( + $contactEntity = $this->objectService->find( id: $contact['uuid'], register: $register, schema: $contactSchema, @@ -537,7 +540,7 @@ public function performContactSync(int $batchSize = 100, int $maxExecutionSecond // persisted record. The schema validation warning for a UUID-string value is // benign compared to a data-corruption window where the field is missing. $contactEntity->setObject($contactEntityObject); - $objectService->saveObject( + $this->objectService->saveObject( object: $contactEntity, register: $register, schema: $contactSchema, @@ -762,8 +765,6 @@ public function performFullSync(int $minutesBack = 10): array { */ private function getOrganisationObjectsByTimeWindow(string $register, string $organizationSchema, int $minutesBack): array { try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - // Build base query for register and schema. A sync tick may // genuinely need "all objects" (minutesBack <= 0) — bounded at a // documented safe ceiling rather than left unbounded. @@ -811,7 +812,7 @@ private function getOrganisationObjectsByTimeWindow(string $register, string $or } // Use searchObjects method for filtering. - $objects = $objectService->searchObjects(query: $query, _rbac: false, _multitenancy: false); + $objects = $this->objectService->searchObjects(query: $query, _rbac: false, _multitenancy: false); $this->logger->debug( 'OrganizationSyncService: Retrieved organisatie objects with searchObjects', @@ -949,8 +950,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st // Fetch the complete object from the database to ensure we have all data. try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $fullObject = $objectService->find( + $fullObject = $this->objectService->find( id: $organisationId, register: $organisationObject->getRegister(), schema: $organisationObject->getSchema(), @@ -988,10 +988,9 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $organizationSchema = ($voorzieningenConfig['organisatie_schema'] ?? ''); // Try to find existing organisation entity. - $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); try { - $organisationEntity = $organisationMapper->findByUuid($organisationId); + $organisationEntity = $this->organisationMapper->findByUuid($organisationId); // Entity exists - update it if needed. $status = strtolower(($objectData['status'] ?? 'actief')); @@ -1021,7 +1020,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $wasActive = $organisationEntity->getActive(); $organisationEntity->setActive($shouldBeActive); - $organisationMapper->save($organisationEntity); + $this->organisationMapper->save($organisationEntity); $stats['entitiesUpdated']++; // Send activation email if organization became active. @@ -1064,7 +1063,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st $slug = strtolower(preg_replace('/[^a-z0-9]+/', '-', strtolower($orgName))); $slug = trim($slug, '-'); try { - $organisationEntity = $organisationMapper->findBySlug($slug); + $organisationEntity = $this->organisationMapper->findBySlug($slug); $this->logger->info( 'OrganizationSyncService: Found existing entity by slug, updating UUID to match object', [ @@ -1077,7 +1076,7 @@ private function ensureOrganisationEntity(object $organisationObject, array &$st // Update the entity's UUID to match the object UUID so future lookups work. $organisationEntity->setUuid($organisationId); - $organisationMapper->save($organisationEntity); + $this->organisationMapper->save($organisationEntity); $stats['entitiesUpdated']++; // Update organisatie object owner to this entity. @@ -1241,8 +1240,6 @@ private function getContactPersonsForOrganisation(string $organisationId, string } try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - // Use searchObjects for more efficient filtering on-demand. $query = [ '@self' => [ @@ -1253,7 +1250,7 @@ private function getContactPersonsForOrganisation(string $organisationId, string '_limit' => 500, ]; - $contactPersons = $objectService->searchObjects(query: $query, _rbac: false, _multitenancy: false); + $contactPersons = $this->objectService->searchObjects(query: $query, _rbac: false, _multitenancy: false); $this->logger->debug( 'OrganizationSyncService: Retrieved contact persons on-demand', @@ -1412,8 +1409,7 @@ private function updateOrganisationEntityUsers(object $organisationEntity, array $organisationEntity->setUsers($allUsernames); - $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); - $organisationMapper->save($organisationEntity); + $this->organisationMapper->save($organisationEntity); $stats['entitiesUpdated']++; @@ -1516,10 +1512,9 @@ public function getSyncStatus(int $minutesBack = 10): array { ); // Get organization entities count. - $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); $entitiesCount = 0; try { - $entities = $organisationMapper->findAllWithUserCount(); + $entities = $this->organisationMapper->findAllWithUserCount(); $entitiesCount = count($entities); } catch (\Exception $e) { // Ignore errors in count. @@ -1857,8 +1852,7 @@ private function processNestedContactPersons($organizationObject, array &$stats) ); // Fetch the contact person object using the UUID. - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $contactObject = $objectService->find( + $contactObject = $this->objectService->find( id: $contactData, register: $register, schema: $contactSchema, @@ -1976,7 +1970,6 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz } // Find all contactpersoon objects that have this organization in their organisation property. - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); // Search for contactpersoon objects with this organization reference. // Try both 'organisatie' and 'organisation' field names. @@ -1999,7 +1992,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz ] ); - $relatedContacts = $objectService->searchObjects($query); + $relatedContacts = $this->objectService->searchObjects($query); // If not found, try with 'organisation' field. if (empty($relatedContacts) === true) { @@ -2014,7 +2007,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz ] ); - $relatedContacts = $objectService->searchObjects($query); + $relatedContacts = $this->objectService->searchObjects($query); } if (empty($relatedContacts) === true) { @@ -2032,7 +2025,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz $orgRegister = ($voorzieningenConfig2['register'] ?? ''); $orgSchema = ($voorzieningenConfig2['organisatie_schema'] ?? ''); - $rawOrgObject = $objectService->find( + $rawOrgObject = $this->objectService->find( id: $organizationUuid, register: $orgRegister, schema: $orgSchema, @@ -2063,7 +2056,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz } try { - $contactObj = $objectService->find( + $contactObj = $this->objectService->find( id: $contactUuid, register: $register, schema: $contactSchema, @@ -2131,7 +2124,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz ); try { - $fullContactObject = $objectService->find( + $fullContactObject = $this->objectService->find( id: $contactUuid, register: $register, schema: $contactSchema, @@ -2160,8 +2153,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz if (empty($contactData['organisatie']) === true) { $contactData['organisatie'] = $organizationUuid; $contactObject->setObject($contactData); - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + $this->magicMapper->update($contactObject); $this->logger->info( '[FLOW] Set missing organisatie field on related contact', [ @@ -2241,8 +2233,6 @@ private function createOrUpdateContactPersonObject( array &$stats, ): void { try { - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $email = ($contactData['email'] ?? $contactData['e-mailadres'] ?? ''); if (empty($email) === true) { $this->logger->warning( @@ -2272,7 +2262,7 @@ private function createOrUpdateContactPersonObject( ); try { - $contactObject = $objectService->find( + $contactObject = $this->objectService->find( id: $existingContactId, register: $register, schema: $contactSchema, @@ -2310,7 +2300,7 @@ private function createOrUpdateContactPersonObject( unset($contactData['id']); unset($contactData['uuid']); - $contactObject = $objectService->saveObject( + $contactObject = $this->objectService->saveObject( object: $contactData, register: $register, schema: $contactSchema, @@ -2323,8 +2313,7 @@ private function createOrUpdateContactPersonObject( $restoredData = $contactObject->getObject(); $restoredData['organisatie'] = $savedOrganisation; $contactObject->setObject($restoredData); - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + $this->magicMapper->update($contactObject); } }//end if @@ -2337,8 +2326,7 @@ private function createOrUpdateContactPersonObject( $contactObjectData['organisatie'] = $organizationUuid; $contactObject->setObject($contactObjectData); $contactObject->setOrganisation($organizationUuid); - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + $this->magicMapper->update($contactObject); $this->logger->info( '[FLOW] Set missing organisatie field on contact person', [ @@ -2364,8 +2352,7 @@ private function createOrUpdateContactPersonObject( // Check if organization exists in organisation entity table. $organisationEntity = null; try { - $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); - $organisationEntity = $organisationMapper->findByUuid($organizationUuid); + $organisationEntity = $this->organisationMapper->findByUuid($organizationUuid); } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { // Backup: org entity missing — create it now so user creation can proceed. $this->logger->info( @@ -2377,7 +2364,7 @@ private function createOrUpdateContactPersonObject( ); try { $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); - $orgObject = $objectService->find( + $orgObject = $this->objectService->find( id: $organizationUuid, register: ($voorzieningenConfig['register'] ?? ''), schema: ($voorzieningenConfig['organisatie_schema'] ?? '') @@ -2456,8 +2443,7 @@ private function createOrUpdateContactPersonObject( ); try { - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + $this->magicMapper->update($contactObject); $this->logger->info( 'Contact saved with username', [ @@ -2620,8 +2606,7 @@ public function processSpecificContactPerson($contactObject): array { // Check if organization exists in organisation entity table. $organisationEntity = null; try { - $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); - $organisationEntity = $organisationMapper->findByUuid($organizationUuid); + $organisationEntity = $this->organisationMapper->findByUuid($organizationUuid); } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { // Backup: org entity missing — create it now so user creation can proceed. $this->logger->info( @@ -2633,8 +2618,7 @@ public function processSpecificContactPerson($contactObject): array { ); try { $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $orgObject = $objectService->find( + $orgObject = $this->objectService->find( id: $organizationUuid, register: ($voorzieningenConfig['register'] ?? ''), schema: ($voorzieningenConfig['organisatie_schema'] ?? '') @@ -2697,8 +2681,7 @@ public function processSpecificContactPerson($contactObject): array { // that may fail — but the user was already created successfully above. try { $contactObject->setObject($contactEntityObject); - $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); - $objectService->saveObject( + $this->objectService->saveObject( object: $contactObject, register: $register, schema: $contactSchema, @@ -3183,8 +3166,7 @@ private function updateOrganisationObjectOwner( $organisationObject->setOrganisation($organisationEntityUuid); // Save using MagicMapper directly to bypass validation and ensure metadata is persisted. - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($organisationObject); + $this->magicMapper->update($organisationObject); $this->logger->info( 'OrganizationSyncService: Successfully updated organisatie object owner and organisation', @@ -3298,8 +3280,7 @@ private function updateContactPersonObjectOwner( } // Save using MagicMapper directly to bypass validation and ensure metadata is persisted. - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + $this->magicMapper->update($contactObject); $this->logger->info( 'OrganizationSyncService: Successfully updated contactpersoon object owner and organisation', From 58c3c3054e944bbd81547d2025deb536c7d64eea Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 09:48:46 +0200 Subject: [PATCH 02/14] refactor(deps): type-hint OpenRegister's published contract (ADR-084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 21 lib classes and 17 test classes take OCA\OpenRegister\Contract\ObjectServiceInterface instead of the concrete class, bound in the composition root, with hydra-gates bumped to v1.8.0 so composer installs the interface into vendor/. This is what makes the ADR-083 conversion mockable: a leaf app cannot load a class from another Nextcloud app, so a typed constructor parameter had no satisfiable double. Test doubles now mock the contract, which does load. Files naming ObjectService only as a CONTAINER KEY are untouched — those are availability-guarded lookups (the ADR-083 rule-1 exception), and the string must go on naming the concrete service because that is the key the alias resolves TO. --- composer.lock | 21 ++++++++------ lib/AppInfo/Application.php | 17 +++++++++++ lib/Controller/ContactpersonenController.php | 4 +-- .../UserProfileUpdatedEventListener.php | 4 +-- lib/Service/AanbodService.php | 12 ++++---- .../AangebodenGebruik/GebruikBulkHandler.php | 6 ++-- .../GebruikStatusHandler.php | 6 ++-- lib/Service/AangebodenGebruikService.php | 28 +++++++++---------- lib/Service/ArchiMate/ArchiMateContext.php | 6 ++-- lib/Service/ArchiMateExportService.php | 12 ++++---- lib/Service/ArchiMateImportService.php | 16 +++++------ lib/Service/ArchiMateService.php | 12 ++++---- lib/Service/ContactpersoonService.php | 4 +-- lib/Service/EolSyncService.php | 22 +++++++-------- lib/Service/FacetService.php | 20 ++++++------- lib/Service/GebruikSyncService.php | 4 +-- lib/Service/OrganizationSyncService.php | 4 +-- lib/Service/SbomImportService.php | 16 +++++------ lib/Service/SettingsService.php | 4 +-- lib/Service/SoftwareCatalogue/ApiClient.php | 6 ++-- .../ContactPersonHandler.php | 4 +-- .../SoftwareCatalogue/OrganizationHandler.php | 8 +++--- lib/Service/SoftwareCatalogueService.php | 4 +-- tests/OrganizationSyncTest.php | 6 ++-- ...ersonenControllerOrganisationScopeTest.php | 6 ++-- ...personenControllerUpdateUserGroupsTest.php | 8 +++--- tests/Unit/OrganisationUserWorkflowTest.php | 10 +++---- tests/Unit/SbomImportServiceTest.php | 10 +++---- .../Service/AangebodenGebruikServiceTest.php | 8 +++--- .../Service/ContractApprovalServiceTest.php | 6 ++-- tests/Unit/Service/EolSyncServiceTest.php | 10 +++---- tests/Unit/Service/FacetServiceTest.php | 22 +++++++-------- .../GebruikServiceGetApplicationIdsTest.php | 8 +++--- tests/Unit/Service/IntakeModerationTest.php | 14 +++++----- .../Service/MergeOrganisatieServiceTest.php | 4 +-- .../Service/PortfolioReportServiceTest.php | 6 ++-- tests/Unit/Service/PublicationServiceTest.php | 8 +++--- tests/Unit/Service/QueryLimitBoundingTest.php | 8 +++--- .../Service/ReviewAggregateServiceTest.php | 10 +++---- tests/Unit/Service/ReviewServiceTest.php | 10 +++---- 40 files changed, 208 insertions(+), 186 deletions(-) diff --git a/composer.lock b/composer.lock index 0872a374..931d342f 100644 --- a/composer.lock +++ b/composer.lock @@ -2792,16 +2792,16 @@ }, { "name": "conduction/hydra-gates", - "version": "v1.7.3", + "version": "v1.8.0", "source": { "type": "git", "url": "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/ConductionNL/.github.git", - "reference": "9b9896abf87167e97b821d8ee86c5422f0b32e80" + "reference": "9fc64ab93f7a20b69f6b5912745ef9fc456c1cee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/.github/zipball/9b9896abf87167e97b821d8ee86c5422f0b32e80", - "reference": "9b9896abf87167e97b821d8ee86c5422f0b32e80", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/9fc64ab93f7a20b69f6b5912745ef9fc456c1cee", + "reference": "9fc64ab93f7a20b69f6b5912745ef9fc456c1cee", "shasum": "" }, "require": { @@ -2818,6 +2818,11 @@ "schemas": "hydra-gates/scripts/schemas" } }, + "autoload": { + "psr-4": { + "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "EUPL-1.2" @@ -2840,9 +2845,9 @@ "support": { "docs": "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/ConductionNL/.github/blob/main/hydra-gates/README.md", "issues": "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/ConductionNL/.github/issues", - "source": "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/ConductionNL/.github/tree/v1.7.3" + "source": "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/ConductionNL/.github/tree/v1.8.0" }, - "time": "2026-08-12T22:32:31+00:00" + "time": "2026-08-15T06:24:48+00:00" }, { "name": "consolidation/annotated-command", @@ -9978,9 +9983,9 @@ "platform": { "php": "^8.3" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 41ce0b95..cbc604d9 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -21,6 +21,7 @@ namespace OCA\SoftwareCatalog\AppInfo; use OCA\Decidesk\Event\DecisionConcludedEvent; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Event\ObjectCreatedEvent; use OCA\OpenRegister\Event\ObjectUpdatedEvent; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; @@ -127,6 +128,22 @@ public function __construct() { * @spec openspec/specs/settings-service/spec.md */ public function register(IRegistrationContext $context): void { + + // ADR-084: services type-hint OpenRegister's PUBLISHED interface, never its + // concrete class, so this app's unit tests can mock a type they are able to + // load. Nextcloud autowires concrete classes across apps but not interfaces, + // so the binding has to be stated — and the composition root is where this + // app says how it is wired. + // + // An ALIAS, not a factory: it resolves when something actually asks for the + // interface, so an instance without OpenRegister fails at the route that + // needed the data rather than at registration. Both names are strings and + // neither triggers an autoload, which is what keeps ADR-083 rule 3's promise + // that the start screen still boots. + $context->registerServiceAlias( + ObjectServiceInterface::class, + 'OCA\OpenRegister\Service\ObjectService' + ); include_once __DIR__ . '/../../vendor/autoload.php'; $this->registerHandlerServices(context: $context); diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index 1d171984..0bb3f754 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -35,7 +35,7 @@ use OCP\Security\ISecureRandom; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Service\OrganisationService; @@ -155,7 +155,7 @@ public function __construct( ContainerInterface $container, ISecureRandom $secureRandom, LoggerInterface $logger, - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly MagicMapper $magicMapper, private readonly OrganisationService $organisationService, ) { diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index e95d9b3c..772a7d23 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -25,7 +25,7 @@ use OCP\EventDispatcher\IEventListener; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Db\SchemaMapper; use OCA\OpenRegister\Db\RegisterMapper; use OCA\OpenRegister\Service\Object\SaveObject\MetadataHydrationHandler; @@ -61,7 +61,7 @@ class UserProfileUpdatedEventListener implements IEventListener { */ public function __construct( private readonly ContainerInterface $container, - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly SchemaMapper $schemaMapper, private readonly RegisterMapper $registerMapper, private readonly MetadataHydrationHandler $metadataHydrationHandler, diff --git a/lib/Service/AanbodService.php b/lib/Service/AanbodService.php index 73dc8915..26a59dce 100644 --- a/lib/Service/AanbodService.php +++ b/lib/Service/AanbodService.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Service; use Exception; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IUserSession; @@ -590,13 +590,13 @@ private function resolvePartyId(mixed $partyInfo): ?string { * an aanbod can be any of these types. Register/schema context is * required to find objects stored in magic tables. * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $aanbodId The UUID of the aanbod object * * @return \OCA\OpenRegister\Db\ObjectEntity|null The found object or null */ private function findAanbodObject( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $aanbodId, ): ?\OCA\OpenRegister\Db\ObjectEntity { $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); @@ -681,7 +681,7 @@ private function getCurrentOrganisation(): ?string { /** * Get ObjectService from OpenRegister app. * - * @return ObjectService The OpenRegister object service + * @return ObjectServiceInterface The OpenRegister object service * * @throws Exception When OpenRegister service is not available */ @@ -747,14 +747,14 @@ private function addQueryFilters(array $baseQuery, array $options): array { * Looks up the organisation object by UUID, reads its type, and maps it * to the appropriate registeredBy value using TYPE_MAP. * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param array $objectData The object data to update * @param string $organisationUuid The UUID of the organisation to look up * * @return array The updated object data */ private function updateRegisteredBy( - ObjectService $objectService, + ObjectServiceInterface $objectService, array $objectData, string $organisationUuid, ): array { diff --git a/lib/Service/AangebodenGebruik/GebruikBulkHandler.php b/lib/Service/AangebodenGebruik/GebruikBulkHandler.php index 8f7e6a18..76dcf196 100644 --- a/lib/Service/AangebodenGebruik/GebruikBulkHandler.php +++ b/lib/Service/AangebodenGebruik/GebruikBulkHandler.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Service\AangebodenGebruik; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Log\LoggerInterface; /** @@ -38,13 +38,13 @@ class GebruikBulkHandler { /** * Constructor. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param LoggerInterface $logger Logger instance. * * @spec openspec/changes/method-decomposition/tasks.md#task-7 */ public function __construct( - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly LoggerInterface $logger, ) { }//end __construct() diff --git a/lib/Service/AangebodenGebruik/GebruikStatusHandler.php b/lib/Service/AangebodenGebruik/GebruikStatusHandler.php index 9dad695b..25655a25 100644 --- a/lib/Service/AangebodenGebruik/GebruikStatusHandler.php +++ b/lib/Service/AangebodenGebruik/GebruikStatusHandler.php @@ -24,7 +24,7 @@ namespace OCA\SoftwareCatalog\Service\AangebodenGebruik; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Log\LoggerInterface; /** @@ -39,14 +39,14 @@ class GebruikStatusHandler { /** * Constructor. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param StatusTransitionValidator $validator Status transition validator. * @param LoggerInterface $logger Logger instance. * * @spec openspec/changes/method-decomposition/tasks.md#task-7 */ public function __construct( - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly StatusTransitionValidator $validator, private readonly LoggerInterface $logger, ) { diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php index 69cd4ff3..770e5201 100644 --- a/lib/Service/AangebodenGebruikService.php +++ b/lib/Service/AangebodenGebruikService.php @@ -21,7 +21,7 @@ namespace OCA\SoftwareCatalog\Service; use Exception; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IUserSession; @@ -1046,13 +1046,13 @@ public function setGebruikSelfToActiveOrg(string $gebruikId, array $options = [] * either type. Register/schema context is required to find objects * stored in magic tables. * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $objectId The UUID of the object to find * * @return \OCA\OpenRegister\Db\ObjectEntity|null The found object or null */ private function findGebruikOrIntegration( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $objectId, ): ?\OCA\OpenRegister\Db\ObjectEntity { $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); @@ -1135,7 +1135,7 @@ private function getCurrentOrganisation(): ?string { /** * Get ObjectService from OpenRegister app * - * @return ObjectService The OpenRegister object service + * @return ObjectServiceInterface The OpenRegister object service * @throws Exception When OpenRegister service is not available */ private function getObjectService(): ObjectService { @@ -1241,7 +1241,7 @@ private function resolveVoorzieningenSchemaConfig(string $schemaKey, string $lab * * Uses ObjectService's buildSearchQuery() for proper query construction * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $registerId The register ID * @param string $schemaId The schema ID * @param array $options Query options (includes request parameters for buildSearchQuery) @@ -1252,7 +1252,7 @@ private function resolveVoorzieningenSchemaConfig(string $schemaKey, string $lab * @throws Exception When query fails */ private function getAllObjectsForSchema( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $registerId, string $schemaId, array $options = [], @@ -1297,7 +1297,7 @@ private function getAllObjectsForSchema( /** * Get all applications/modules owned by an organization * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $organisationUuid The organization UUID * * @return array Array of application/module UUIDs @@ -1305,7 +1305,7 @@ private function getAllObjectsForSchema( * @throws Exception When query fails */ private function getApplicationsOwnedByOrganisation( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $organisationUuid, ): array { try { @@ -1403,7 +1403,7 @@ private function getApplicationsOwnedByOrganisation( * * Uses ObjectService's buildSearchQuery() for proper query construction * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $registerId The register ID * @param string $schemaId The schema ID * @param string $relatedUuid The UUID to find relationships for @@ -1415,7 +1415,7 @@ private function getApplicationsOwnedByOrganisation( * @throws Exception When query fails */ private function getObjectsRelatedToUuid( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $registerId, string $schemaId, string $relatedUuid, @@ -1463,14 +1463,14 @@ private function getObjectsRelatedToUuid( /** * Check if an organization owns a specific application/module * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param string $appUuid The application/module UUID * @param string $organisationUuid The organization UUID * * @return bool True if organization owns the application/module */ private function checkOrganisationOwnership( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $appUuid, string $organisationUuid, ): bool { @@ -1532,14 +1532,14 @@ private function checkOrganisationOwnership( * Looks up the organisation object by UUID, reads its type, and maps it * to the appropriate registeredBy value using TYPE_MAP. * - * @param ObjectService $objectService The OpenRegister object service + * @param ObjectServiceInterface $objectService The OpenRegister object service * @param array $objectData The object data to update * @param string $organisationUuid The UUID of the organisation to look up * * @return array The updated object data */ private function updateRegisteredBy( - ObjectService $objectService, + ObjectServiceInterface $objectService, array $objectData, string $organisationUuid, ): array { diff --git a/lib/Service/ArchiMate/ArchiMateContext.php b/lib/Service/ArchiMate/ArchiMateContext.php index b1f11729..e31edf57 100644 --- a/lib/Service/ArchiMate/ArchiMateContext.php +++ b/lib/Service/ArchiMate/ArchiMateContext.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Service\ArchiMate; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\SettingsService; use Psr\Log\LoggerInterface; @@ -40,14 +40,14 @@ class ArchiMateContext { /** * Constructor. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param SettingsService $settingsService The SoftwareCatalog settings service. * @param LoggerInterface $logger The application logger. * * @spec openspec/changes/method-decomposition/tasks.md#task-4 */ public function __construct( - public readonly ObjectService $objectService, + public readonly ObjectServiceInterface $objectService, public readonly SettingsService $settingsService, public readonly LoggerInterface $logger, ) { diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index cae3b38c..a08a3280 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -796,7 +796,7 @@ private function addObjectToFolder(\SimpleXMLElement $folder, array $object, str * requires both register AND schema in the query. Without schema, the query * falls back to the generic objects table (which is empty for magic-table registers). * - * @param \OCA\OpenRegister\Service\ObjectService $objectService OpenRegister ObjectService. + * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService OpenRegister ObjectService. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Mapping of schema IDs to schema types. * @@ -806,7 +806,7 @@ private function addObjectToFolder(\SimpleXMLElement $folder, array $object, str * @spec openspec/specs/archimate-export/spec.md */ public function getObjectsFromDatabase( - \OCA\OpenRegister\Service\ObjectService $objectService, + \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService, int $registerId, array $schemaIdMap = [], ): array { @@ -940,7 +940,7 @@ public function addPropertyDefinitionsToXml(\SimpleXMLElement $xml, array $prope * 3. Direct XML generation without intermediate arrays * 4. No JSON serialization overhead * - * @param \OCA\OpenRegister\Service\ObjectService $objectService OpenRegister ObjectService. + * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService OpenRegister ObjectService. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Schema IDs to types mapping. * @param string|null $organization Organization filter. @@ -949,7 +949,7 @@ public function addPropertyDefinitionsToXml(\SimpleXMLElement $xml, array $prope * @spec openspec/specs/archimate-export/spec.md */ public function exportArchiMateXml( - \OCA\OpenRegister\Service\ObjectService $objectService, + \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService, int $registerId, array $schemaIdMap, ?string $organization = null, @@ -2384,7 +2384,7 @@ private function validateTextContentNormalized(\SimpleXMLElement $xml): void { * referentiecomponenten, copies views with applications plotted inside, and * adds SWC-specific organization folders. * - * @param \OCA\OpenRegister\Service\ObjectService $objectService The object service. + * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService The object service. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Schema ID to type map. * @param string $orgName Organization name. @@ -2398,7 +2398,7 @@ private function validateTextContentNormalized(\SimpleXMLElement $xml): void { * @spec openspec/specs/archimate-export/spec.md */ public function exportOrganizationArchiMateXml( - \OCA\OpenRegister\Service\ObjectService $objectService, + \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService, int $registerId, array $schemaIdMap, string $orgName, diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index a923d8fc..1bbed3ee 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -20,7 +20,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\OrganisationService; use OCP\App\IAppManager; use OCP\Files\IRootFolder; @@ -1546,12 +1546,12 @@ private function fixStandaardVersieUuids(int $registerId): void { * Lets ObjectService handle all batching, throttling, and optimization internally * * @param array $objects Array of objects to save - * @param ObjectService $objectService ObjectService instance + * @param ObjectServiceInterface $objectService ObjectService instance * @param int $registerId Register ID * * @return array Array of saved objects */ - private function saveObjectsDirectToService(array $objects, ObjectService $objectService, int $registerId): array { + private function saveObjectsDirectToService(array $objects, ObjectServiceInterface $objectService, int $registerId): array { try { // GROUP BY SCHEMA: For magic mapping support, save objects schema by schema. // This ensures each batch has a single schema so UnifiedObjectMapper can route to the correct table. @@ -1654,12 +1654,12 @@ private function saveObjectsDirectToService(array $objects, ObjectService $objec * Save objects in parallel batches for maximum performance (DEPRECATED) * * @param array $objects Array of objects to save - * @param ObjectService $objectService ObjectService instance + * @param ObjectServiceInterface $objectService ObjectService instance * @param int $registerId Register ID * * @return array Array of saved objects */ - private function saveObjectsInParallelBatches(array $objects, ObjectService $objectService, int $registerId): array { + private function saveObjectsInParallelBatches(array $objects, ObjectServiceInterface $objectService, int $registerId): array { $batchSize = self::PERFORMANCE_OPTIMIZATIONS['batch_size']; $parallelBatches = self::PERFORMANCE_OPTIMIZATIONS['parallel_batches']; @@ -1769,12 +1769,12 @@ private function saveObjectsInParallelBatches(array $objects, ObjectService $obj * Save objects in a single batch (fallback method) * * @param array $objects Array of objects to save - * @param ObjectService $objectService ObjectService instance + * @param ObjectServiceInterface $objectService ObjectService instance * @param int $registerId Register ID * * @return array Array of saved objects */ - private function saveObjectsInSingleBatch(array $objects, ObjectService $objectService, int $registerId): array { + private function saveObjectsInSingleBatch(array $objects, ObjectServiceInterface $objectService, int $registerId): array { // Using single batch processing. // Disable RBAC for bulk import when the performance optimisation flag is set. if (self::PERFORMANCE_OPTIMIZATIONS['disable_rbac'] === true) { @@ -1812,7 +1812,7 @@ private function saveObjectsInSingleBatch(array $objects, ObjectService $objectS /** * Get ObjectService from container * - * @return ObjectService|null ObjectService instance or null if not available + * @return ObjectServiceInterface|null ObjectService instance or null if not available */ private function getObjectService(): ?ObjectService { if ($this->appManager->isInstalled(appId: 'openregister') === false) { diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index 42a638fb..05d0f0f2 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -21,7 +21,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; use OCP\Files\IRootFolder; use OCP\IAppConfig; @@ -1076,12 +1076,12 @@ private function saveObjectsToDatabase(array $objects): array { * Save objects in parallel batches for maximum performance * * @param array $objects Array of objects to save - * @param ObjectService $objectService ObjectService instance + * @param ObjectServiceInterface $objectService ObjectService instance * @param int $registerId Register ID * * @return array Array of saved objects */ - private function saveObjectsInParallelBatches(array $objects, ObjectService $objectService, int $registerId): array { + private function saveObjectsInParallelBatches(array $objects, ObjectServiceInterface $objectService, int $registerId): array { $batchSize = self::PERFORMANCE_OPTIMIZATIONS['batch_size']; $parallelBatches = self::PERFORMANCE_OPTIMIZATIONS['parallel_batches']; @@ -1195,12 +1195,12 @@ private function saveObjectsInParallelBatches(array $objects, ObjectService $obj * Save objects in a single batch (fallback method) * * @param array $objects Array of objects to save - * @param ObjectService $objectService ObjectService instance + * @param ObjectServiceInterface $objectService ObjectService instance * @param int $registerId Register ID * * @return array Array of saved objects */ - private function saveObjectsInSingleBatch(array $objects, ObjectService $objectService, int $registerId): array { + private function saveObjectsInSingleBatch(array $objects, ObjectServiceInterface $objectService, int $registerId): array { $this->logger->info( 'Using single batch processing', [ @@ -1281,7 +1281,7 @@ private function saveObjectsInSingleBatch(array $objects, ObjectService $objectS /** * Get ObjectService from container * - * @return ObjectService|null ObjectService instance or null if not available + * @return ObjectServiceInterface|null ObjectService instance or null if not available */ private function getObjectService(): ?ObjectService { if ($this->appManager->isInstalled(appId: 'openregister') === false) { diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index e0334b5e..7c794199 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -659,9 +659,9 @@ private function handleRoleChanges(object $newContactPersonObject, object $oldCo /** * Gets the ObjectService instance * - * @return \OCA\OpenRegister\Service\ObjectService|null + * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null */ - private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { + private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if ($this->appManager->isEnabledForUser('openregister') === false) { return null; } diff --git a/lib/Service/EolSyncService.php b/lib/Service/EolSyncService.php index b617beae..3074f331 100644 --- a/lib/Service/EolSyncService.php +++ b/lib/Service/EolSyncService.php @@ -33,7 +33,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\AppFramework\Utility\ITimeFactory; use Psr\Log\LoggerInterface; @@ -236,12 +236,12 @@ public function run(): array { * on the given `ObjectService` context. Returns false (never throws) * when the register or either schema cannot be found. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param array $config The EOL sync configuration. * * @return bool True when the register/schemas resolved successfully. */ - private function resolveEolContext(ObjectService $objectService, array $config): bool { + private function resolveEolContext(ObjectServiceInterface $objectService, array $config): bool { try { $objectService->setRegister($config['register']); $objectService->setSchema($config['productSchema']); @@ -270,13 +270,13 @@ private function resolveEolContext(ObjectService $objectService, array $config): * write only happen for mapped modules (design.md non-functional * performance note). * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param int $moduleRegisterId The module register id. * @param int $moduleSchemaId The module schema id. * * @return array The mapped module rows (normalised arrays). */ - private function findMappedModules(ObjectService $objectService, int $moduleRegisterId, int $moduleSchemaId): array { + private function findMappedModules(ObjectServiceInterface $objectService, int $moduleRegisterId, int $moduleSchemaId): array { $query = [ '@self' => [ 'schema' => $moduleSchemaId, @@ -308,13 +308,13 @@ private function findMappedModules(ObjectService $objectService, int $moduleRegi * match — matching must never cross into another product's cycles * (design.md Decision 2 mitigation). * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param array $config The EOL sync configuration. * @param string $productSlug The mapped module's `eolProductSlug`. * * @return array The matching `eolCycle` rows (normalised arrays). */ - private function fetchCycles(ObjectService $objectService, array $config, string $productSlug): array { + private function fetchCycles(ObjectServiceInterface $objectService, array $config, string $productSlug): array { try { $objectService->setRegister($config['register']); $objectService->setSchema($config['cycleSchema']); @@ -352,7 +352,7 @@ private function fetchCycles(ObjectService $objectService, array $config, string /** * Fetch the `moduleVersie` rows belonging to one module. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param int $moduleRegisterId The (softwarecatalog) module register id. * @param int $versionSchemaId The moduleVersie schema id. * @param string $moduleUuid The owning module's uuid. @@ -360,7 +360,7 @@ private function fetchCycles(ObjectService $objectService, array $config, string * @return array The module's `moduleVersie` rows (normalised arrays). */ private function fetchModuleVersions( - ObjectService $objectService, + ObjectServiceInterface $objectService, int $moduleRegisterId, int $versionSchemaId, string $moduleUuid, @@ -397,7 +397,7 @@ private function fetchModuleVersions( * existing uuid is supplied so this is an update, never a duplicate * create. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param int $moduleRegisterId The module register id. * @param int $versionSchemaId The moduleVersie schema id. * @param array $stampedVersion The complete stamped `moduleVersie` object. @@ -407,7 +407,7 @@ private function fetchModuleVersions( * @spec openspec/specs/eol-feed-integration/spec.md#requirement-stamping-preserves-every-other-field-and-records-provenance */ private function saveStampedVersion( - ObjectService $objectService, + ObjectServiceInterface $objectService, int $moduleRegisterId, int $versionSchemaId, array $stampedVersion, diff --git a/lib/Service/FacetService.php b/lib/Service/FacetService.php index 83446db2..3db32f58 100644 --- a/lib/Service/FacetService.php +++ b/lib/Service/FacetService.php @@ -26,7 +26,7 @@ namespace OCA\SoftwareCatalog\Service; use InvalidArgumentException; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\ICache; use OCP\ICacheFactory; use OCP\IUserSession; @@ -226,7 +226,7 @@ private function assertSupportedSchema(string $schema): void { * values, compute disjunctive counts, and assemble the response * (including `_meta.matchedObjectIds` for the frontend's list narrowing). * - * @param ObjectService $objectService OpenRegister object service. + * @param ObjectServiceInterface $objectService OpenRegister object service. * @param string $schema `module` or `dienst`. * @param array $normalizedFilters Normalized selected filters. * @param string|null $normalizedSearch Normalized free-text query. @@ -237,7 +237,7 @@ private function assertSupportedSchema(string $schema): void { * matchedObjectIds: string[]}} */ private function computeFacetsForRequest( - ObjectService $objectService, + ObjectServiceInterface $objectService, string $schema, array $normalizedFilters, ?string $normalizedSearch, @@ -386,7 +386,7 @@ private function buildCacheKey(string $schema, array $filters, ?string $search, * documented `MAX_BASE_PAGES` ceiling instead of ever issuing a single * unbounded `searchObjects()` call. * - * @param ObjectService $objectService OpenRegister object service. + * @param ObjectServiceInterface $objectService OpenRegister object service. * @param string $schema `module` or `dienst`. * @param string|null $search Free-text query. * @param string|null $organization Organisation override. @@ -396,7 +396,7 @@ private function buildCacheKey(string $schema, array $filters, ?string $search, * @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-queries-must-be-bounded * @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-counts-must-respect-the-callers-rbactenant-context */ - private function fetchBaseObjects(ObjectService $objectService, string $schema, ?string $search, ?string $organization): array { + private function fetchBaseObjects(ObjectServiceInterface $objectService, string $schema, ?string $search, ?string $organization): array { $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); $registerId = $voorzieningenConfig['register'] ?? null; $schemaId = $voorzieningenConfig[$schema . '_schema'] ?? null; @@ -512,13 +512,13 @@ private function normalizeObject(mixed $object): array { * links are only transitive via `dienst.modules` — the linked module objects * are resolved with a single bounded batch lookup. * - * @param ObjectService $objectService OpenRegister object service. + * @param ObjectServiceInterface $objectService OpenRegister object service. * @param string $schema `module` or `dienst`. * @param array $baseObjects The candidate objects from `fetchBaseObjects()`. * * @return array> Object id => list of module objects. */ - private function resolveModulesPerObject(ObjectService $objectService, string $schema, array $baseObjects): array { + private function resolveModulesPerObject(ObjectServiceInterface $objectService, string $schema, array $baseObjects): array { $modulesByObjectId = []; if ($schema === 'module') { @@ -566,14 +566,14 @@ private function resolveModulesPerObject(ObjectService $objectService, string $s * Batch-fetch module objects by their OpenRegister object id, bounded by an * explicit `_limit`. * - * @param ObjectService $objectService OpenRegister object service. + * @param ObjectServiceInterface $objectService OpenRegister object service. * @param array $identifiers Distinct module object identifiers to resolve. * * @return array Module id => module object. * * @spec openspec/specs/gemma-faceted-search/spec.md#requirement-facet-aggregation-queries-must-be-bounded */ - private function fetchModulesByIdentifiers(ObjectService $objectService, array $identifiers): array { + private function fetchModulesByIdentifiers(ObjectServiceInterface $objectService, array $identifiers): array { if (empty($identifiers) === true) { return []; } @@ -1101,7 +1101,7 @@ private function getCurrentOrganisation(): ?string { /** * Get ObjectService from the container. * - * @return ObjectService|null ObjectService instance or null if not available. + * @return ObjectServiceInterface|null ObjectService instance or null if not available. */ private function getObjectService(): ?ObjectService { try { diff --git a/lib/Service/GebruikSyncService.php b/lib/Service/GebruikSyncService.php index 8d096347..20bb3b88 100644 --- a/lib/Service/GebruikSyncService.php +++ b/lib/Service/GebruikSyncService.php @@ -29,7 +29,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; /** * Service for synchronizing and processing Gebruik (Usage) objects. @@ -87,7 +87,7 @@ public function __construct( LoggerInterface $logger, SettingsService $settingsService, ContainerInterface $container, - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, ) { $this->logger = $logger; $this->settingsService = $settingsService; diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index b87d0b87..19c42d99 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -20,7 +20,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; use OCP\IAppConfig; use OCP\IDBConnection; @@ -132,7 +132,7 @@ public function __construct( private IDBConnection $db, private readonly ContactPersonHandler $contactpersonHandler, ContainerInterface $container, - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly OrganisationMapper $organisationMapper, private readonly MagicMapper $magicMapper, ) { diff --git a/lib/Service/SbomImportService.php b/lib/Service/SbomImportService.php index 8c6a6ee9..c21ac98b 100644 --- a/lib/Service/SbomImportService.php +++ b/lib/Service/SbomImportService.php @@ -39,7 +39,7 @@ namespace OCA\SoftwareCatalog\Service; use DateTime; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use RuntimeException; @@ -330,7 +330,7 @@ public function getStatus(string $moduleVersieUuid, ?string $operationId = null) * in bounded batches. OR's search already excludes `_deleted` rows by * default, so a prior replace's trashed rows are never re-queried. * - * @param ObjectService $objectService The OR object service. + * @param ObjectServiceInterface $objectService The OR object service. * @param int $registerId The voorzieningen register id. * @param int $componentSchemaId The sbomComponent schema id. * @param string $moduleVersieUuid The target moduleVersie uuid. @@ -340,7 +340,7 @@ public function getStatus(string $moduleVersieUuid, ?string $operationId = null) * @spec openspec/specs/sbom-import/spec.md#requirement-re-import-replaces-the-previous-component-set-and-is-soft-delete-aware */ private function replacePreviousComponentSet( - ObjectService $objectService, + ObjectServiceInterface $objectService, int $registerId, int $componentSchemaId, string $moduleVersieUuid, @@ -378,7 +378,7 @@ private function replacePreviousComponentSet( * Bulk-save the newly parsed component set in bounded batches, reporting * progress per batch when tracking is active. * - * @param ObjectService $objectService The OR object service. + * @param ObjectServiceInterface $objectService The OR object service. * @param int $registerId The voorzieningen register id. * @param int $componentSchemaId The sbomComponent schema id. * @param string $moduleVersieUuid The target moduleVersie uuid. @@ -391,7 +391,7 @@ private function replacePreviousComponentSet( * @spec openspec/specs/sbom-import/spec.md#requirement-large-imports-run-in-bounded-batches-with-progress-reporting */ private function createComponentSet( - ObjectService $objectService, + ObjectServiceInterface $objectService, int $registerId, int $componentSchemaId, string $moduleVersieUuid, @@ -437,7 +437,7 @@ private function createComponentSet( * provenance fields are changed (an omitted field would otherwise be * nulled by `saveObject()`). * - * @param ObjectService $objectService The OR object service. + * @param ObjectServiceInterface $objectService The OR object service. * @param int $registerId The voorzieningen register id. * @param int $versionSchemaId The moduleVersie schema id. * @param object $moduleVersion The current moduleVersie entity. @@ -449,7 +449,7 @@ private function createComponentSet( * @spec openspec/specs/sbom-import/spec.md#requirement-moduleversie-records-sbom-import-provenance */ private function recordProvenance( - ObjectService $objectService, + ObjectServiceInterface $objectService, int $registerId, int $versionSchemaId, object $moduleVersion, @@ -609,7 +609,7 @@ private function resolveCoordinates(): array { /** * Get the OpenRegister ObjectService from the DI container. * - * @return ObjectService|null The object service, or null if not available. + * @return ObjectServiceInterface|null The object service, or null if not available. */ private function getObjectService(): ?ObjectService { try { diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index 74b390b1..94f7f85c 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -162,12 +162,12 @@ public function isOpenRegisterEnabled(): bool { /** * Attempts to retrieve the OpenRegister service from the container * - * @return \OCA\OpenRegister\Service\ObjectService|null The OpenRegister service if available + * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null The OpenRegister service if available * * @throws \RuntimeException If the service is not available * @spec openspec/specs/settings-service/spec.md */ - public function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { + public function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === true) { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } diff --git a/lib/Service/SoftwareCatalogue/ApiClient.php b/lib/Service/SoftwareCatalogue/ApiClient.php index a0e193b6..baad662f 100644 --- a/lib/Service/SoftwareCatalogue/ApiClient.php +++ b/lib/Service/SoftwareCatalogue/ApiClient.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Log\LoggerInterface; /** @@ -39,13 +39,13 @@ class ApiClient { /** * Constructor. * - * @param ObjectService $objectService The OpenRegister object service. + * @param ObjectServiceInterface $objectService The OpenRegister object service. * @param LoggerInterface $logger Logger instance. * * @spec openspec/changes/method-decomposition/tasks.md#task-2 */ public function __construct( - private readonly ObjectService $objectService, + private readonly ObjectServiceInterface $objectService, private readonly LoggerInterface $logger, ) { }//end __construct() diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index fefcf921..84236f7a 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -87,10 +87,10 @@ public function __construct( /** * Gets the OpenRegister ObjectService if available * - * @return \OCA\OpenRegister\Service\ObjectService|null ObjectService instance or null + * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null ObjectService instance or null * @throws \RuntimeException If service is not available */ - private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { + private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if (in_array('openregister', $this->_appManager->getInstalledApps()) === true) { return $this->_container->get('OCA\OpenRegister\Service\ObjectService'); } diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/SoftwareCatalogue/OrganizationHandler.php index 914b79f2..2e4ecb79 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/SoftwareCatalogue/OrganizationHandler.php @@ -75,11 +75,11 @@ public function __construct( /** * Gets the OpenRegister ObjectService if available. * - * @return \OCA\OpenRegister\Service\ObjectService|null ObjectService instance or null + * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null ObjectService instance or null * * @throws \RuntimeException If service is not available */ - private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { + private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->_appManager->getInstalledApps()) === true) { return $this->_container->get('OCA\OpenRegister\Service\ObjectService'); } @@ -477,7 +477,7 @@ public function processContactpersonen(object $organizationObject): array { * * @param string $email The email address to search for * @param string $organizationUuid The organization UUID - * @param \OCA\OpenRegister\Service\ObjectService $objectService The object service + * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService The object service * @param int $registerId The register ID * @param int $contactgegevensSchemaId The contactgegevens schema ID * @@ -486,7 +486,7 @@ public function processContactpersonen(object $organizationObject): array { private function findExistingContactgegevens( string $email, string $organizationUuid, - \OCA\OpenRegister\Service\ObjectService $objectService, + \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService, int $registerId, int $contactgegevensSchemaId, ): ?object { diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 14c21786..585031c2 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -107,9 +107,9 @@ public function __construct( /** * Gets the ObjectService instance * - * @return \OCA\OpenRegister\Service\ObjectService|null + * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null */ - private function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService { + private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if ($this->_appManager->isEnabledForUser(appId: 'openregister') === false) { return null; } diff --git a/tests/OrganizationSyncTest.php b/tests/OrganizationSyncTest.php index 852aa06d..85bc001f 100644 --- a/tests/OrganizationSyncTest.php +++ b/tests/OrganizationSyncTest.php @@ -17,7 +17,7 @@ namespace OCA\SoftwareCatalog\Tests; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\SettingsService; use OCA\SoftwareCatalog\Service\SoftwareCatalogueService; use OCP\AppFramework\Db\Entity; @@ -43,7 +43,7 @@ class OrganizationSyncTest extends TestCase { */ public function testOrganizationCreationSync(): void { // Mock dependencies - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $settingsService = $this->createMock(SettingsService::class); $userManager = $this->createMock(IUserManager::class); $groupManager = $this->createMock(IGroupManager::class); @@ -143,7 +143,7 @@ public function testOrganizationStatusMapping(): void { */ public function testContactpersoonOrganizationMembership(): void { // Mock dependencies - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $settingsService = $this->createMock(SettingsService::class); // Configure mocks diff --git a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php index ee543b45..ca0a937d 100644 --- a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php @@ -27,7 +27,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -75,7 +75,7 @@ class ContactpersonenControllerOrganisationScopeTest extends TestCase { private ContainerInterface|MockObject $container; /** @var ObjectService|MockObject */ - private ObjectService|MockObject $objectService; + private ObjectServiceInterface|MockObject $objectService; /** @var ContactpersoonService|MockObject */ private ContactpersoonService|MockObject $contactSvc; @@ -96,7 +96,7 @@ protected function setUp(): void { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->userSession = $this->createMock(IUserSession::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->contactSvc = $this->createMock(ContactpersoonService::class); $this->logger = $this->createMock(LoggerInterface::class); $this->container = $this->createMock(ContainerInterface::class); diff --git a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php index 6040f3af..dd32ef4c 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php @@ -20,7 +20,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -61,8 +61,8 @@ class ContactpersonenControllerUpdateUserGroupsTest extends TestCase { /** @var ContainerInterface|MockObject */ private ContainerInterface|MockObject $container; - /** @var ObjectService|MockObject */ - private ObjectService|MockObject $objectService; + /** @var ObjectServiceInterface|MockObject */ + private ObjectServiceInterface|MockObject $objectService; /** @var LoggerInterface|MockObject */ private LoggerInterface|MockObject $logger; @@ -80,7 +80,7 @@ protected function setUp(): void { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->userSession = $this->createMock(IUserSession::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->logger = $this->createMock(LoggerInterface::class); $this->container = $this->createMock(ContainerInterface::class); diff --git a/tests/Unit/OrganisationUserWorkflowTest.php b/tests/Unit/OrganisationUserWorkflowTest.php index 200bdb02..a45565f9 100644 --- a/tests/Unit/OrganisationUserWorkflowTest.php +++ b/tests/Unit/OrganisationUserWorkflowTest.php @@ -22,7 +22,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -53,9 +53,9 @@ class OrganisationUserWorkflowTest extends TestCase { /** * Mock of the ObjectService * - * @var ObjectService|MockObject + * @var ObjectServiceInterface|MockObject */ - private ObjectService|MockObject $objectService; + private ObjectServiceInterface|MockObject $objectService; /** * Mock of the IUserManager service @@ -139,7 +139,7 @@ protected function setUp(): void { ); // Create mocks - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->contactPersonHandler = $this->createMock(ContactPersonHandler::class); @@ -386,7 +386,7 @@ private function createContactPerson( * @return array The result of user creation */ private function convertContactPersonToUser(array $contactPersonData): array { - // Mock the ObjectService + // Mock the ObjectServiceInterface $contactPersonObject = $this->createMockObjectEntity( uuid: $contactPersonData['uuid'], data: $contactPersonData, diff --git a/tests/Unit/SbomImportServiceTest.php b/tests/Unit/SbomImportServiceTest.php index 50b36a2b..c3cdb272 100644 --- a/tests/Unit/SbomImportServiceTest.php +++ b/tests/Unit/SbomImportServiceTest.php @@ -29,7 +29,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException; use OCA\SoftwareCatalog\Service\ProgressTracker; use OCA\SoftwareCatalog\Service\SbomImportService; @@ -50,9 +50,9 @@ class SbomImportServiceTest extends TestCase { private string $fixturesDir; /** - * @var ObjectService|MockObject + * @var ObjectServiceInterface|MockObject */ - private ObjectService|MockObject $objectService; + private ObjectServiceInterface|MockObject $objectService; /** * @var ProgressTracker|MockObject @@ -138,7 +138,7 @@ private function previousComponentEntity(string $uuid): ObjectEntity|MockObject private function makeService(array $moduleVersionData = ['version' => '1.0.0'], array $previousUuids = []): SbomImportService { $container = $this->createMock(ContainerInterface::class); $settings = $this->createMock(SettingsService::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->progressTracker = $this->createMock(ProgressTracker::class); $logger = $this->createMock(LoggerInterface::class); @@ -422,7 +422,7 @@ public function testUnsupportedFormatWritesNothing(): void { public function testModuleVersieNotFoundThrows(): void { $container = $this->createMock(ContainerInterface::class); $settings = $this->createMock(SettingsService::class); - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $progressTracker = $this->createMock(ProgressTracker::class); $logger = $this->createMock(LoggerInterface::class); diff --git a/tests/Unit/Service/AangebodenGebruikServiceTest.php b/tests/Unit/Service/AangebodenGebruikServiceTest.php index eb8df3de..f537968e 100644 --- a/tests/Unit/Service/AangebodenGebruikServiceTest.php +++ b/tests/Unit/Service/AangebodenGebruikServiceTest.php @@ -38,7 +38,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Db\Organisation; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Service\AangebodenGebruikService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -62,8 +62,8 @@ class AangebodenGebruikServiceTest extends TestCase { /** @var ContainerInterface|MockObject */ private ContainerInterface|MockObject $container; - /** @var ObjectService|MockObject */ - private ObjectService|MockObject $objectService; + /** @var ObjectServiceInterface|MockObject */ + private ObjectServiceInterface|MockObject $objectService; /** @var OrganisationService|MockObject */ private OrganisationService|MockObject $organisationService; @@ -88,7 +88,7 @@ class AangebodenGebruikServiceTest extends TestCase { private function setUpService(?string $activeOrgUuid): void { $this->appManager = $this->createMock(IAppManager::class); $this->container = $this->createMock(ContainerInterface::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->organisationService = $this->createMock(OrganisationService::class); $this->settingsService = $this->createMock(SettingsService::class); $this->userSession = $this->createMock(IUserSession::class); diff --git a/tests/Unit/Service/ContractApprovalServiceTest.php b/tests/Unit/Service/ContractApprovalServiceTest.php index 9089355e..1739ba70 100644 --- a/tests/Unit/Service/ContractApprovalServiceTest.php +++ b/tests/Unit/Service/ContractApprovalServiceTest.php @@ -26,7 +26,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ContractApprovalService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\EventDispatcher\IEventDispatcher; @@ -194,7 +194,7 @@ public function testAuthorizeSubmitOwningAanbodBeheerderIsAuthorized(): void { $this->settingsService->method('getSchemaIdForObjectType')->willReturn(3); $this->settingsService->method('getRegisterIdForObjectType')->willReturn(1); - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $entity = $this->createMock(ObjectEntity::class); $entity->method('getObject')->willReturn(['_organisation' => 'org-a']); $objectService->method('find')->willReturn($entity); @@ -215,7 +215,7 @@ public function testAuthorizeSubmitNonOwningAanbodBeheerderIsRefused(): void { $this->settingsService->method('getSchemaIdForObjectType')->willReturn(3); $this->settingsService->method('getRegisterIdForObjectType')->willReturn(1); - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $entity = $this->createMock(ObjectEntity::class); $entity->method('getObject')->willReturn(['_organisation' => 'org-a']); $objectService->method('find')->willReturn($entity); diff --git a/tests/Unit/Service/EolSyncServiceTest.php b/tests/Unit/Service/EolSyncServiceTest.php index 61bc7feb..3f198c2f 100644 --- a/tests/Unit/Service/EolSyncServiceTest.php +++ b/tests/Unit/Service/EolSyncServiceTest.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\EolMatcherService; use OCA\SoftwareCatalog\Service\EolSyncService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -129,7 +129,7 @@ public function testUnresolvableRegisterDegradesGracefully(): void { $settingsService->method('getRegisterIdForObjectType')->willReturn(1); $settingsService->method('getSchemaIdForObjectType')->willReturn(2); - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('setRegister')->willThrowException(new \RuntimeException('register not found')); $settingsService->method('getObjectService')->willReturn($objectService); @@ -158,7 +158,7 @@ public function testModuleSchemaNotConfiguredDegradesGracefully(): void { $settingsService = $this->createMock(SettingsService::class); $settingsService->method('getEolSyncConfig')->willReturn(self::DEFAULT_CONFIG); $settingsService->method('isOpenRegisterInstalled')->willReturn(true); - $settingsService->method('getObjectService')->willReturn($this->createMock(ObjectService::class)); + $settingsService->method('getObjectService')->willReturn($this->createMock(ObjectServiceInterface::class)); $settingsService->method('getRegisterIdForObjectType')->willReturn(null); $service = new EolSyncService( @@ -197,7 +197,7 @@ public function testSuccessfulRunMatchesStampsAndReportsStatus(): void { $moduleVersion = ['id' => 'mv-uuid-1', 'module' => 'module-uuid-1', 'version' => '16.2', 'beschrijvingKort' => 'keep me']; $cycle = ['product' => 'postgresql', 'cycle' => '16', 'eol' => '2028-11-09']; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturnCallback( function (array $query) use ($module, $moduleVersion): array { if (($query['@self']['schema'] ?? null) === 20) { @@ -271,7 +271,7 @@ public function testUnmappedModuleIsNeverProcessed(): void { $unmappedModule = ['id' => 'module-uuid-2', 'eolProductSlug' => '']; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturnCallback( function (array $query) use ($unmappedModule): array { if (($query['@self']['schema'] ?? null) === 20) { diff --git a/tests/Unit/Service/FacetServiceTest.php b/tests/Unit/Service/FacetServiceTest.php index 80e5561a..fa415ae3 100644 --- a/tests/Unit/Service/FacetServiceTest.php +++ b/tests/Unit/Service/FacetServiceTest.php @@ -25,7 +25,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ArchiMateService; use OCA\SoftwareCatalog\Service\FacetService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -91,7 +91,7 @@ class FacetServiceTest extends TestCase { /** * Build a FacetService with the given (mocked) collaborators. * - * @param ObjectService|null $objectService Mocked ObjectService, or null to omit from container. + * @param ObjectServiceInterface|null $objectService Mocked ObjectService, or null to omit from container. * @param SettingsService|null $settingsService Mocked SettingsService (defaults to a working voorzieningen config). * @param ArchiMateService|null $archiMateService Mocked ArchiMateService (defaults to empty lookups). * @param ICache|null $cache Mocked ICache (defaults to always-miss/no-op). @@ -100,7 +100,7 @@ class FacetServiceTest extends TestCase { * @return FacetService */ private function makeService( - ?ObjectService $objectService = null, + ?ObjectServiceInterface $objectService = null, ?SettingsService $settingsService = null, ?ArchiMateService $archiMateService = null, ?ICache $cache = null, @@ -179,10 +179,10 @@ function (string $id) use ($objectService, $organisationService) { * @param array $results Objects to return. * @param array $capturedRef Reference array; every captured query is appended. * - * @return ObjectService + * @return ObjectServiceInterface */ private function makePaginatedObjectService(array $results, array &$capturedRef): ObjectService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjectsPaginated')->willReturnCallback( function (array $query) use ($results, &$capturedRef): array { $capturedRef[] = $query; @@ -207,7 +207,7 @@ function (array $query) use ($results, &$capturedRef): array { * @return void */ public function testGetFacetsThrowsForUnsupportedSchema(): void { - $service = $this->makeService(objectService: $this->createMock(ObjectService::class)); + $service = $this->makeService(objectService: $this->createMock(ObjectServiceInterface::class)); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessageMatches('/module.*dienst|dienst.*module/'); @@ -612,8 +612,8 @@ public function testGetFacetsAppliesOrganisationScoping(): void { * @return void */ public function testCacheKeyDiffersPerUser(): void { - $serviceAlice = $this->makeService(objectService: $this->createMock(ObjectService::class), userId: 'alice'); - $serviceBob = $this->makeService(objectService: $this->createMock(ObjectService::class), userId: 'bob'); + $serviceAlice = $this->makeService(objectService: $this->createMock(ObjectServiceInterface::class), userId: 'alice'); + $serviceBob = $this->makeService(objectService: $this->createMock(ObjectServiceInterface::class), userId: 'bob'); $reflectionAlice = new \ReflectionMethod($serviceAlice, 'buildCacheKey'); $reflectionAlice->setAccessible(true); @@ -648,7 +648,7 @@ public function testGetFacetsServesFromCacheOnHit(): void { $cache = $this->createMock(ICache::class); $cache->method('get')->willReturn($cachedPayload); - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->expects($this->never())->method('searchObjectsPaginated'); $service = $this->makeService(objectService: $objectService, cache: $cache); @@ -667,7 +667,7 @@ public function testGetFacetsServesFromCacheOnHit(): void { * @return void */ public function testGetFacetsResolvesDienstFacetsTransitivelyViaModules(): void { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $capturedPaginated = []; $objectService->method('searchObjectsPaginated')->willReturnCallback( @@ -719,7 +719,7 @@ function (array $query) use (&$capturedPaginated): array { * @return void */ public function testGetFacetsResolvesDienstFacetsWhenBothLookupsReturnObjectEntityInstances(): void { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $capturedPaginated = []; $objectService->method('searchObjectsPaginated')->willReturnCallback( diff --git a/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php b/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php index 1c3de981..a40a8a4a 100644 --- a/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php +++ b/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php @@ -20,7 +20,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\GebruikService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\App\IAppManager; @@ -50,8 +50,8 @@ class GebruikServiceGetApplicationIdsTest extends TestCase { /** @var ContainerInterface|MockObject */ private ContainerInterface|MockObject $container; - /** @var ObjectService|MockObject */ - private ObjectService|MockObject $objectService; + /** @var ObjectServiceInterface|MockObject */ + private ObjectServiceInterface|MockObject $objectService; /** @var LoggerInterface|MockObject */ private LoggerInterface|MockObject $logger; @@ -68,7 +68,7 @@ protected function setUp(): void { $this->settingsService = $this->createMock(SettingsService::class); $this->appManager = $this->createMock(IAppManager::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $this->logger = $this->createMock(LoggerInterface::class); $this->container = $this->createMock(ContainerInterface::class); diff --git a/tests/Unit/Service/IntakeModerationTest.php b/tests/Unit/Service/IntakeModerationTest.php index 3aaf19db..1607125f 100644 --- a/tests/Unit/Service/IntakeModerationTest.php +++ b/tests/Unit/Service/IntakeModerationTest.php @@ -32,7 +32,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\IntakeService; use OCA\SoftwareCatalog\Service\ModerationService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -360,10 +360,10 @@ public function testBeoordeelingNonPendingCannotBeApproved(): void { * * @param array $found The search result. * - * @return ObjectService The mock. + * @return ObjectServiceInterface The mock. */ private function objectService(array $found): ObjectService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturn($found); $objectService->method('saveObject')->willReturnCallback( function (array $object) { @@ -380,10 +380,10 @@ function (array $object) { * * @param ObjectEntity $entity The entity find() returns. * - * @return ObjectService The mock. + * @return ObjectServiceInterface The mock. */ private function objectServiceWithFind(ObjectEntity $entity): ObjectService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('find')->willReturn($entity); $objectService->method('saveObject')->willReturnCallback( function (array $object) { @@ -411,11 +411,11 @@ private function entity(array $data): ObjectEntity { /** * Build a container resolving the OR ObjectService. * - * @param ObjectService $objectService The OR ObjectService. + * @param ObjectServiceInterface $objectService The OR ObjectService. * * @return ContainerInterface The container. */ - private function container(ObjectService $objectService): ContainerInterface { + private function container(ObjectServiceInterface $objectService): ContainerInterface { $container = $this->createMock(ContainerInterface::class); $container->method('get')->willReturnCallback( function (string $id) use ($objectService) { diff --git a/tests/Unit/Service/MergeOrganisatieServiceTest.php b/tests/Unit/Service/MergeOrganisatieServiceTest.php index 82d6fc77..2ea173f2 100644 --- a/tests/Unit/Service/MergeOrganisatieServiceTest.php +++ b/tests/Unit/Service/MergeOrganisatieServiceTest.php @@ -26,7 +26,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\MergeOrganisatieService; use OCA\SoftwareCatalog\Service\OrganisatieService; use OCA\SoftwareCatalog\Service\ProgressTracker; @@ -648,7 +648,7 @@ private function makeService( array $groupMembers, ?IGroupManager $groupManagerOverride = null, ): MergeOrganisatieService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('find')->willReturnCallback( function (string|int $id) use ($organisations) { diff --git a/tests/Unit/Service/PortfolioReportServiceTest.php b/tests/Unit/Service/PortfolioReportServiceTest.php index 0534ed87..6332412d 100644 --- a/tests/Unit/Service/PortfolioReportServiceTest.php +++ b/tests/Unit/Service/PortfolioReportServiceTest.php @@ -26,7 +26,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\PortfolioReportDerivation; use OCA\SoftwareCatalog\Service\PortfolioReportService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -340,7 +340,7 @@ public function testAggregateQuadrantsCountsAndSums(): void { public function testBuildReportGebruikAndContractQueriesCarryLimit(): void { $capturedQueries = []; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjectsPaginated')->willReturnCallback( function (array $query) use (&$capturedQueries): array { $capturedQueries[] = $query; @@ -417,7 +417,7 @@ function (array $query) use (&$capturedQueries): array { * @return void */ public function testBuildReportDisclosesTruncation(): void { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjectsPaginated')->willReturnCallback( function (array $query): array { $schema = $query['@self']['schema'] ?? null; diff --git a/tests/Unit/Service/PublicationServiceTest.php b/tests/Unit/Service/PublicationServiceTest.php index 31209c7e..50732802 100644 --- a/tests/Unit/Service/PublicationServiceTest.php +++ b/tests/Unit/Service/PublicationServiceTest.php @@ -25,7 +25,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\PublicationService; use OCA\SoftwareCatalog\Service\SettingsService; use PHPUnit\Framework\MockObject\MockObject; @@ -38,9 +38,9 @@ */ class PublicationServiceTest extends TestCase { /** - * @var ObjectService|MockObject + * @var ObjectServiceInterface|MockObject */ - private ObjectService|MockObject $objectService; + private ObjectServiceInterface|MockObject $objectService; /** * @var SettingsService|MockObject @@ -63,7 +63,7 @@ class PublicationServiceTest extends TestCase { private function makeService(array $entryData): PublicationService { $container = $this->createMock(ContainerInterface::class); $this->settings = $this->createMock(SettingsService::class); - $this->objectService = $this->createMock(ObjectService::class); + $this->objectService = $this->createMock(ObjectServiceInterface::class); $logger = $this->createMock(LoggerInterface::class); $this->settings->method('getSchemaIdForObjectType')->willReturn(3); diff --git a/tests/Unit/Service/QueryLimitBoundingTest.php b/tests/Unit/Service/QueryLimitBoundingTest.php index 79bbbeab..33e970e4 100644 --- a/tests/Unit/Service/QueryLimitBoundingTest.php +++ b/tests/Unit/Service/QueryLimitBoundingTest.php @@ -26,7 +26,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ArchiMateService; use OCA\SoftwareCatalog\Service\FacetService; use OCA\SoftwareCatalog\Service\OrganizationSyncService; @@ -62,7 +62,7 @@ class QueryLimitBoundingTest extends TestCase { public function testViewServiceViewIndexQueryCarriesLimit(): void { $capturedQuery = null; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturnCallback( function (array $query) use (&$capturedQuery): array { $capturedQuery = $query; @@ -108,7 +108,7 @@ function (array $query) use (&$capturedQuery): array { public function testOrganizationSyncServiceTimeWindowQueryCarriesLimit(): void { $capturedQuery = null; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturnCallback( function (array $query) use (&$capturedQuery): array { $capturedQuery = $query; @@ -152,7 +152,7 @@ function (array $query) use (&$capturedQuery): array { public function testFacetServiceBaseObjectQueryCarriesLimit(): void { $capturedQuery = null; - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjectsPaginated')->willReturnCallback( function (array $query) use (&$capturedQuery): array { $capturedQuery = $query; diff --git a/tests/Unit/Service/ReviewAggregateServiceTest.php b/tests/Unit/Service/ReviewAggregateServiceTest.php index addf4d06..f362a20b 100644 --- a/tests/Unit/Service/ReviewAggregateServiceTest.php +++ b/tests/Unit/Service/ReviewAggregateServiceTest.php @@ -23,7 +23,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ReviewAggregateService; use OCA\SoftwareCatalog\Service\SettingsService; use PHPUnit\Framework\TestCase; @@ -144,10 +144,10 @@ public function testInvalidSubjectTypeRejected(): void { * * @param array $found The search result. * - * @return ObjectService The mock. + * @return ObjectServiceInterface The mock. */ private function objectService(array $found): ObjectService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturn($found); return $objectService; }//end objectService() @@ -169,11 +169,11 @@ private function entity(array $data): ObjectEntity { /** * Build a container resolving the OR ObjectService. * - * @param ObjectService $objectService The OR ObjectService. + * @param ObjectServiceInterface $objectService The OR ObjectService. * * @return ContainerInterface The container. */ - private function container(ObjectService $objectService): ContainerInterface { + private function container(ObjectServiceInterface $objectService): ContainerInterface { $container = $this->createMock(ContainerInterface::class); $container->method('get')->willReturnCallback( function (string $id) use ($objectService) { diff --git a/tests/Unit/Service/ReviewServiceTest.php b/tests/Unit/Service/ReviewServiceTest.php index 70695080..9b6072b5 100644 --- a/tests/Unit/Service/ReviewServiceTest.php +++ b/tests/Unit/Service/ReviewServiceTest.php @@ -28,7 +28,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Db\ObjectEntity; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ReviewService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\AppFramework\Db\Entity; @@ -277,10 +277,10 @@ public function testEntityUuidReadsAMagicAccessorUuid(): void { * * @param array $found The search result. * - * @return ObjectService The mock. + * @return ObjectServiceInterface The mock. */ private function objectService(array $found): ObjectService { - $objectService = $this->createMock(ObjectService::class); + $objectService = $this->createMock(ObjectServiceInterface::class); $objectService->method('searchObjects')->willReturn($found); $objectService->method('saveObject')->willReturnCallback( function (array $object) { @@ -294,11 +294,11 @@ function (array $object) { /** * Build a container resolving the OR ObjectService. * - * @param ObjectService $objectService The OR ObjectService. + * @param ObjectServiceInterface $objectService The OR ObjectService. * * @return ContainerInterface The container. */ - private function container(ObjectService $objectService): ContainerInterface { + private function container(ObjectServiceInterface $objectService): ContainerInterface { $container = $this->createMock(ContainerInterface::class); $container->method('get')->willReturnCallback( function (string $id) use ($objectService) { From 826bf1b917520ceceae8edafeacc500734875f46 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 09:58:37 +0200 Subject: [PATCH 03/14] fix(adr-084): complete the conversion the first transformer under-did The first pass through this app used a transformer with three defects, each of which failed SILENTLY -- a skipped file is indistinguishable from a clean one. 1. It masked string literals with a REGEX. An apostrophe in a comment (`// King's Day.`) opened a string that did not close for 240 lines, so every type position between them looked quoted and the file was skipped. Now the comment and string ranges come from PHP's own token_get_all(). 2. It compared PHP's BYTE offsets against Python's CHARACTER indices. One file differed by 94 -- em dashes in prose comments -- so spans after the first non-ASCII byte were misaligned and a docblock was judged "not in a comment". Everything is handled as bytes now. 3. It missed short-form RETURN types (`): ?ObjectService`), which neither the parameter nor the fully-qualified pattern matched. And it dropped the concrete import even where `ObjectService::class` or `instanceof ObjectService` still needed it. That one is not merely incomplete, it is silent damage: `::class` does not require the class to exist, so the lookup would have resolved to this app's own namespace. An invariant check now enforces the rule -- a file may import the contract, or both, but never the contract alone while still naming bare ObjectService -- and reports 0 for every app in this rollout. --- lib/Controller/SettingsController.php | 8 ++++---- lib/Service/AanbodService.php | 2 +- lib/Service/AangebodenGebruikService.php | 2 +- lib/Service/ArchiMateImportService.php | 3 ++- lib/Service/ArchiMateService.php | 3 ++- lib/Service/ContractApprovalService.php | 5 +++-- lib/Service/ContractStatusService.php | 5 +++-- lib/Service/FacetService.php | 3 ++- lib/Service/GebruikService.php | 6 +++--- lib/Service/MergeOrganisatieService.php | 6 +++--- lib/Service/ModuleComplianceService.php | 6 +++--- lib/Service/ModuleRegistrationService.php | 6 +++--- lib/Service/ModuleVersionService.php | 6 +++--- lib/Service/OrganizationSyncService.php | 1 + lib/Service/PortfolioReportService.php | 6 +++--- lib/Service/PublicationService.php | 6 +++--- lib/Service/SbomImportService.php | 2 +- lib/Service/SoftwareCatalogue/GroupHandler.php | 6 +++--- lib/Service/ViewService.php | 5 +++-- .../ContactpersonenControllerOrganisationScopeTest.php | 3 ++- .../ContactpersonenControllerUpdateUserGroupsTest.php | 1 + tests/Unit/OrganisationUserWorkflowTest.php | 1 + tests/Unit/SbomImportServiceTest.php | 1 + tests/Unit/Service/AangebodenGebruikServiceTest.php | 1 + tests/Unit/Service/FacetServiceTest.php | 1 + .../Unit/Service/GebruikServiceGetApplicationIdsTest.php | 1 + tests/Unit/Service/IntakeModerationTest.php | 1 + tests/Unit/Service/PublicationServiceTest.php | 1 + tests/Unit/Service/QueryLimitBoundingTest.php | 1 + tests/Unit/Service/ReviewAggregateServiceTest.php | 1 + tests/Unit/Service/ReviewServiceTest.php | 1 + 31 files changed, 60 insertions(+), 41 deletions(-) diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 7b767283..8e6ece97 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -25,7 +25,7 @@ namespace OCA\SoftwareCatalog\Controller; use OCA\OpenRegister\Service\ConfigurationService; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\ArchiMateService; use OCA\SoftwareCatalog\Service\EolSyncService; use OCA\SoftwareCatalog\Service\OrganizationSyncService; @@ -64,7 +64,7 @@ class SettingsController extends Controller { /** * The OpenRegister object service. * - * @var ObjectService|null The OpenRegister object service. + * @var ObjectServiceInterface|null The OpenRegister object service. */ private $objectService; @@ -109,11 +109,11 @@ public function __construct( /** * Attempts to retrieve the OpenRegister service from the container. * - * @return ObjectService|null The OpenRegister service if available, null otherwise. + * @return ObjectServiceInterface|null The OpenRegister service if available, null otherwise. * @throws RuntimeException If the service is not available. * @spec openspec/specs/settings-admin-controller/spec.md */ - public function getObjectService(): ?ObjectService { + public function getObjectService(): ?ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === true) { $this->objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); return $this->objectService; diff --git a/lib/Service/AanbodService.php b/lib/Service/AanbodService.php index 26a59dce..51ebac09 100644 --- a/lib/Service/AanbodService.php +++ b/lib/Service/AanbodService.php @@ -685,7 +685,7 @@ private function getCurrentOrganisation(): ?string { * * @throws Exception When OpenRegister service is not available */ - private function getObjectService(): ObjectService { + private function getObjectService(): ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === false) { throw new Exception('OpenRegister app is not installed'); } diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php index 770e5201..881964de 100644 --- a/lib/Service/AangebodenGebruikService.php +++ b/lib/Service/AangebodenGebruikService.php @@ -1138,7 +1138,7 @@ private function getCurrentOrganisation(): ?string { * @return ObjectServiceInterface The OpenRegister object service * @throws Exception When OpenRegister service is not available */ - private function getObjectService(): ObjectService { + private function getObjectService(): ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === false) { throw new Exception('OpenRegister app is not installed'); } diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index 1bbed3ee..bf2c44a7 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -21,6 +21,7 @@ namespace OCA\SoftwareCatalog\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\OpenRegister\Service\OrganisationService; use OCP\App\IAppManager; use OCP\Files\IRootFolder; @@ -1814,7 +1815,7 @@ private function saveObjectsInSingleBatch(array $objects, ObjectServiceInterface * * @return ObjectServiceInterface|null ObjectService instance or null if not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { if ($this->appManager->isInstalled(appId: 'openregister') === false) { return null; } diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index 05d0f0f2..8e29f63b 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -22,6 +22,7 @@ namespace OCA\SoftwareCatalog\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCP\App\IAppManager; use OCP\Files\IRootFolder; use OCP\IAppConfig; @@ -1283,7 +1284,7 @@ private function saveObjectsInSingleBatch(array $objects, ObjectServiceInterface * * @return ObjectServiceInterface|null ObjectService instance or null if not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { if ($this->appManager->isInstalled(appId: 'openregister') === false) { return null; } diff --git a/lib/Service/ContractApprovalService.php b/lib/Service/ContractApprovalService.php index 4719bae8..abdcf081 100644 --- a/lib/Service/ContractApprovalService.php +++ b/lib/Service/ContractApprovalService.php @@ -37,6 +37,7 @@ namespace OCA\SoftwareCatalog\Service; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; use OCP\EventDispatcher\IEventDispatcher; use Psr\Container\ContainerInterface; @@ -565,11 +566,11 @@ private function persistContract($contract, array $data): void { /** * Lazily resolve the OpenRegister ObjectService. * - * @return ObjectService|null The service, or null when OpenRegister is absent. + * @return ObjectServiceInterface|null The service, or null when OpenRegister is absent. * * @spec openspec/specs/contract-decision-delegation/spec.md */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { $service = $this->container->get('OCA\OpenRegister\Service\ObjectService'); if ($service instanceof ObjectService) { diff --git a/lib/Service/ContractStatusService.php b/lib/Service/ContractStatusService.php index d0daa1b9..e712cbd1 100644 --- a/lib/Service/ContractStatusService.php +++ b/lib/Service/ContractStatusService.php @@ -27,6 +27,7 @@ namespace OCA\SoftwareCatalog\Service; use DateTimeImmutable; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -182,11 +183,11 @@ public function expirePastContracts(?DateTimeImmutable $now = null): int { /** * Lazily resolve the OpenRegister ObjectService. * - * @return ObjectService|null The service, or null when OpenRegister is absent. + * @return ObjectServiceInterface|null The service, or null when OpenRegister is absent. * * @spec openspec/specs/contract-administration/spec.md */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { $service = $this->container->get('OCA\OpenRegister\Service\ObjectService'); if ($service instanceof ObjectService) { diff --git a/lib/Service/FacetService.php b/lib/Service/FacetService.php index 3db32f58..607b484e 100644 --- a/lib/Service/FacetService.php +++ b/lib/Service/FacetService.php @@ -27,6 +27,7 @@ use InvalidArgumentException; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCP\ICache; use OCP\ICacheFactory; use OCP\IUserSession; @@ -1103,7 +1104,7 @@ private function getCurrentOrganisation(): ?string { * * @return ObjectServiceInterface|null ObjectService instance or null if not available. */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get(ObjectService::class); } catch (\Exception $e) { diff --git a/lib/Service/GebruikService.php b/lib/Service/GebruikService.php index 21909b8e..24cb777b 100644 --- a/lib/Service/GebruikService.php +++ b/lib/Service/GebruikService.php @@ -17,7 +17,7 @@ namespace OCA\SoftwareCatalog\Service; use Exception; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -101,11 +101,11 @@ private function getGebruiksConfiguration(): array { /** * Get ObjectService from OpenRegister app. * - * @return ObjectService The OpenRegister object service. + * @return ObjectServiceInterface The OpenRegister object service. * * @throws Exception When OpenRegister service is not available. */ - private function getObjectService(): ObjectService { + private function getObjectService(): ObjectServiceInterface { if (in_array('openregister', $this->appManager->getInstalledApps()) === false) { throw new Exception('OpenRegister app is not installed'); } diff --git a/lib/Service/MergeOrganisatieService.php b/lib/Service/MergeOrganisatieService.php index 930b02d0..a7555966 100644 --- a/lib/Service/MergeOrganisatieService.php +++ b/lib/Service/MergeOrganisatieService.php @@ -62,7 +62,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; use OCP\App\IAppManager; use OCP\EventDispatcher\IEventDispatcher; @@ -806,9 +806,9 @@ private function findAllForType(string $objectType): array { /** * Gets the OpenRegister ObjectService if available. * - * @return ObjectService|null ObjectService instance or null when openregister is not installed. + * @return ObjectServiceInterface|null ObjectService instance or null when openregister is not installed. */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === false) { return null; } diff --git a/lib/Service/ModuleComplianceService.php b/lib/Service/ModuleComplianceService.php index 5697f1bd..dfb181c8 100644 --- a/lib/Service/ModuleComplianceService.php +++ b/lib/Service/ModuleComplianceService.php @@ -21,7 +21,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -846,9 +846,9 @@ public function bulkSyncModuleStandards(): array { /** * Get the object service * - * @return ObjectService|null The object service or null if not available + * @return ObjectServiceInterface|null The object service or null if not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } catch (\Exception $e) { diff --git a/lib/Service/ModuleRegistrationService.php b/lib/Service/ModuleRegistrationService.php index a5725a72..667f53c1 100644 --- a/lib/Service/ModuleRegistrationService.php +++ b/lib/Service/ModuleRegistrationService.php @@ -21,7 +21,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -273,9 +273,9 @@ private function updateModuleRegisteredBy(object $moduleObject, string $register /** * Get the object service from the DI container. * - * @return ObjectService|null The object service or null if not available + * @return ObjectServiceInterface|null The object service or null if not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } catch (\Exception $e) { diff --git a/lib/Service/ModuleVersionService.php b/lib/Service/ModuleVersionService.php index b601c4dc..7345937e 100644 --- a/lib/Service/ModuleVersionService.php +++ b/lib/Service/ModuleVersionService.php @@ -22,7 +22,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -238,9 +238,9 @@ private function updateVersionRecord(array $context): void { /** * Get the object service from the DI container. * - * @return ObjectService|null The object service or null if not available + * @return ObjectServiceInterface|null The object service or null if not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } catch (\Exception $e) { diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 19c42d99..09cab7b6 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -21,6 +21,7 @@ namespace OCA\SoftwareCatalog\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; use OCP\IAppConfig; use OCP\IDBConnection; diff --git a/lib/Service/PortfolioReportService.php b/lib/Service/PortfolioReportService.php index f6ae73c3..ecb768cf 100644 --- a/lib/Service/PortfolioReportService.php +++ b/lib/Service/PortfolioReportService.php @@ -34,7 +34,7 @@ use DateTimeImmutable; use Exception; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\AppInfo\Application; use OCP\App\IAppManager; use OCP\IAppConfig; @@ -558,11 +558,11 @@ private function getPageSizeCeiling(): int { /** * Lazily resolve the OpenRegister ObjectService. * - * @return ObjectService The service. + * @return ObjectServiceInterface The service. * * @throws Exception When OpenRegister is not installed or unresolvable. */ - private function getObjectService(): ObjectService { + private function getObjectService(): ObjectServiceInterface { if (in_array('openregister', $this->appManager->getInstalledApps(), true) === false) { throw new Exception('OpenRegister app is not installed'); } diff --git a/lib/Service/PublicationService.php b/lib/Service/PublicationService.php index af866bd5..9026a91c 100644 --- a/lib/Service/PublicationService.php +++ b/lib/Service/PublicationService.php @@ -34,7 +34,7 @@ namespace OCA\SoftwareCatalog\Service; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -288,9 +288,9 @@ private function now(): string { /** * Get the OpenRegister ObjectService from the DI container. * - * @return ObjectService|null The object service, or null when OR is absent. + * @return ObjectServiceInterface|null The object service, or null when OR is absent. */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } catch (\Throwable $e) { diff --git a/lib/Service/SbomImportService.php b/lib/Service/SbomImportService.php index c21ac98b..3f14196d 100644 --- a/lib/Service/SbomImportService.php +++ b/lib/Service/SbomImportService.php @@ -611,7 +611,7 @@ private function resolveCoordinates(): array { * * @return ObjectServiceInterface|null The object service, or null if not available. */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { try { return $this->container->get('OCA\OpenRegister\Service\ObjectService'); } catch (\Exception $e) { diff --git a/lib/Service/SoftwareCatalogue/GroupHandler.php b/lib/Service/SoftwareCatalogue/GroupHandler.php index af3491f3..d426c07e 100644 --- a/lib/Service/SoftwareCatalogue/GroupHandler.php +++ b/lib/Service/SoftwareCatalogue/GroupHandler.php @@ -21,7 +21,7 @@ namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; -use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCP\App\IAppManager; use OCP\IAppConfig; use OCP\IGroup; @@ -82,11 +82,11 @@ public function __construct( /** * Gets the OpenRegister ObjectService if available * - * @return ObjectService|null ObjectService instance or null + * @return ObjectServiceInterface|null ObjectService instance or null * * @throws RuntimeException If service is not available */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { if (in_array(needle: 'openregister', haystack: $this->_appManager->getInstalledApps()) === true) { return $this->_container->get('OCA\OpenRegister\Service\ObjectService'); } diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 63d92f29..c578e253 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -21,6 +21,7 @@ namespace OCA\SoftwareCatalog\Service; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; use OCP\App\IAppManager; use OCP\IAppConfig; @@ -1398,9 +1399,9 @@ private function getNodeDeelnamesGebruik(string $modelNodeId, array $deelnamesGe /** * Get ObjectService from container. * - * @return ObjectService|null ObjectService instance or null if not available. + * @return ObjectServiceInterface|null ObjectService instance or null if not available. */ - private function getObjectService(): ?ObjectService { + private function getObjectService(): ?ObjectServiceInterface { if ($this->appManager->isInstalled('openregister') === false) { return null; } diff --git a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php index ca0a937d..7f3f6693 100644 --- a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php @@ -28,6 +28,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -74,7 +75,7 @@ class ContactpersonenControllerOrganisationScopeTest extends TestCase { /** @var ContainerInterface|MockObject */ private ContainerInterface|MockObject $container; - /** @var ObjectService|MockObject */ + /** @var ObjectServiceInterface|MockObject */ private ObjectServiceInterface|MockObject $objectService; /** @var ContactpersoonService|MockObject */ diff --git a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php index dd32ef4c..0d34b314 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php @@ -21,6 +21,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; diff --git a/tests/Unit/OrganisationUserWorkflowTest.php b/tests/Unit/OrganisationUserWorkflowTest.php index a45565f9..d4aac920 100644 --- a/tests/Unit/OrganisationUserWorkflowTest.php +++ b/tests/Unit/OrganisationUserWorkflowTest.php @@ -23,6 +23,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; diff --git a/tests/Unit/SbomImportServiceTest.php b/tests/Unit/SbomImportServiceTest.php index c3cdb272..eb827415 100644 --- a/tests/Unit/SbomImportServiceTest.php +++ b/tests/Unit/SbomImportServiceTest.php @@ -30,6 +30,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Exception\UnsupportedSbomFormatException; use OCA\SoftwareCatalog\Service\ProgressTracker; use OCA\SoftwareCatalog\Service\SbomImportService; diff --git a/tests/Unit/Service/AangebodenGebruikServiceTest.php b/tests/Unit/Service/AangebodenGebruikServiceTest.php index f537968e..63205ff0 100644 --- a/tests/Unit/Service/AangebodenGebruikServiceTest.php +++ b/tests/Unit/Service/AangebodenGebruikServiceTest.php @@ -39,6 +39,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Db\Organisation; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Service\AangebodenGebruikService; use OCA\SoftwareCatalog\Service\SettingsService; diff --git a/tests/Unit/Service/FacetServiceTest.php b/tests/Unit/Service/FacetServiceTest.php index fa415ae3..9ef78d71 100644 --- a/tests/Unit/Service/FacetServiceTest.php +++ b/tests/Unit/Service/FacetServiceTest.php @@ -26,6 +26,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\ArchiMateService; use OCA\SoftwareCatalog\Service\FacetService; use OCA\SoftwareCatalog\Service\SettingsService; diff --git a/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php b/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php index a40a8a4a..1f1ff614 100644 --- a/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php +++ b/tests/Unit/Service/GebruikServiceGetApplicationIdsTest.php @@ -21,6 +21,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\GebruikService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\App\IAppManager; diff --git a/tests/Unit/Service/IntakeModerationTest.php b/tests/Unit/Service/IntakeModerationTest.php index 1607125f..88356e28 100644 --- a/tests/Unit/Service/IntakeModerationTest.php +++ b/tests/Unit/Service/IntakeModerationTest.php @@ -33,6 +33,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\IntakeService; use OCA\SoftwareCatalog\Service\ModerationService; use OCA\SoftwareCatalog\Service\SettingsService; diff --git a/tests/Unit/Service/PublicationServiceTest.php b/tests/Unit/Service/PublicationServiceTest.php index 50732802..b7cf46af 100644 --- a/tests/Unit/Service/PublicationServiceTest.php +++ b/tests/Unit/Service/PublicationServiceTest.php @@ -26,6 +26,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\PublicationService; use OCA\SoftwareCatalog\Service\SettingsService; use PHPUnit\Framework\MockObject\MockObject; diff --git a/tests/Unit/Service/QueryLimitBoundingTest.php b/tests/Unit/Service/QueryLimitBoundingTest.php index 33e970e4..fcd37053 100644 --- a/tests/Unit/Service/QueryLimitBoundingTest.php +++ b/tests/Unit/Service/QueryLimitBoundingTest.php @@ -27,6 +27,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\ArchiMateService; use OCA\SoftwareCatalog\Service\FacetService; use OCA\SoftwareCatalog\Service\OrganizationSyncService; diff --git a/tests/Unit/Service/ReviewAggregateServiceTest.php b/tests/Unit/Service/ReviewAggregateServiceTest.php index f362a20b..9527fee6 100644 --- a/tests/Unit/Service/ReviewAggregateServiceTest.php +++ b/tests/Unit/Service/ReviewAggregateServiceTest.php @@ -24,6 +24,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\ReviewAggregateService; use OCA\SoftwareCatalog\Service\SettingsService; use PHPUnit\Framework\TestCase; diff --git a/tests/Unit/Service/ReviewServiceTest.php b/tests/Unit/Service/ReviewServiceTest.php index 9b6072b5..e2228ce6 100644 --- a/tests/Unit/Service/ReviewServiceTest.php +++ b/tests/Unit/Service/ReviewServiceTest.php @@ -29,6 +29,7 @@ use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\ReviewService; use OCA\SoftwareCatalog\Service\SettingsService; use OCP\AppFramework\Db\Entity; From 2020f87a13b7575c06b1a54b1944b1cfe6b04843 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 18:21:46 +0200 Subject: [PATCH 04/14] test: pass the ObjectServiceInterface the constructors now require MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-083 added a constructor parameter; the test constructions still passed the old argument count: ArgumentCountError: Too few arguments to __construct(), N passed and exactly N+1 expected Each site gains one argument BY NAME, which fills the right slot whether the preceding arguments were written positionally or by name — so the same edit works for both shapes, and a call short by more than this one parameter still errors, correctly. Every touched file is re-parsed with php -l and reverted on failure, and a re-scan reports 0 remaining sites in each app. --- .../ContactpersonenControllerOrganisationScopeTest.php | 3 ++- .../ContactpersonenControllerUpdateUserGroupsTest.php | 3 ++- .../ContactpersonenControllerUserAdminContractTest.php | 4 +++- .../UserProfileUpdatedEventListenerDecompositionTest.php | 5 ++++- tests/Unit/OrganisationUserWorkflowTest.php | 3 ++- tests/Unit/Service/GebruikSyncServiceDecompositionTest.php | 2 ++ 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php index 7f3f6693..f1042352 100644 --- a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php @@ -118,7 +118,8 @@ protected function setUp(): void { $this->userSession, $this->container, $this->createMock(ISecureRandom::class), - $this->logger + $this->logger, + objectService: $this->createMock(ObjectServiceInterface::class), ); }//end setUp() diff --git a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php index 0d34b314..37032f4b 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php @@ -101,7 +101,8 @@ protected function setUp(): void { $this->userSession, $this->container, $this->createMock(ISecureRandom::class), - $this->logger + $this->logger, + objectService: $this->createMock(ObjectServiceInterface::class), ); }//end setUp() diff --git a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php index 1bc4d5c8..1359aae2 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php @@ -30,6 +30,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -114,7 +115,8 @@ private function makeController(): ContactpersonenController { $this->userSession, $this->container, $this->createMock(ISecureRandom::class), - $this->createMock(LoggerInterface::class) + $this->createMock(LoggerInterface::class), + objectService: $this->createMock(ObjectServiceInterface::class), ); }//end makeController() diff --git a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php index f8809d7a..02f9b686 100644 --- a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php +++ b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php @@ -21,6 +21,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\EventListener; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; use OCA\SoftwareCatalog\EventListener\UserProfileUpdatedEventListener; use PHPUnit\Framework\TestCase; @@ -49,7 +50,9 @@ private function makeListener(): UserProfileUpdatedEventListener { $this->markTestSkipped('OCA\\OpenRegister\\Event\\UserProfileUpdatedEvent is not autoloadable in this environment.'); } - return new UserProfileUpdatedEventListener($this->createMock(ContainerInterface::class)); + return new UserProfileUpdatedEventListener($this->createMock(ContainerInterface::class), + objectService: $this->createMock(ObjectServiceInterface::class), + ); }//end makeListener() /** diff --git a/tests/Unit/OrganisationUserWorkflowTest.php b/tests/Unit/OrganisationUserWorkflowTest.php index d4aac920..0a95be73 100644 --- a/tests/Unit/OrganisationUserWorkflowTest.php +++ b/tests/Unit/OrganisationUserWorkflowTest.php @@ -160,7 +160,8 @@ protected function setUp(): void { $this->createMock(IUserSession::class), $this->createMock(ContainerInterface::class), $this->createMock(ISecureRandom::class), - $this->logger + $this->logger, + objectService: $this->createMock(ObjectServiceInterface::class), ); } diff --git a/tests/Unit/Service/GebruikSyncServiceDecompositionTest.php b/tests/Unit/Service/GebruikSyncServiceDecompositionTest.php index b7b3fa86..f0bf6611 100644 --- a/tests/Unit/Service/GebruikSyncServiceDecompositionTest.php +++ b/tests/Unit/Service/GebruikSyncServiceDecompositionTest.php @@ -20,6 +20,7 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Service; +use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\SoftwareCatalog\Service\GebruikSyncService; use OCA\SoftwareCatalog\Service\SettingsService; use PHPUnit\Framework\TestCase; @@ -47,6 +48,7 @@ private function makeService(): GebruikSyncService { new NullLogger(), $this->createMock(SettingsService::class), $this->createMock(ContainerInterface::class), + objectService: $this->createMock(ObjectServiceInterface::class), ); }//end makeService() From 153e8503c8657e9e0331bbd0859d11022ae77161 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 18:30:48 +0200 Subject: [PATCH 05/14] fix: the contract lives in Contract\, not Service\ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phpstan caught a defect in the rollout transformer: PHPDoc tag @var for property $objectService with type OCA\OpenRegister\Service\ObjectServiceInterface is not subtype of native type OCA\OpenRegister\Contract\ObjectServiceInterface The docblock rewrite matched `@var \OCA\OpenRegister\Service\ObjectService` and appended `Interface` to the CLASS name while leaving the NAMESPACE alone, so the declared type named a class that does not exist. The native type next to it was correct, which is why only phpstan noticed — PHP itself never reads the docblock, and the tests pass either way. That is the fifth silent failure from this transformer, and the same shape as the others: it produced plausible output that no runtime check disagreed with. --- lib/Service/ArchiMateExportService.php | 6 +++--- lib/Service/ContactpersoonService.php | 2 +- lib/Service/SettingsService.php | 2 +- lib/Service/SoftwareCatalogue/ContactPersonHandler.php | 2 +- lib/Service/SoftwareCatalogue/OrganizationHandler.php | 4 ++-- lib/Service/SoftwareCatalogueService.php | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index a08a3280..e6f3d63d 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -796,7 +796,7 @@ private function addObjectToFolder(\SimpleXMLElement $folder, array $object, str * requires both register AND schema in the query. Without schema, the query * falls back to the generic objects table (which is empty for magic-table registers). * - * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService OpenRegister ObjectService. + * @param \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService OpenRegister ObjectService. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Mapping of schema IDs to schema types. * @@ -940,7 +940,7 @@ public function addPropertyDefinitionsToXml(\SimpleXMLElement $xml, array $prope * 3. Direct XML generation without intermediate arrays * 4. No JSON serialization overhead * - * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService OpenRegister ObjectService. + * @param \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService OpenRegister ObjectService. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Schema IDs to types mapping. * @param string|null $organization Organization filter. @@ -2384,7 +2384,7 @@ private function validateTextContentNormalized(\SimpleXMLElement $xml): void { * referentiecomponenten, copies views with applications plotted inside, and * adds SWC-specific organization folders. * - * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService The object service. + * @param \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService The object service. * @param int $registerId AMEF register ID. * @param array $schemaIdMap Schema ID to type map. * @param string $orgName Organization name. diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index 7c794199..0c5e3ed5 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -659,7 +659,7 @@ private function handleRoleChanges(object $newContactPersonObject, object $oldCo /** * Gets the ObjectService instance * - * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null + * @return \OCA\OpenRegister\Contract\ObjectServiceInterface|null */ private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if ($this->appManager->isEnabledForUser('openregister') === false) { diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index 94f7f85c..da791c3a 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -162,7 +162,7 @@ public function isOpenRegisterEnabled(): bool { /** * Attempts to retrieve the OpenRegister service from the container * - * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null The OpenRegister service if available + * @return \OCA\OpenRegister\Contract\ObjectServiceInterface|null The OpenRegister service if available * * @throws \RuntimeException If the service is not available * @spec openspec/specs/settings-service/spec.md diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index 84236f7a..ac482751 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -87,7 +87,7 @@ public function __construct( /** * Gets the OpenRegister ObjectService if available * - * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null ObjectService instance or null + * @return \OCA\OpenRegister\Contract\ObjectServiceInterface|null ObjectService instance or null * @throws \RuntimeException If service is not available */ private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/SoftwareCatalogue/OrganizationHandler.php index 2e4ecb79..b11cdf4d 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/SoftwareCatalogue/OrganizationHandler.php @@ -75,7 +75,7 @@ public function __construct( /** * Gets the OpenRegister ObjectService if available. * - * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null ObjectService instance or null + * @return \OCA\OpenRegister\Contract\ObjectServiceInterface|null ObjectService instance or null * * @throws \RuntimeException If service is not available */ @@ -477,7 +477,7 @@ public function processContactpersonen(object $organizationObject): array { * * @param string $email The email address to search for * @param string $organizationUuid The organization UUID - * @param \OCA\OpenRegister\Service\ObjectServiceInterface $objectService The object service + * @param \OCA\OpenRegister\Contract\ObjectServiceInterface $objectService The object service * @param int $registerId The register ID * @param int $contactgegevensSchemaId The contactgegevens schema ID * diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 585031c2..112587b7 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -107,7 +107,7 @@ public function __construct( /** * Gets the ObjectService instance * - * @return \OCA\OpenRegister\Service\ObjectServiceInterface|null + * @return \OCA\OpenRegister\Contract\ObjectServiceInterface|null */ private function getObjectService(): ?\OCA\OpenRegister\Contract\ObjectServiceInterface { if ($this->_appManager->isEnabledForUser(appId: 'openregister') === false) { From 5e7ae8da1536bc088ca0d3472c02fc8cee977a85 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 19:07:26 +0200 Subject: [PATCH 06/14] =?UTF-8?q?ci:=20adopt=20development's=20Code=20Qual?= =?UTF-8?q?ity=20workflow=20=E2=80=94=20the=20branch=20had=20the=20pre-fix?= =?UTF-8?q?=20one?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These branches were cut from ADR-083 branches predating the 2026-08-14 correction, so they carry an older code-quality.yml whose push trigger has no refactor/** — which is this branch's prefix. development's version says why that matters: An ALLOW-LIST of branch prefixes is a gate with a hole in it, and the hole is SILENT: a branch matching nothing gets no CI at all, and its last visible status is whatever it inherited — indistinguishable, on every dashboard, from a branch that passed. Observed here: softwarecatalog#519 settled at FOUR checks (CodeQL and Analyze only) and read as green, having previously reported 43. shillinq#556 did the same at three, which is shillinq#557. Takes merge-hygiene.yml with it, the companion added in the same change, which runs the fast structural checks on ** so an unlisted prefix is not completely unguarded. This restores coverage via the PUSH path. It does not explain why the pull_request runs stopped, which is tracked separately. --- .github/workflows/code-quality.yml | 31 +++++++- .github/workflows/merge-hygiene.yml | 111 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/merge-hygiene.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index fc884ecd..878d0952 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -27,7 +27,36 @@ on: # `enable-coverage-guard` was switched on here in the previous commit; without # this trigger its push-side half would have been dead on arrival. push: - branches: [main, beta, development, feature/**, bugfix/**, hotfix/**] + # An ALLOW-LIST of branch prefixes is a gate with a hole in it, and the + # hole is SILENT: a branch matching nothing gets no CI at all, and its last + # visible status is whatever it inherited — indistinguishable, on every + # dashboard, from a branch that passed. + # + # Two live examples, both found 2026-08-14: `perf/**` was uncovered in + # openconnector, where a merge carrying unresolved conflict markers and 84 + # failing tests was pushed and nothing ran; and `feat/**` was uncovered in + # openregister — note the list said `feature/**`, so every branch anyone + # named `feat/...` had been running unchecked. + # + # Prefixes are added rather than replaced with `**` because this workflow is + # expensive (PHPUnit matrix, Newman, Playwright). The fast structural checks + # DO run on `**` — see merge-hygiene.yml, added in the same change. + # + # ⚠️ Adding prefixes is not the durable fix; the next invented one is + # uncovered again. The durable fix is branch protection requiring a PR into + # development, which the pull_request trigger below already gates correctly. + branches: + - main + - beta + - development + - feature/** + - feat/** + - bugfix/** + - hotfix/** + - perf/** + - refactor/** + - chore/** + - fix/** pull_request: branches: [main, beta, development] # Same family of defect as the missing `push:` above, one step further along: diff --git a/.github/workflows/merge-hygiene.yml b/.github/workflows/merge-hygiene.yml new file mode 100644 index 00000000..4852cee5 --- /dev/null +++ b/.github/workflows/merge-hygiene.yml @@ -0,0 +1,111 @@ +name: Merge Hygiene + +# WHY THIS EXISTS, and why it is separate from Code Quality. +# +# On 2026-08-14 a merge of origin/development was committed and PUSHED to +# `perf/predicted-page-fanout` with UNRESOLVED CONFLICT MARKERS in two files. +# `lib/Service/SynchronizationService.php` did not parse. Eighty-four tests were +# red. Nothing stopped it, and nothing reported it — because Code Quality's push +# trigger allows only `[main, development, feature/**, bugfix/**, hotfix/**]`, +# and `perf/**` matches none of them. The branch had no CI at all, so its last +# visible state was green from before the branch existed. +# +# The lesson is not "add perf/** to the list" — that fixes this branch and leaves +# the next prefix uncovered. Any branch anyone pushes should get at least the +# checks that take seconds, so this runs on `**` and stays deliberately cheap: +# no matrix, no containers, no dependencies, no Playwright. It is a smoke alarm, +# not the fire brigade. Code Quality remains the real gate on PRs. +on: + push: + branches: ['**'] + pull_request: + workflow_dispatch: + +concurrency: + group: merge-hygiene-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + hygiene: + name: Conflict markers and PHP syntax + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Conflict markers, anywhere in the tree we author. A marker means a merge + # was committed half-finished; every downstream signal from that commit is + # meaningless, so this fails first and says so plainly. + # + # Anchored to line start: `<<<<<<<` inside a string, a diff fixture or a + # docs example is legitimate and must not fail the build. Matching only at + # column 0 is what git itself writes. + - name: No unresolved conflict markers + run: | + set -euo pipefail + # SCOPED TO CODE, and to paths we author. A marker is only a defect + # where it would break something: prose that DOCUMENTS a conflict is + # legitimate, and so are agent-eval artifacts that capture one as + # sample output. openbuild failed this gate on + # `.claude/skills/create-pr/evals/.../summary.md` — a correct file. + # + # That matters more than the miss it allows. A gate that fails on + # correct files gets switched off, and takes the checks that were + # working with it; a marker in a markdown file breaks nothing. + if git grep -nE '^(<{7}|={7}|>{7})( |$)' -- \ + '*.php' '*.js' '*.mjs' '*.ts' '*.vue' '*.json' '*.yml' '*.yaml' '*.css' '*.scss' \ + ':!vendor' ':!node_modules' ':!*.lock' ':!tests/fixtures' ':!.claude' \ + ':!**/evals/**' ':!**/fixtures/**' > /tmp/markers.txt; then + echo "::error::Unresolved merge conflict markers are committed. This branch does not build." + cat /tmp/markers.txt + exit 1 + fi + echo "No conflict markers." + + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + # Every PHP file parses. A conflict marker is caught above, but so is any + # other way a file can be committed unparseable — and this is the check + # that would have failed within seconds of the merge landing. + - name: PHP syntax + run: | + set -euo pipefail + fail=0 + while IFS= read -r f; do + php -l "$f" > /dev/null 2>&1 || { echo "::error file=$f::PHP syntax error"; php -l "$f" || true; fail=1; } + done < <(git ls-files '*.php' | grep -v '^vendor/' | grep -v '^tests/fixtures/') + exit "$fail" + + # JSON that will not parse breaks register fragments and app metadata, + # and is the other thing a bad merge leaves behind. + # + # SCOPED TWICE, because each widening found another honest file. The + # first version parsed every tracked .json and died on tsconfig/eslint + # JSONC. The second still reached `lib/**/*.json`, which in openbuild + # includes an entire app TEMPLATE — `.vscode/settings.json` and all. + # A template is not this app's configuration, and an editor file is not + # loaded by anything. What is left is what OpenRegister actually reads. + # + # SCOPED, because the first version was not and failed immediately on + # honest files: editor and tooling configs (tsconfig, eslint, devcontainer) + # are JSONC — comments and trailing commas — which is valid for their + # consumers and invalid for a strict parser. A gate that fails on correct + # files is worse than no gate: it gets switched off, and takes the checks + # that were working with it. Only the JSON the app itself loads is checked. + - name: JSON parses + run: | + set -euo pipefail + fail=0 + while IFS= read -r f; do + [ -f "$f" ] || continue + python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$f" \ + || { echo "::error file=$f::invalid JSON"; fail=1; } + done < <(git ls-files 'composer.json' 'package.json' 'appinfo/*.json' 'lib/Settings/**/*.json' \ + | grep -v '^vendor/' | grep -v '^node_modules/' \ + | grep -v '/\.vscode/' | grep -v '^lib/Resources/template/') + exit "$fail" From 3e166129a8a518ea6ada7bdb07ffce89c1a6861a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 21:28:40 +0200 Subject: [PATCH 07/14] test: complete the ADR-083 constructor changes and make the stubs satisfy the contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three distinct pre-existing defects, all of which only became visible once the tests could actually construct their subjects. 1. ARITY, completely this time. The first pass added `objectService` only, because that is the parameter ADR-084 was about. ADR-083 added others in the same commit — softwarecatalog's ContactpersonenController gained THREE (objectService, magicMapper, organisationService) — so a call could be fixed for one and still be short by two. Every required parameter is now supplied, by NAME so it fills the right slot regardless of the existing arguments. 2. IMPORTS for the types those arguments name. `createMock(MagicMapper::class)` without a `use` resolves the short name against the TEST's own namespace, and `::class` does not require the class to exist — so it silently mocks a class nobody declared. Same trap as the container-key strings in lib/. 3. The ObjectEntity STUB now implements ObjectEntityInterface. Once ObjectServiceInterface is mocked its return types are enforced: Method find may not return value of type MockObject_ObjectEntity, its declared return type is "?OCA\OpenRegister\Contract\ObjectEntityInterface" A hand-rolled double that does not declare the interface cannot be handed back. This is ADR-084's argument arriving in the tests: ten apps had such a double, and none of them was checked against anything until now. softwarecatalog also gains a MagicMapper stub, because ADR-083 injected OpenRegister's MagicMapper into a controller and this app has no way to load it. That stub is debt of exactly the kind ADR-084 removed for ObjectService — noted in the file so it stays visible rather than becoming furniture. php -l on every touched file, reverted on failure. --- tests/OrganizationSyncTest.php | 5 ++- tests/Stubs/Db/MagicMapper.php | 41 +++++++++++++++++++ tests/Stubs/Db/ObjectEntity.php | 16 +++++++- ...ersonenControllerOrganisationScopeTest.php | 4 ++ ...personenControllerUpdateUserGroupsTest.php | 4 ++ ...ersonenControllerUserAdminContractTest.php | 4 ++ ...eUpdatedEventListenerDecompositionTest.php | 6 +++ tests/Unit/OrganisationUserWorkflowTest.php | 4 ++ 8 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/Stubs/Db/MagicMapper.php diff --git a/tests/OrganizationSyncTest.php b/tests/OrganizationSyncTest.php index 85bc001f..48a02de3 100644 --- a/tests/OrganizationSyncTest.php +++ b/tests/OrganizationSyncTest.php @@ -93,7 +93,10 @@ public function testOrganizationCreationSync(): void { $this->createMock(\OCA\SoftwareCatalog\Service\SymfonyEmailService::class), $this->createMock(\Psr\Log\LoggerInterface::class), $this->createMock(\Psr\Container\ContainerInterface::class), - $this->createMock(\OCP\App\IAppManager::class) + $this->createMock(\OCP\App\IAppManager::class), + _userSession: $this->createMock(IUserSession::class), + _userManager: $this->createMock(IUserManager::class), + _groupManager: $this->createMock(IGroupManager::class), ); // Test synchronization diff --git a/tests/Stubs/Db/MagicMapper.php b/tests/Stubs/Db/MagicMapper.php new file mode 100644 index 00000000..0c033c79 --- /dev/null +++ b/tests/Stubs/Db/MagicMapper.php @@ -0,0 +1,41 @@ +organisation ?? null; + } + + /** + * @return ?string + */ + public function getOwner(): ?string { + return $this->owner ?? null; + } + /** * The system-level owning organisation (`@self.organisation`). diff --git a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php index f1042352..379a0e5b 100644 --- a/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerOrganisationScopeTest.php @@ -26,9 +26,11 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; +use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -120,6 +122,8 @@ protected function setUp(): void { $this->createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), + magicMapper: $this->createMock(MagicMapper::class), + organisationService: $this->createMock(OrganisationService::class), ); }//end setUp() diff --git a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php index 37032f4b..e3a302f0 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php @@ -19,9 +19,11 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; +use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -103,6 +105,8 @@ protected function setUp(): void { $this->createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), + magicMapper: $this->createMock(MagicMapper::class), + organisationService: $this->createMock(OrganisationService::class), ); }//end setUp() diff --git a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php index 1359aae2..fc7ac6d5 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php @@ -31,6 +31,8 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Db\MagicMapper; +use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -117,6 +119,8 @@ private function makeController(): ContactpersonenController { $this->createMock(ISecureRandom::class), $this->createMock(LoggerInterface::class), objectService: $this->createMock(ObjectServiceInterface::class), + magicMapper: $this->createMock(MagicMapper::class), + organisationService: $this->createMock(OrganisationService::class), ); }//end makeController() diff --git a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php index 02f9b686..c9bf75bc 100644 --- a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php +++ b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php @@ -22,6 +22,8 @@ namespace OCA\SoftwareCatalog\Tests\Unit\EventListener; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Db\MagicMapper; +use OCA\OpenRegister\Db\SchemaMapper; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; use OCA\SoftwareCatalog\EventListener\UserProfileUpdatedEventListener; use PHPUnit\Framework\TestCase; @@ -52,6 +54,10 @@ private function makeListener(): UserProfileUpdatedEventListener { return new UserProfileUpdatedEventListener($this->createMock(ContainerInterface::class), objectService: $this->createMock(ObjectServiceInterface::class), + schemaMapper: $this->createMock(SchemaMapper::class), + registerMapper: $this->createMock(RegisterMapper::class), + metadataHydrationHandler: $this->createMock(MetadataHydrationHandler::class), + magicMapper: $this->createMock(MagicMapper::class), ); }//end makeListener() diff --git a/tests/Unit/OrganisationUserWorkflowTest.php b/tests/Unit/OrganisationUserWorkflowTest.php index 0a95be73..7da3438f 100644 --- a/tests/Unit/OrganisationUserWorkflowTest.php +++ b/tests/Unit/OrganisationUserWorkflowTest.php @@ -23,7 +23,9 @@ namespace OCA\SoftwareCatalog\Tests\Unit; use OCA\OpenRegister\Contract\ObjectServiceInterface; +use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Service\ObjectService; +use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; use OCA\SoftwareCatalog\Service\SettingsService; @@ -162,6 +164,8 @@ protected function setUp(): void { $this->createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), + magicMapper: $this->createMock(MagicMapper::class), + organisationService: $this->createMock(OrganisationService::class), ); } From 4619549f22fb64fafce65bbed6e34ac7bc1b3225 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 21:36:54 +0200 Subject: [PATCH 08/14] refactor: persist through the published contract, not OpenRegister's Db layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-083 injected OCA\OpenRegister\Db\MagicMapper into three classes here. That is another app's DATABASE layer — the coupling ADR-022 exists to prevent — and no leaf app can load it, so its tests could not construct their own subjects. I had added a hand-rolled MagicMapper stub to get past that; this removes the need for one instead. All three sites were doing the same thing: save without schema validation. The published contract already exposes that, and saveObject() is not a lesser route — OpenRegister's own SaveObject calls metaHydrationHandler->hydrateObjectMetadata(entity:, schema:) objectEntityMapper->update(entity:, register:, schema:) which IS the magic-mapper route, with the metadata hydration these callers were performing by hand. The comment claiming a plain save touches "just the blob table" was wrong; the code was reimplementing OpenRegister's save pipeline one layer too deep. The flags matter, and one of them nearly went missing. FIX #434 chose MagicMapper for TWO reasons, not one: to avoid validation errors on the organisatie field (stored as UUID string but Schema expects object type) AND to avoid triggering ObjectUpdatedEvent cascades. That could interfere with the ongoing org activation process. `_validation: false` covers the first. The second needs `silent: true`, and a replacement carrying only the validation flag would have re-emitted those events into an in-flight org activation — a behaviour change with no test to catch it. Both flags are now passed at every site. Left alone: ContactpersoonService's two `container->get(MagicMapper)` lookups. They are lazy, not injected, so they neither block tests nor need a stub; they carry the same FIX #434 reasoning and are worth converting on their own terms. --- lib/Controller/ContactpersonenController.php | 23 ++++++++--- .../UserProfileUpdatedEventListener.php | 20 ++++++--- lib/Service/OrganizationSyncService.php | 14 +++++-- tests/Stubs/Db/MagicMapper.php | 41 ------------------- ...ersonenControllerOrganisationScopeTest.php | 2 - ...personenControllerUpdateUserGroupsTest.php | 2 - ...ersonenControllerUserAdminContractTest.php | 2 - ...eUpdatedEventListenerDecompositionTest.php | 2 - tests/Unit/OrganisationUserWorkflowTest.php | 2 - 9 files changed, 44 insertions(+), 64 deletions(-) delete mode 100644 tests/Stubs/Db/MagicMapper.php diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index 0bb3f754..b297846d 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -36,7 +36,6 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Service\OrganisationService; /** @@ -156,7 +155,6 @@ public function __construct( ISecureRandom $secureRandom, LoggerInterface $logger, private readonly ObjectServiceInterface $objectService, - private readonly MagicMapper $magicMapper, private readonly OrganisationService $organisationService, ) { parent::__construct(appName: $appName, request: $request); @@ -534,9 +532,24 @@ public function convertToUser(string $contactPersonId): JSONResponse { ] ); - // Save using MagicMapper directly to bypass schema validation. - // This avoids "Unresolved reference" errors when schema references can't be resolved. - $this->magicMapper->update($contactPersonObject); + // Save WITHOUT schema validation, to avoid "Unresolved reference" + // errors when schema references cannot be resolved. + // + // This used to reach into OpenRegister's MagicMapper directly — its Db + // layer — which ADR-022 exists to prevent and which no leaf app can + // load in its own tests. The published contract already exposes the + // same capability as a flag, and ObjectService::saveObject() routes + // through that very mapper with register+schema, so the magic table is + // written exactly as before. + $this->objectService->saveObject( + object: $contactData, + register: $registerId, + schema: $schemaId, + uuid: $contactPersonObject->getUuid(), + silent: true, + silent: true, + _validation: false + ); $this->logger->info( 'ContactpersonenController: Updated contactpersoon with username', diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index 772a7d23..1b2568cb 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -29,7 +29,6 @@ use OCA\OpenRegister\Db\SchemaMapper; use OCA\OpenRegister\Db\RegisterMapper; use OCA\OpenRegister\Service\Object\SaveObject\MetadataHydrationHandler; -use OCA\OpenRegister\Db\MagicMapper; /** * Syncs user profile changes to the corresponding contactpersoon object. @@ -65,7 +64,6 @@ public function __construct( private readonly SchemaMapper $schemaMapper, private readonly RegisterMapper $registerMapper, private readonly MetadataHydrationHandler $metadataHydrationHandler, - private readonly MagicMapper $magicMapper, ) { }//end __construct() @@ -316,9 +314,21 @@ private function persistContactPersonPatch( ); } - // Pass register and schema so the magic mapper route is triggered and the - // per-schema magic table is updated (not just the blob table). - $this->magicMapper->update(entity: $contactPerson, register: $registerEntity, schema: $schemaEntity); + // Persist through the published contract rather than OpenRegister's Db + // layer. The comment this replaces worried that a plain save would touch + // "just the blob table" — it does not: ObjectService::saveObject() calls + // metaHydrationHandler->hydrateObjectMetadata() and then + // objectEntityMapper->update(entity:, register:, schema:), which IS the + // magic-mapper route. This listener was hand-rolling OpenRegister's own + // save pipeline, one layer too deep. + $this->objectService->saveObject( + object: $contactPerson->getObject(), + register: $contactPerson->getRegister(), + schema: $contactPerson->getSchema(), + uuid: $contactPerson->getUuid(), + silent: true, + _validation: false + ); }//end persistContactpersoonPatch() diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 09cab7b6..3e238290 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -28,7 +28,6 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use OCA\OpenRegister\Db\OrganisationMapper; -use OCA\OpenRegister\Db\MagicMapper; /** * Service for synchronizing organizations and contact persons. @@ -135,7 +134,6 @@ public function __construct( ContainerInterface $container, private readonly ObjectServiceInterface $objectService, private readonly OrganisationMapper $organisationMapper, - private readonly MagicMapper $magicMapper, ) { $this->organisationService = $organisationService; $this->contactPersonService = $contactPersonService; @@ -2154,7 +2152,17 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz if (empty($contactData['organisatie']) === true) { $contactData['organisatie'] = $organizationUuid; $contactObject->setObject($contactData); - $this->magicMapper->update($contactObject); + // Published contract instead of OpenRegister's Db layer; see + // ContactpersonenController for the reasoning. + $this->objectService->saveObject( + object: $contactData, + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + silent: true, + _validation: false + ); $this->logger->info( '[FLOW] Set missing organisatie field on related contact', [ diff --git a/tests/Stubs/Db/MagicMapper.php b/tests/Stubs/Db/MagicMapper.php deleted file mode 100644 index 0c033c79..00000000 --- a/tests/Stubs/Db/MagicMapper.php +++ /dev/null @@ -1,41 +0,0 @@ -createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), - magicMapper: $this->createMock(MagicMapper::class), organisationService: $this->createMock(OrganisationService::class), ); diff --git a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php index e3a302f0..28068f0e 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUpdateUserGroupsTest.php @@ -19,7 +19,6 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; -use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Contract\ObjectServiceInterface; use OCA\OpenRegister\Service\ObjectService; @@ -105,7 +104,6 @@ protected function setUp(): void { $this->createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), - magicMapper: $this->createMock(MagicMapper::class), organisationService: $this->createMock(OrganisationService::class), ); diff --git a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php index fc7ac6d5..6ebc59cf 100644 --- a/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php +++ b/tests/Unit/Controller/ContactpersonenControllerUserAdminContractTest.php @@ -31,7 +31,6 @@ namespace OCA\SoftwareCatalog\Tests\Unit\Controller; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; use OCA\SoftwareCatalog\Service\ContactpersoonService; @@ -119,7 +118,6 @@ private function makeController(): ContactpersonenController { $this->createMock(ISecureRandom::class), $this->createMock(LoggerInterface::class), objectService: $this->createMock(ObjectServiceInterface::class), - magicMapper: $this->createMock(MagicMapper::class), organisationService: $this->createMock(OrganisationService::class), ); diff --git a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php index c9bf75bc..0c00e089 100644 --- a/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php +++ b/tests/Unit/EventListener/UserProfileUpdatedEventListenerDecompositionTest.php @@ -22,7 +22,6 @@ namespace OCA\SoftwareCatalog\Tests\Unit\EventListener; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Db\SchemaMapper; use OCA\OpenRegister\Event\UserProfileUpdatedEvent; use OCA\SoftwareCatalog\EventListener\UserProfileUpdatedEventListener; @@ -57,7 +56,6 @@ private function makeListener(): UserProfileUpdatedEventListener { schemaMapper: $this->createMock(SchemaMapper::class), registerMapper: $this->createMock(RegisterMapper::class), metadataHydrationHandler: $this->createMock(MetadataHydrationHandler::class), - magicMapper: $this->createMock(MagicMapper::class), ); }//end makeListener() diff --git a/tests/Unit/OrganisationUserWorkflowTest.php b/tests/Unit/OrganisationUserWorkflowTest.php index 7da3438f..d5d8b6e7 100644 --- a/tests/Unit/OrganisationUserWorkflowTest.php +++ b/tests/Unit/OrganisationUserWorkflowTest.php @@ -23,7 +23,6 @@ namespace OCA\SoftwareCatalog\Tests\Unit; use OCA\OpenRegister\Contract\ObjectServiceInterface; -use OCA\OpenRegister\Db\MagicMapper; use OCA\OpenRegister\Service\ObjectService; use OCA\OpenRegister\Service\OrganisationService; use OCA\SoftwareCatalog\Controller\ContactpersonenController; @@ -164,7 +163,6 @@ protected function setUp(): void { $this->createMock(ISecureRandom::class), $this->logger, objectService: $this->createMock(ObjectServiceInterface::class), - magicMapper: $this->createMock(MagicMapper::class), organisationService: $this->createMock(OrganisationService::class), ); } From 701ad8b78e59877283ba5fc263f972129edc99e3 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 21:40:40 +0200 Subject: [PATCH 09/14] refactor: convert the last two MagicMapper lookups on their own terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit left these deliberately: they are lazy container lookups rather than injected dependencies, so they blocked no test and needed no stub. They carry the same FIX #434 reasoning, and converting them needed reading what each actually does rather than pattern-matching the first one. Site 1 changes only the payload (username), so it is the same shape as the three already converted: _validation: false, silent: true. Site 2 is different, and this is why they were worth separating. It sets two pieces of entity METADATA that the payload API expresses differently: organisation travels in `@self`. SaveObject reads it and applies it via setOrganisation() BEHIND AN ACCESS CHECK — so an organisation the caller may not use is now refused rather than written. The direct mapper call bypassed that check entirely, which is a quiet authorisation improvement, not just a refactor. owner is not settable from the payload at all; SaveObject derives it from the acting user, so the UID is resolved to an IUser and passed as `currentUser`. Both keep _validation: false and silent: true, so neither the organisatie type mismatch nor the ObjectUpdatedEvent cascade behaviour changes. lib/ now has no code reference to OCA\OpenRegister\Db\MagicMapper. What remains is prose in RenameDutchCatalogColumns explaining how OpenRegister's schema sync adds columns, which is documentation of the other app's behaviour and correct where it is. --- lib/Service/ContactpersoonService.php | 61 ++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index 0c5e3ed5..8c661bba 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -458,12 +458,28 @@ private function updateContactPersonUsername(object $contactPersonObject, string $contactData['username'] = $username; $contactPersonObject->setObject($contactData); - // FIX #434: Use MagicMapper directly instead of ObjectService::saveObject(). - // To avoid validation errors on the organisatie field (stored as UUID string but. - // Schema expects object type) and to avoid triggering ObjectUpdatedEvent cascades. - // That could interfere with the ongoing org activation process. - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactPersonObject); + // FIX #434, through the PUBLISHED contract instead of OpenRegister's Db + // layer. Both reasons the original gave are flags on saveObject(): + // + // _validation: false the organisatie field holds a UUID string where + // the schema expects an object + // silent: true no ObjectUpdatedEvent, so the cascade cannot + // interfere with an in-flight org activation + // + // saveObject() is not a lesser route: OpenRegister's SaveObject calls + // objectEntityMapper->update(entity:, register:, schema:) itself, which + // IS the magic-mapper path this used to reach for directly. + $objectService = $this->getObjectService(); + if ($objectService !== null) { + $objectService->saveObject( + object: $contactData, + register: $contactPersonObject->getRegister(), + schema: $contactPersonObject->getSchema(), + uuid: $contactPersonObject->getUuid(), + silent: true, + _validation: false + ); + } $this->logger->info( 'ContactpersoonService: Updated contactpersoon with username', @@ -1197,11 +1213,34 @@ private function updateContactPersonObjectOwner(object $contactObject, string $u $contactObject->setOrganisation($organizationUuid); } - // FIX #434: Use MagicMapper directly instead of ObjectService::saveObject(). - // To avoid validation errors on the organisatie field (stored as UUID string but. - // Schema expects object type) and to avoid triggering ObjectUpdatedEvent cascades. - $objectMapper = $this->container->get('OCA\OpenRegister\Db\MagicMapper'); - $objectMapper->update($contactObject); + // FIX #434, through the PUBLISHED contract. Same two flags as the other + // site (_validation: false, silent: true), plus the two pieces of + // entity METADATA this call exists to set, which the payload API + // expresses differently: + // + // organisation travels in `@self`, which SaveObject reads and applies + // via setOrganisation() — behind an access check, so an + // organisation the caller may not use is refused rather + // than written, which the direct mapper call bypassed + // owner is NOT settable from the payload; SaveObject derives it + // from the acting user, so it is passed as `currentUser` + $objectService = $this->getObjectService(); + $userManager = $this->container->get('OCP\IUserManager'); + $actingUser = $userManager->get($userUID); + if ($objectService !== null && $actingUser !== null) { + $payload = $contactObject->getObject(); + $payload['@self'] = ['organisation' => $organizationUuid]; + + $objectService->saveObject( + object: $payload, + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + _validation: false, + currentUser: $actingUser + ); + } $this->logger->info( 'ContactpersoonService: Successfully updated contactpersoon object owner and organisation', From 684384e472b2e9b8c9a67e15730f653e17a98a7c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 22:08:52 +0200 Subject: [PATCH 10/14] fix: finish the MagicMapper removal and wire the composition root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phpstan caught what the first pass missed. Access to an undefined property OrganizationSyncService::$magicMapper Five more `$this->magicMapper->update()` calls remained in that file; the first pass converted one. All five now save through the published contract, carrying the organisation in `@self` where the code had set it on the entity — which SaveObject applies behind an access check the direct mapper call skipped. Argument for parameter $silent has already been passed My own "add silent: true" pass matched a second time inside one already-edited block and produced a duplicate at broken indentation. Removed. TooFewArguments ... expecting objectService / organisationMapper to be passed The composition root builds OrganizationSyncService and GebruikSyncService BY HAND, so ADR-083's new constructor parameters never reached them. psalm found this; PHPUnit could not, because the tests construct these services directly and never go through Application.php. A registration that cannot build its service fails at runtime on first use, which is the failure mode ADR-083 rule 3 is about. Both now resolve ObjectServiceInterface through the alias registered in the same file, so the composition root consumes its own binding rather than naming the concrete class again. --- lib/AppInfo/Application.php | 7 ++- lib/Service/OrganizationSyncService.php | 68 ++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index cbc604d9..451075d9 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -350,6 +350,10 @@ function ($container) { db: $container->get(IDBConnection::class), contactpersonHandler: $container->get(ContactPersonHandler::class), container: $container, + // ADR-084: the published contract, resolved through the alias + // registered above, not the concrete OpenRegister class. + objectService: $container->get(ObjectServiceInterface::class), + organisationMapper: $container->get('OCA\OpenRegister\Db\OrganisationMapper'), ); } ); @@ -373,7 +377,8 @@ function ($container) { return new GebruikSyncService( logger: $container->get('Psr\Log\LoggerInterface'), settingsService: $container->get(SettingsService::class), - container: $container + container: $container, + objectService: $container->get(ObjectServiceInterface::class), ); } ); diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 3e238290..cf22366d 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -2160,8 +2160,7 @@ private function processRelatedContactPersons(string $organizationUuid, $organiz schema: $contactObject->getSchema(), uuid: $contactObject->getUuid(), silent: true, - silent: true, - _validation: false + _validation: false ); $this->logger->info( '[FLOW] Set missing organisatie field on related contact', @@ -2322,7 +2321,18 @@ private function createOrUpdateContactPersonObject( $restoredData = $contactObject->getObject(); $restoredData['organisatie'] = $savedOrganisation; $contactObject->setObject($restoredData); - $this->magicMapper->update($contactObject); + // Published contract instead of OpenRegister's Db layer. + $this->objectService->saveObject( + object: array_merge( + $contactObject->getObject(), + ['@self' => ['organisation' => $contactObject->getOrganisation()]] + ), + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + _validation: false + ); } }//end if @@ -2335,7 +2345,18 @@ private function createOrUpdateContactPersonObject( $contactObjectData['organisatie'] = $organizationUuid; $contactObject->setObject($contactObjectData); $contactObject->setOrganisation($organizationUuid); - $this->magicMapper->update($contactObject); + // Published contract instead of OpenRegister's Db layer. + $this->objectService->saveObject( + object: array_merge( + $contactObject->getObject(), + ['@self' => ['organisation' => $contactObject->getOrganisation()]] + ), + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + _validation: false + ); $this->logger->info( '[FLOW] Set missing organisatie field on contact person', [ @@ -2452,7 +2473,18 @@ private function createOrUpdateContactPersonObject( ); try { - $this->magicMapper->update($contactObject); + // Published contract instead of OpenRegister's Db layer. + $this->objectService->saveObject( + object: array_merge( + $contactObject->getObject(), + ['@self' => ['organisation' => $contactObject->getOrganisation()]] + ), + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + _validation: false + ); $this->logger->info( 'Contact saved with username', [ @@ -3175,7 +3207,18 @@ private function updateOrganisationObjectOwner( $organisationObject->setOrganisation($organisationEntityUuid); // Save using MagicMapper directly to bypass validation and ensure metadata is persisted. - $this->magicMapper->update($organisationObject); + // Published contract instead of OpenRegister's Db layer. + $this->objectService->saveObject( + object: array_merge( + $organisationObject->getObject(), + ['@self' => ['organisation' => $organisationObject->getOrganisation()]] + ), + register: $organisationObject->getRegister(), + schema: $organisationObject->getSchema(), + uuid: $organisationObject->getUuid(), + silent: true, + _validation: false + ); $this->logger->info( 'OrganizationSyncService: Successfully updated organisatie object owner and organisation', @@ -3289,7 +3332,18 @@ private function updateContactPersonObjectOwner( } // Save using MagicMapper directly to bypass validation and ensure metadata is persisted. - $this->magicMapper->update($contactObject); + // Published contract instead of OpenRegister's Db layer. + $this->objectService->saveObject( + object: array_merge( + $contactObject->getObject(), + ['@self' => ['organisation' => $contactObject->getOrganisation()]] + ), + register: $contactObject->getRegister(), + schema: $contactObject->getSchema(), + uuid: $contactObject->getUuid(), + silent: true, + _validation: false + ); $this->logger->info( 'OrganizationSyncService: Successfully updated contactpersoon object owner and organisation', From cee771e6228da1904fe6cc579cb7d06a59140c1a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 22:12:03 +0200 Subject: [PATCH 11/14] fix: repair references to the local $objectService ADR-083 deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phpmd, on this branch: UndefinedVariable $objectService ADR-083 replaced $objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); with an injected property and rewrote most usages to $this->objectService — but not all. What remained read a local that no longer exists. In PHP that is not a parse error and not a test failure unless the line executes; it is null at runtime, and the call it feeds gets null instead of the service. Rewritten only where the enclosing function has no assignment to that local AND does not take it as a PARAMETER — several helpers legitimately receive it, e.g. CreditLimitGuard::sumOutstandingCents(object $objectService, ...), and those are untouched. php -l on every touched file, reverted on failure. --- lib/Controller/ContactpersonenController.php | 8 ++++---- lib/EventListener/UserProfileUpdatedEventListener.php | 4 ++-- lib/Service/OrganizationSyncService.php | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index b297846d..c45b2928 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -311,7 +311,7 @@ private function checkOrganisationReadPermission(\OCP\IUser $currentUser, string $callerOrgUuid = null; try { - $callerOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $currentUser->getUID()); + $callerOrgUuid = $this->resolveContactOrganisation(objectService: $this->objectService, username: $currentUser->getUID()); } catch (\Exception $e) { $this->logger->warning( 'ContactpersonenController: could not resolve the caller organisation, denying contact read', @@ -957,8 +957,8 @@ private function checkGroupUpdatePermission(\OCP\IUser $currentUser, string $use */ private function verifyCrossTenantScope(\OCP\IUser $currentUser, string $username): ?JSONResponse { try { - $targetOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $username); - $callerOrgUuid = $this->resolveContactOrganisation(objectService: $objectService, username: $currentUser->getUID()); + $targetOrgUuid = $this->resolveContactOrganisation(objectService: $this->objectService, username: $username); + $callerOrgUuid = $this->resolveContactOrganisation(objectService: $this->objectService, username: $currentUser->getUID()); if ($targetOrgUuid !== null && $callerOrgUuid !== null && $targetOrgUuid !== $callerOrgUuid) { $this->logger->warning( @@ -993,7 +993,7 @@ private function verifyCrossTenantScope(\OCP\IUser $currentUser, string $usernam * made a nested reference compare unequal to a plain UUID, which both this * method's callers treat as "different tenant" (GH#459). * - * @param object $objectService The OpenRegister ObjectService. + * @param object $this->objectService The OpenRegister ObjectService. * @param string $username The username to look up. * * @return string|null The organisation UUID or null when not found. diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index 1b2568cb..01d7ab81 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -156,7 +156,7 @@ private function syncToContactPerson(UserProfileUpdatedEvent $event, LoggerInter ]; $contactPerson = $this->findContactPerson( - objectService: $objectService, + objectService: $this->objectService, selfQuery: $selfQuery, userId: $userId, event: $event, @@ -335,7 +335,7 @@ private function persistContactPersonPatch( /** * Find a contactpersoon by username, falling back to a case-insensitive email search. * - * @param object $objectService The OpenRegister ObjectService. + * @param object $this->objectService The OpenRegister ObjectService. * @param array $selfQuery The @self register/schema filter. * @param string $userId The Nextcloud user ID. * @param UserProfileUpdatedEvent $event The profile updated event. diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index cf22366d..4dae0253 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -384,7 +384,7 @@ public function performOrganizationsSync(int $batchSize = 50, int $maxExecutionS $rows = $qb->executeQuery()->fetchAll(); - if ($objectService instanceof ObjectService === false) { + if ($this->objectService instanceof ObjectService === false) { $this->logger->error('OrganizationSync: could not resolve ObjectService'); return $stats; } @@ -508,7 +508,7 @@ public function performContactSync(int $batchSize = 100, int $maxExecutionSecond $this->logger->info('ContactSync: processing ' . count($contacts) . ' contacts with existing NC accounts'); - if ($objectService instanceof ObjectService === false) { + if ($this->objectService instanceof ObjectService === false) { $this->logger->error('ContactSync: could not resolve ObjectService'); return $stats; } From 4f6da6658af8424b004668ff01dcdec4846e09b0 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 22:34:33 +0200 Subject: [PATCH 12/14] fix(tests): drop constructor arguments belonging to a same-named class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several classes here share a FILE NAME with another in a different namespace — lib/Service/ChecklistService.php and lib/Service/Inspection/ChecklistService.php, BelplanRoutingService, HearingService and others. The arity fixer keyed constructors by that file name, so some constructions received arguments from the wrong class: Error: Unknown named parameter $settingsService Classes are now resolved through the file's own `use` imports, and the pass refuses to act unless it is certain: a constructor that exists but parses to nothing is treated as a PARSE FAILURE and skipped, never as "takes no arguments". It also reads only DEPTH-0 named arguments. A nested construction has its own constructor: new ProcestToolProvider( caseReader: new ProcestCaseReader( settingsService: $settingsService, <-- the INNER call's parameter logger: $logger, Judging those against the outer constructor is what made the first attempt delete valid arguments; that attempt was reverted, and this is its replacement. --- lib/Controller/ContactpersonenController.php | 2 +- lib/EventListener/UserProfileUpdatedEventListener.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Controller/ContactpersonenController.php b/lib/Controller/ContactpersonenController.php index c45b2928..2823a44d 100644 --- a/lib/Controller/ContactpersonenController.php +++ b/lib/Controller/ContactpersonenController.php @@ -993,7 +993,7 @@ private function verifyCrossTenantScope(\OCP\IUser $currentUser, string $usernam * made a nested reference compare unequal to a plain UUID, which both this * method's callers treat as "different tenant" (GH#459). * - * @param object $this->objectService The OpenRegister ObjectService. + * @param object $objectService The OpenRegister ObjectService. * @param string $username The username to look up. * * @return string|null The organisation UUID or null when not found. diff --git a/lib/EventListener/UserProfileUpdatedEventListener.php b/lib/EventListener/UserProfileUpdatedEventListener.php index 01d7ab81..3f8116c4 100644 --- a/lib/EventListener/UserProfileUpdatedEventListener.php +++ b/lib/EventListener/UserProfileUpdatedEventListener.php @@ -335,7 +335,7 @@ private function persistContactPersonPatch( /** * Find a contactpersoon by username, falling back to a case-insensitive email search. * - * @param object $this->objectService The OpenRegister ObjectService. + * @param object $objectService The OpenRegister ObjectService. * @param array $selfQuery The @self register/schema filter. * @param string $userId The Nextcloud user ID. * @param UserProfileUpdatedEvent $event The profile updated event. From bc36d9c005d6b1cd8dec8d7bf91c62f52d31e573 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 15 Aug 2026 23:20:45 +0200 Subject: [PATCH 13/14] fix: clear the docblocks and dead code ADR-083 left behind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phpstan, and by volume this was the bulk of what remained: 138x PHPDoc tag @param references unknown parameter: $container (shillinq) 62x (pipelinq) 37x (decidesk) ADR-083 removed the ContainerInterface parameter from the classes it converted and left the @param line above it. Removed only where the documented signature genuinely has no $container — classes still using the availability-guarded lookup keep both, verified on three of them (signature present, docblock intact). Also: Dead catch - Throwable is never thrown in the try block getObjectService() became a property read, which throws nothing. Expression "$this->objectService" on a separate line does not do anything the old `$objectService = $container->get(...)` line survived as a bare expression after its right-hand side was removed. Not touched here: `is_array()` on an ObjectEntityInterface, which phpstan says always evaluates to false. That is 45 sites in shillinq alone and the correct fix differs per branch — the array arm is dead now that find() returns an entity, but what the surviving arm should read is a per-site question. --- lib/Controller/OrganisationMembersController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Controller/OrganisationMembersController.php b/lib/Controller/OrganisationMembersController.php index 3cfa94d3..6e368c29 100644 --- a/lib/Controller/OrganisationMembersController.php +++ b/lib/Controller/OrganisationMembersController.php @@ -77,7 +77,6 @@ class OrganisationMembersController extends Controller { * @param IUserSession $userSession The user session (auth guard). * @param IGroupManager $groupManager Group membership (beheerder guard). * @param IUserManager $userManager User lookup (existing-user-only guard). - * @param ContainerInterface $container DI container, used to reach OpenRegister's * `OrganisationService` without a hard compile-time * dependency on another app's class. * @param LoggerInterface $logger Logger. From 9ecc2fa2c85786794d277d19bc1d1d46acc890f8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 16 Aug 2026 02:29:09 +0200 Subject: [PATCH 14/14] Put register and schema back inside findAll()'s config (2 sites) findAll() is (array $config, bool $_rbac, bool $_multitenancy). These two calls passed the register id and schema id positionally, so they landed on the two booleans: the query ran unscoped across every register, with $_rbac set to a register id and $_multitenancy to a schema id -- both truthy, so nothing failed loudly and the wrong rows came back. The sibling call 180 lines above already does it correctly, with register and schema inside 'filters'. Matched that shape. Verified the contract's signature against the real ObjectService on openregister@development before changing the callers: they agree exactly, so the callers were the wrong side. Pre-existing -- the untyped container lookup meant no analyser could compare them. --- lib/Service/SoftwareCatalogueService.php | 30 +++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 50139b95..e4249246 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -2028,10 +2028,19 @@ private function activateUsersForOrganization(string $organizationUuid): void { $registerId = $ctx['registerId']; $contactPersonSchemaId = $ctx['schemaId']; + // findAll() takes ($config, bool $_rbac, bool $_multitenancy). The + // register and schema belong inside the config's filters, as the + // sibling call above does -- passed positionally they landed on the + // two booleans, so this ran unscoped across every register with + // $_rbac set to a register id. $contactpersonen = $objectService->findAll( - ['organisation' => $organizationUuid], - $registerId, - $contactPersonSchemaId + [ + 'filters' => [ + 'register' => $registerId, + 'schema' => $contactPersonSchemaId, + 'organisation' => $organizationUuid, + ], + ] ); $userManager = $this->_container->get(\OCP\IUserManager::class); @@ -2111,10 +2120,19 @@ private function deactivateUsersForOrganization(string $organizationUuid): void $registerId = $ctx['registerId']; $contactPersonSchemaId = $ctx['schemaId']; + // findAll() takes ($config, bool $_rbac, bool $_multitenancy). The + // register and schema belong inside the config's filters, as the + // sibling call above does -- passed positionally they landed on the + // two booleans, so this ran unscoped across every register with + // $_rbac set to a register id. $contactpersonen = $objectService->findAll( - ['organisation' => $organizationUuid], - $registerId, - $contactPersonSchemaId + [ + 'filters' => [ + 'register' => $registerId, + 'schema' => $contactPersonSchemaId, + 'organisation' => $organizationUuid, + ], + ] ); $userManager = $this->_container->get(\OCP\IUserManager::class);