From 0766e9dc8f64e865bfcfd0b6a2575f966dbd7691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 25 Aug 2026 08:26:55 +0200 Subject: [PATCH] refactor(repository): make entity deletion idempotent --- .../Security/Ports/Repositories/ClientRepository.php | 3 --- src/Domain/Ports/Repositories/BrandRepository.php | 3 --- .../Adapters/Repositories/ApiClientRepository.php | 6 +----- .../Adapters/Repositories/EloquentBrandRepository.php | 7 +------ .../Adapters/Repositories/InMemoryBrandRepository.php | 5 ----- .../Adapters/Repositories/InMemoryClientRepository.php | 5 ----- .../Adapters/Repositories/ApiClientRepositoryTest.php | 10 ++++++---- .../Repositories/EloquentBrandRepositoryTest.php | 10 ++++++---- .../Repositories/InMemoryBrandRepositoryTest.php | 10 ++++++---- .../Repositories/InMemoryClientRepositoryTest.php | 10 ++++++---- 10 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/Application/Security/Ports/Repositories/ClientRepository.php b/src/Application/Security/Ports/Repositories/ClientRepository.php index 687ebeb..4e1c642 100644 --- a/src/Application/Security/Ports/Repositories/ClientRepository.php +++ b/src/Application/Security/Ports/Repositories/ClientRepository.php @@ -31,8 +31,5 @@ interface ClientRepository extends Repository public function persist(Client $client): void; - /** - * @throws \Application\Security\Exceptions\ClientNotFoundException - */ public function delete(Client $client): void; } diff --git a/src/Domain/Ports/Repositories/BrandRepository.php b/src/Domain/Ports/Repositories/BrandRepository.php index c1c3ff2..2e33ecf 100644 --- a/src/Domain/Ports/Repositories/BrandRepository.php +++ b/src/Domain/Ports/Repositories/BrandRepository.php @@ -24,8 +24,5 @@ interface BrandRepository extends Repository public function persist(Brand $entity): void; - /** - * @throws \Domain\Exceptions\BrandNotFoundException - */ public function delete(Brand $entity): void; } diff --git a/src/Infra/Adapters/Repositories/ApiClientRepository.php b/src/Infra/Adapters/Repositories/ApiClientRepository.php index f01aadf..538d26b 100644 --- a/src/Infra/Adapters/Repositories/ApiClientRepository.php +++ b/src/Infra/Adapters/Repositories/ApiClientRepository.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Infra\Adapters\Repositories; -use Application\Security\Exceptions\ClientNotFoundException; use Application\Security\Models\Entities\Client; use Application\Security\Models\ValueObjects\ClientIdentifier; use Application\Security\Models\ValueObjects\ClientName; @@ -113,10 +112,7 @@ final class ApiClientRepository implements ClientRepository #[\Override] public function delete(Client $client): void { - $deleted = ApiClient::query()->where('id', $client->id->value)->delete(); - if ($deleted === 0) { - throw ClientNotFoundException::forClient($client->id); - } + ApiClient::query()->where('id', $client->id->value)->delete(); } private function toDomain(ApiClient $apiClient): Client diff --git a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php index 8c4be6f..8bd2ddf 100644 --- a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php +++ b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Infra\Adapters\Repositories; -use Domain\Exceptions\BrandNotFoundException; use Domain\Models\Entities\Brand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandCode; @@ -124,14 +123,10 @@ final readonly class EloquentBrandRepository implements BrandRepository #[\Override] public function delete(Brand $entity): void { - $deleted = BrandModel::query() + BrandModel::query() ->where('id', $entity->id()->value) ->delete(); - if ($deleted === 0) { - throw BrandNotFoundException::forBrand($entity->id()); - } - $this->brandCache->invalidate($entity); } diff --git a/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php b/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php index 3956b65..ed81794 100644 --- a/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php +++ b/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Infra\Adapters\Repositories; -use Domain\Exceptions\BrandNotFoundException; use Domain\Models\Entities\Brand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandCode; @@ -75,10 +74,6 @@ final class InMemoryBrandRepository implements BrandRepository #[\Override] public function delete(Brand $entity): void { - if (! isset($this->brands[$entity->id()->value])) { - throw BrandNotFoundException::forBrand($entity->id()); - } - unset($this->brands[$entity->id()->value]); } } diff --git a/src/Infra/Adapters/Repositories/InMemoryClientRepository.php b/src/Infra/Adapters/Repositories/InMemoryClientRepository.php index f4d5a25..db4beec 100644 --- a/src/Infra/Adapters/Repositories/InMemoryClientRepository.php +++ b/src/Infra/Adapters/Repositories/InMemoryClientRepository.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Infra\Adapters\Repositories; -use Application\Security\Exceptions\ClientNotFoundException; use Application\Security\Models\Entities\Client; use Application\Security\Models\ValueObjects\ClientIdentifier; use Application\Security\Models\ValueObjects\ClientName; @@ -48,10 +47,6 @@ final class InMemoryClientRepository implements ClientRepository #[\Override] public function delete(Client $client): void { - if (! isset($this->clients[$client->id->value])) { - throw ClientNotFoundException::forClient($client->id); - } - unset($this->clients[$client->id->value]); } diff --git a/tests/Integration/Infra/Adapters/Repositories/ApiClientRepositoryTest.php b/tests/Integration/Infra/Adapters/Repositories/ApiClientRepositoryTest.php index 7e89cb2..4278e82 100644 --- a/tests/Integration/Infra/Adapters/Repositories/ApiClientRepositoryTest.php +++ b/tests/Integration/Infra/Adapters/Repositories/ApiClientRepositoryTest.php @@ -183,7 +183,7 @@ describe('ApiClientRepository Integration Tests', function (): void { ]); }); - it('throws ClientNotFoundException when deleting non-existent client', function (): void { + it('is idempotent when deleting non-existent client', function (): void { // GIVEN $repository = new ApiClientRepository(); $client = Client::hydrate( @@ -195,9 +195,11 @@ describe('ApiClientRepository Integration Tests', function (): void { updatedAt: new DateTimeImmutable(), ); - // WHEN & THEN - expect(fn () => $repository->delete($client)) - ->toThrow(ClientNotFoundException::class); + // WHEN + $repository->delete($client); + + // THEN + expect($repository->find($client->id))->toBeNull(); }); it('finds all clients with pagination', function (): void { diff --git a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php index 75fb1e5..cdc17a2 100644 --- a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php +++ b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php @@ -246,7 +246,7 @@ describe('EloquentBrandRepository Integration Tests', function (): void { ->and(Cache::has('brand:all'))->toBeFalse(); }); - it('throws BrandNotFoundException when deleting non-existent brand', function (): void { + it('is idempotent when deleting non-existent brand', function (): void { // GIVEN $brandCache = resolve(BrandCache::class); $repository = new EloquentBrandRepository($brandCache); @@ -257,8 +257,10 @@ describe('EloquentBrandRepository Integration Tests', function (): void { arcadiaId: new ArcadiaId('999'), ); - // WHEN & THEN - expect(fn () => $repository->delete($brand)) - ->toThrow(BrandNotFoundException::class); + // WHEN + $repository->delete($brand); + + // THEN + expect($repository->find($brand->id()))->toBeNull(); }); }); diff --git a/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php b/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php index 96e480d..405abca 100644 --- a/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php +++ b/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php @@ -63,14 +63,16 @@ describe('InMemoryBrandRepository Unit Tests', function (): void { expect($repository->find($brand->id()))->toBeNull(); }); - it('throws BrandNotFoundException when deleting non-existent brand', function (): void { + it('is idempotent when deleting non-existent brand', function (): void { // GIVEN $repository = new InMemoryBrandRepository(); $brand = createBrand('MCQ02', 'MaƮtre CoQ', '2'); - // WHEN & THEN - expect(fn () => $repository->delete($brand)) - ->toThrow(BrandNotFoundException::class); + // WHEN + $repository->delete($brand); + + // THEN + expect($repository->find($brand->id()))->toBeNull(); }); it('clears all brands', function (): void { diff --git a/tests/Unit/Infra/Adapters/Repositories/InMemoryClientRepositoryTest.php b/tests/Unit/Infra/Adapters/Repositories/InMemoryClientRepositoryTest.php index 70bd688..9721573 100644 --- a/tests/Unit/Infra/Adapters/Repositories/InMemoryClientRepositoryTest.php +++ b/tests/Unit/Infra/Adapters/Repositories/InMemoryClientRepositoryTest.php @@ -63,14 +63,16 @@ describe('InMemoryClientRepository Unit Tests', function (): void { ->and($foundAfterDelete)->toBeNull(); }); - it('throws ClientNotFoundException when deleting non-existent client', function (): void { + it('is idempotent when deleting non-existent client', function (): void { // GIVEN $repository = new InMemoryClientRepository(); $client = createClient('arcadia_id_' . str_repeat('9', 32)); - // WHEN & THEN - expect(fn () => $repository->delete($client)) - ->toThrow(ClientNotFoundException::class); + // WHEN + $repository->delete($client); + + // THEN + expect($repository->find($client->id))->toBeNull(); }); it('finds all clients with pagination', function (): void {