diff --git a/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php b/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php index ad1ab63..25822fa 100644 --- a/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php +++ b/src/Application/Arcadia/Usecase/Brands/FindAll/FindAllBrands.php @@ -24,22 +24,17 @@ final readonly class FindAllBrands implements Usecase { Assert::isInstanceOf($input, Input::class); - $arcadiaBrands = $this->arcadiaBrandDirectory->findAll(); - $brandItems = []; + $paginated = $this->arcadiaBrandDirectory->findAll($input->pagination); + $brandItems = []; - foreach ($arcadiaBrands as $arcadiaBrand) { + foreach ($paginated->getItems() as $arcadiaBrand) { $brand = $this->brandResolver->resolve($arcadiaBrand->id); $brandItems[] = BrandItem::fromDomain($brand); } - $total = count($brandItems); - $offset = $input->pagination->from(); - $limit = $input->pagination->pageSize(); - $paged = array_values(array_slice($brandItems, $offset, $limit)); - return new Output( - brands: $paged, - total: $total, + brands: $brandItems, + total: $paginated->getTotal(), page: $input->pagination->pageNumber(), perPage: $input->pagination->pageSize(), ); diff --git a/src/Domain/Ports/Directories/ArcadiaBrandDirectory.php b/src/Domain/Ports/Directories/ArcadiaBrandDirectory.php index 1965d44..5b6e3fd 100644 --- a/src/Domain/Ports/Directories/ArcadiaBrandDirectory.php +++ b/src/Domain/Ports/Directories/ArcadiaBrandDirectory.php @@ -7,12 +7,14 @@ namespace Domain\Ports\Directories; use Domain\Models\ValueObjects\Brand\ArcadiaBrand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Shared\DDD\Port\Directory; +use Shared\Paginated; +use Shared\Pagination; /** @extends Directory */ interface ArcadiaBrandDirectory extends Directory { - /** @return list */ - public function findAll(): array; + /** @return Paginated */ + public function findAll(Pagination $pagination): Paginated; public function find(ArcadiaId $id): ?ArcadiaBrand; } diff --git a/src/Domain/Ports/Repositories/BrandRepository.php b/src/Domain/Ports/Repositories/BrandRepository.php index 2e33ecf..9a17178 100644 --- a/src/Domain/Ports/Repositories/BrandRepository.php +++ b/src/Domain/Ports/Repositories/BrandRepository.php @@ -9,6 +9,8 @@ use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Brand\BrandId; use Shared\DDD\Port\Repository; +use Shared\Paginated; +use Shared\Pagination; /** @extends Repository */ interface BrandRepository extends Repository @@ -19,8 +21,8 @@ interface BrandRepository extends Repository public function findByArcadiaId(ArcadiaId $arcadiaId): ?Brand; - /** @return list */ - public function findAll(): array; + /** @return Paginated */ + public function findAll(Pagination $pagination): Paginated; public function persist(Brand $entity): void; diff --git a/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php b/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php index 4bbfb27..b2fb20b 100644 --- a/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php +++ b/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php @@ -11,6 +11,8 @@ use Domain\Ports\Directories\ArcadiaBrandDirectory as ArcadiaBrandDirectoryPort; use Illuminate\Database\Connection; use Illuminate\Database\Query\Builder; use Infra\Adapters\Mappers\ArcadiaBrandMapper; +use Shared\Paginated; +use Shared\Pagination; final class ArcadiaMarqueBrandDirectory implements ArcadiaBrandDirectoryPort { @@ -23,9 +25,9 @@ final class ArcadiaMarqueBrandDirectory implements ArcadiaBrandDirectoryPort } #[\Override] - public function findAll(): array + public function findAll(Pagination $pagination): Paginated { - $rows = $this->connection + $query = $this->connection ->table(MarqueTable::table($this->connection)) ->where('COD_MARQ', '>', 0) ->where('LIB_MARQ', 'NOT LIKE', 'Z -%') @@ -34,7 +36,14 @@ final class ArcadiaMarqueBrandDirectory implements ArcadiaBrandDirectoryPort }) ->where(static function (Builder $q): void { $q->whereNull('TYPE_MARQUE')->orWhere('TYPE_MARQUE', '!=', 99); - }) + }); + + $total = (int) (clone $query)->count(); + + $rows = (clone $query) + ->orderBy('COD_MARQ', 'asc') + ->offset($pagination->from()) + ->limit($pagination->pageSize()) ->get(); $items = []; @@ -45,7 +54,7 @@ final class ArcadiaMarqueBrandDirectory implements ArcadiaBrandDirectoryPort $items[] = $domain; } - return $items; + return new Paginated($pagination, $items, $total); } #[\Override] diff --git a/src/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectory.php b/src/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectory.php index 27c1912..cde7b9c 100644 --- a/src/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectory.php +++ b/src/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectory.php @@ -7,6 +7,8 @@ namespace Infra\Adapters\Directories; use Domain\Models\ValueObjects\Brand\ArcadiaBrand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Ports\Directories\ArcadiaBrandDirectory; +use Shared\Paginated; +use Shared\Pagination; final class InMemoryArcadiaBrandDirectory implements ArcadiaBrandDirectory { @@ -28,12 +30,17 @@ final class InMemoryArcadiaBrandDirectory implements ArcadiaBrandDirectory } #[\Override] - public function findAll(): array + public function findAll(Pagination $pagination): Paginated { - return array_values(array_filter( + $filtered = array_values(array_filter( $this->brands, static fn (ArcadiaBrand $b): bool => ! str_starts_with($b->name->value, 'Z -') )); + + $total = count($filtered); + $paged = array_slice($filtered, $pagination->from(), $pagination->pageSize()); + + return new Paginated($pagination, $paged, $total); } #[\Override] diff --git a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php index 8bd2ddf..702a037 100644 --- a/src/Infra/Adapters/Repositories/EloquentBrandRepository.php +++ b/src/Infra/Adapters/Repositories/EloquentBrandRepository.php @@ -13,6 +13,8 @@ use Domain\Ports\Repositories\BrandRepository; use Infra\Cache\BrandCache; use Infra\Database\Models\Brand as BrandModel; use Ramsey\Uuid\Uuid; +use Shared\Paginated; +use Shared\Pagination; final readonly class EloquentBrandRepository implements BrandRepository { @@ -86,23 +88,24 @@ final readonly class EloquentBrandRepository implements BrandRepository } #[\Override] - public function findAll(): array + public function findAll(Pagination $pagination): Paginated { - $cached = $this->brandCache->getAll(); - if ($cached !== null) { - return $cached; - } + $query = BrandModel::query()->orderBy('name', 'asc'); + $total = $query->count(); - $brands = array_values( - BrandModel::query() - ->get() - ->map(fn (BrandModel $model): Brand => $this->toDomain($model)) - ->all() - ); + $brands = $query + ->offset($pagination->from()) + ->limit($pagination->pageSize()) + ->get() + ->map(function (BrandModel $model): Brand { + $brand = $this->toDomain($model); + $this->brandCache->put($brand); - $this->brandCache->putAll(...$brands); + return $brand; + }) + ->all(); - return $brands; + return new Paginated($pagination, array_values($brands), $total); } #[\Override] diff --git a/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php b/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php index ed81794..11ccbbe 100644 --- a/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php +++ b/src/Infra/Adapters/Repositories/InMemoryBrandRepository.php @@ -9,6 +9,8 @@ use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandCode; use Domain\Models\ValueObjects\Brand\BrandId; use Domain\Ports\Repositories\BrandRepository; +use Shared\Paginated; +use Shared\Pagination; final class InMemoryBrandRepository implements BrandRepository { @@ -60,9 +62,13 @@ final class InMemoryBrandRepository implements BrandRepository } #[\Override] - public function findAll(): array + public function findAll(Pagination $pagination): Paginated { - return array_values($this->brands); + $all = array_values($this->brands); + $total = count($all); + $paged = array_slice($all, $pagination->from(), $pagination->pageSize()); + + return new Paginated($pagination, $paged, $total); } #[\Override] diff --git a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php index 34a8755..996d059 100644 --- a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php +++ b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php @@ -15,6 +15,7 @@ use Infra\Adapters\Repositories\EloquentBrandRepository; use Infra\Cache\BrandCache; use Infra\Database\Models\Brand as BrandModel; use Ramsey\Uuid\Uuid; +use Shared\Pagination; use Tests\TestCase; uses(TestCase::class, RefreshDatabase::class); @@ -147,30 +148,34 @@ describe('EloquentBrandRepository Integration Tests', function (): void { ->and($secondResult->name->value)->toBe('Père Dodu'); }); - it('finds all brands and caches the list', function (): void { + it('finds brands with pagination and warms cache', function (): void { // GIVEN $brandCache = resolve(BrandCache::class); $repository = new EloquentBrandRepository($brandCache); + $id1 = Uuid::uuid7()->toString(); + $id2 = Uuid::uuid7()->toString(); BrandModel::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => $id1, 'name' => 'Brand 1', 'arcadia_id' => 1, 'uwil_public_id' => 'BRD01', ]); BrandModel::query()->create([ - 'id' => Uuid::uuid7()->toString(), + 'id' => $id2, 'name' => 'Brand 2', 'arcadia_id' => 2, 'uwil_public_id' => 'BRD02', ]); // WHEN - $all = $repository->findAll(); + $all = $repository->findAll(Pagination::ofPage(1, 25)); // THEN - expect($all)->toHaveCount(2) - ->and(Cache::has('brand:all'))->toBeTrue(); + expect($all->getItems())->toHaveCount(2) + ->and($all->getTotal())->toBe(2) + ->and(Cache::has("brand:id:{$id1}"))->toBeTrue() + ->and(Cache::has("brand:id:{$id2}"))->toBeTrue(); }); it('gracefully recovers when cache contains corrupted or non-array data', function (): void { diff --git a/tests/Unit/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectoryTest.php b/tests/Unit/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectoryTest.php index 815ec67..930f9f5 100644 --- a/tests/Unit/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectoryTest.php +++ b/tests/Unit/Infra/Adapters/Directories/InMemoryArcadiaBrandDirectoryTest.php @@ -8,6 +8,7 @@ use Domain\Models\ValueObjects\Brand\ArcadiaBrand; use Domain\Models\ValueObjects\Brand\ArcadiaId; use Domain\Models\ValueObjects\Brand\BrandName; use Infra\Adapters\Directories\InMemoryArcadiaBrandDirectory; +use Shared\Pagination; covers(InMemoryArcadiaBrandDirectory::class); @@ -38,16 +39,17 @@ describe('InMemoryArcadiaBrandDirectory Unit Tests', function (): void { $directory->add($b1, $b2); // WHEN - $all = $directory->findAll(); + $all = $directory->findAll(Pagination::ofPage(1, 25)); // THEN - expect($all)->toHaveCount(2); + expect($all->getItems())->toHaveCount(2) + ->and($all->getTotal())->toBe(2); // WHEN $directory->clear(); // THEN - expect($directory->findAll())->toBeEmpty(); + expect($directory->findAll(Pagination::ofPage(1, 25))->getItems())->toBeEmpty(); }); it('filters out comment and obsolete brands starting with Z -', function (): void { @@ -58,12 +60,13 @@ describe('InMemoryArcadiaBrandDirectory Unit Tests', function (): void { $directory->add($valid, $comment); // WHEN - $all = $directory->findAll(); + $all = $directory->findAll(Pagination::ofPage(1, 25)); $found = $directory->find(new ArcadiaId('2')); // THEN - expect($all)->toHaveCount(1) - ->and($all[0]->id->value)->toBe('1') + expect($all->getItems())->toHaveCount(1) + ->and($all->getItems()[0]->id->value)->toBe('1') + ->and($all->getTotal())->toBe(1) ->and($found)->toBeNull(); }); }); diff --git a/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php b/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php index 1cd9537..fdfc90f 100644 --- a/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php +++ b/tests/Unit/Infra/Adapters/Repositories/InMemoryBrandRepositoryTest.php @@ -11,6 +11,7 @@ use Domain\Models\ValueObjects\Brand\BrandId; use Domain\Models\ValueObjects\Brand\BrandName; use Infra\Adapters\Repositories\InMemoryBrandRepository; use Ramsey\Uuid\Uuid; +use Shared\Pagination; covers(InMemoryBrandRepository::class); @@ -44,6 +45,22 @@ describe('InMemoryBrandRepository Unit Tests', function (): void { ->and($missingByCode)->toBeNull(); }); + it('finds brands with pagination', function (): void { + // GIVEN + $repository = new InMemoryBrandRepository(); + $b1 = createBrand('LGL01', 'Le Gaulois', '1'); + $b2 = createBrand('MCQ02', 'Maître CoQ', '2'); + $repository->add($b1, $b2); + + // WHEN + $paginated = $repository->findAll(Pagination::ofPage(1, 1)); + + // THEN + expect($paginated->getItems())->toHaveCount(1) + ->and($paginated->getTotal())->toBe(2) + ->and($paginated->getItems()[0])->toEqual($b1); + }); + it('persists and deletes a brand', function (): void { // GIVEN $repository = new InMemoryBrandRepository();