refactor(security): segregate ClientDirectory and ClientRepository ports (ISP)

This commit is contained in:
Jérémy Munsch 2026-08-20 10:02:17 +02:00
parent c8a46bb9b3
commit 15a0ff55a3
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
8 changed files with 45 additions and 22 deletions

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Application\Security\Ports\Directories;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Shared\DDD\Port\Directory;
use Shared\Paginated;
use Shared\Pagination;
/**
* @extends Directory<ClientId, Client>
*/
interface ClientDirectory extends Directory
{
public function find(ClientId $id): ?Client;
/** @return Paginated<Client> */
public function search(string $query, Pagination $pagination): Paginated;
/** @return Paginated<Client> */
public function findAll(Pagination $pagination): Paginated;
}

View file

@ -10,8 +10,6 @@ use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\ClientName;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Shared\DDD\Port\Repository;
use Shared\Paginated;
use Shared\Pagination;
/**
* @extends Repository<ClientId, Client>
@ -20,20 +18,14 @@ interface ClientRepository extends Repository
{
public function find(ClientId $id): ?Client;
public function findByIdentifier(ClientIdentifier $identifier): ?Client;
public function existsByName(ClientName $name): bool;
public function persist(Client $client): void;
/**
* @throws ClientNotFoundException
*/
public function delete(Client $client): void;
public function existsByName(ClientName $name): bool;
public function findByIdentifier(ClientIdentifier $identifier): ?Client;
/** @return Paginated<Client> */
public function search(string $query, Pagination $pagination): Paginated;
/** @return Paginated<Client> */
public function findAll(Pagination $pagination): Paginated;
}

View file

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Application\Security\Usecases\FindAllClients;
use Application\Security\Models\Entities\Client;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Ports\Directories\ClientDirectory;
use Shared\DDD\Usecase\Input as BaseInput;
use Shared\DDD\Usecase\Usecase;
use Shared\Paginated;
@ -15,7 +15,7 @@ use Webmozart\Assert\Assert;
final readonly class FindAllClients implements Usecase
{
public function __construct(
private ClientRepository $repository,
private ClientDirectory $directory,
) {
}
@ -24,7 +24,7 @@ final readonly class FindAllClients implements Usecase
{
Assert::isInstanceOf($input, Input::class);
$page = $this->repository->findAll($input->pagination);
$page = $this->directory->findAll($input->pagination);
return $this->toOutput($page);
}

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Application\Security\Usecases\FindClient;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Ports\Directories\ClientDirectory;
use Shared\DDD\Usecase\Input as BaseInput;
use Shared\DDD\Usecase\Usecase;
use Shared\Pagination;
@ -14,7 +14,7 @@ use Webmozart\Assert\Assert;
final readonly class FindClient implements Usecase
{
public function __construct(
private ClientRepository $clientRepository,
private ClientDirectory $clientDirectory,
) {
}
@ -24,7 +24,7 @@ final readonly class FindClient implements Usecase
Assert::isInstanceOf($input, Input::class);
$pagination = $input->pagination ?? Pagination::ofOffsetAndLimit(0, 20);
$paginated = $this->clientRepository->search($input->query, $pagination);
$paginated = $this->clientDirectory->search($input->query, $pagination);
return new Output($paginated);
}

View file

@ -9,13 +9,14 @@ use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\ClientName;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Application\Security\Ports\Directories\ClientDirectory;
use Application\Security\Ports\Repositories\ClientRepository;
use Illuminate\Support\Str;
use Infra\Database\Models\ApiClient;
use Shared\Paginated;
use Shared\Pagination;
final class ApiClientRepository implements ClientRepository
final class ApiClientRepository implements ClientDirectory, ClientRepository
{
#[\Override]
public function persist(Client $client): void

View file

@ -9,12 +9,13 @@ use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\ClientName;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Application\Security\Ports\Directories\ClientDirectory;
use Application\Security\Ports\Repositories\ClientRepository;
use Illuminate\Support\Str;
use Shared\Paginated;
use Shared\Pagination;
final class InMemoryClientRepository implements ClientRepository
final class InMemoryClientRepository implements ClientDirectory, ClientRepository
{
/** @var array<string, Client> */
private array $clients = [];

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Infra\Providers;
use Application\Security\Ports\Directories\ClientDirectory;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Ports\Services\ClientCredentialGenerator;
use Application\Security\Ports\Services\HmacSignatureValidator;
@ -32,6 +33,7 @@ final class SecurityServiceProvider extends ServiceProvider
{
$this->app->bind(ClientCredentialGenerator::class, ClientCredentialGeneratorImpl::class);
$this->app->bind(ClientRepository::class, ApiClientRepository::class);
$this->app->bind(ClientDirectory::class, ApiClientRepository::class);
$this->app->bind(HmacSignatureValidator::class, HmacSignatureValidatorImpl::class);
$this->app->bind(NonceValidator::class, function (Application $app): CachedNonceValidator {

View file

@ -7,6 +7,7 @@ namespace Presentation\Console\Services;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\Entities\ClientId;
use Application\Security\Ports\Directories\ClientDirectory;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Usecases\FindClient\FindClient;
use Application\Security\Usecases\FindClient\Input as FindClientInput;
@ -22,6 +23,7 @@ final readonly class ClientConsoleResolver
{
public function __construct(
private ClientRepository $clientRepository,
private ClientDirectory $clientDirectory,
private FindClient $findClient,
private UsecaseExecutor $executor,
) {
@ -32,7 +34,7 @@ final readonly class ClientConsoleResolver
*/
public function complete(CompletionSuggestions $suggestions): void
{
$clients = $this->clientRepository->findAll(Pagination::ofOffsetAndLimit(0, 100));
$clients = $this->clientDirectory->findAll(Pagination::ofOffsetAndLimit(0, 100));
/** @var list<Suggestion> $items */
$items = array_map(
@ -56,7 +58,7 @@ final readonly class ClientConsoleResolver
$trimmed = trim($value);
if ($trimmed === '') {
$clients = $this->clientRepository->findAll(Pagination::ofOffsetAndLimit(0, 10));
$clients = $this->clientDirectory->findAll(Pagination::ofOffsetAndLimit(0, 10));
$options = [];
foreach ($clients->getItems() as $client) {
$options[$client->identifier->value] = sprintf(