feat(directory) : migrate Client into Directory module (back)

LST-58 (2.4), part 1/2 — Client move. Prospect + repertoire front are pending
the product spec and will be added on this branch afterward.

- Client entity moved to src/Module/Directory/Domain/Entity; repository split
  into Domain/Repository/ClientRepositoryInterface + Doctrine impl (bound in
  services.yaml). 5 client MCP tools moved to Infrastructure/Mcp/Tool, now
  injecting the interface.
- resolve_target_entities ClientInterface repointed to Directory\Client;
  Directory mapping added; DirectoryModule registered (id directory, 2 RBAC
  perms). Client.projects relation now uses ProjectInterface -> Directory no
  longer depends on ProjectManagement.
- ProjectManagement Create/UpdateProjectTool inject Directory's
  ClientRepositoryInterface; Serializer and fixtures repointed.
- Garde-fous: #[Auditable] + Timestampable/Blamable on Client (additive
  migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
  NULL + COMMENT).

161 tests green, mapping valid, no API route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 18:51:49 +02:00
parent 163bf0891a
commit c5738d269b
18 changed files with 190 additions and 51 deletions
+2
View File
@@ -9,6 +9,7 @@ declare(strict_types=1);
use App\Module\Absence\AbsenceModule; use App\Module\Absence\AbsenceModule;
use App\Module\Core\CoreModule; use App\Module\Core\CoreModule;
use App\Module\Directory\DirectoryModule;
use App\Module\ProjectManagement\ProjectManagementModule; use App\Module\ProjectManagement\ProjectManagementModule;
use App\Module\TimeTracking\TimeTrackingModule; use App\Module\TimeTracking\TimeTrackingModule;
@@ -17,4 +18,5 @@ return [
TimeTrackingModule::class, TimeTrackingModule::class,
ProjectManagementModule::class, ProjectManagementModule::class,
AbsenceModule::class, AbsenceModule::class,
DirectoryModule::class,
]; ];
+6 -1
View File
@@ -25,7 +25,7 @@ doctrine:
App\Shared\Domain\Contract\ProjectInterface: App\Module\ProjectManagement\Domain\Entity\Project App\Shared\Domain\Contract\ProjectInterface: App\Module\ProjectManagement\Domain\Entity\Project
App\Shared\Domain\Contract\TaskInterface: App\Module\ProjectManagement\Domain\Entity\Task App\Shared\Domain\Contract\TaskInterface: App\Module\ProjectManagement\Domain\Entity\Task
App\Shared\Domain\Contract\TaskTagInterface: App\Module\ProjectManagement\Domain\Entity\TaskTag App\Shared\Domain\Contract\TaskTagInterface: App\Module\ProjectManagement\Domain\Entity\TaskTag
App\Shared\Domain\Contract\ClientInterface: App\Entity\Client App\Shared\Domain\Contract\ClientInterface: App\Module\Directory\Domain\Entity\Client
mappings: mappings:
App: App:
type: attribute type: attribute
@@ -53,6 +53,11 @@ doctrine:
is_bundle: false is_bundle: false
dir: '%kernel.project_dir%/src/Module/Absence/Domain/Entity' dir: '%kernel.project_dir%/src/Module/Absence/Domain/Entity'
prefix: 'App\Module\Absence\Domain\Entity' prefix: 'App\Module\Absence\Domain\Entity'
Directory:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Module/Directory/Domain/Entity'
prefix: 'App\Module\Directory\Domain\Entity'
controller_resolver: controller_resolver:
auto_mapping: false auto_mapping: false
+2
View File
@@ -99,4 +99,6 @@ services:
App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface: '@App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository' App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface: '@App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository'
App\Module\Directory\Domain\Repository\ClientRepositoryInterface: '@App\Module\Directory\Infrastructure\Doctrine\DoctrineClientRepository'
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier' App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Directory module: add Timestampable/Blamable columns to client.
*
* Client (moved to App\Module\Directory\Domain\Entity) adopts
* TimestampableBlamableTrait. This migration is purely additive — nullable
* columns + nullable FK to "user" with ON DELETE SET NULL. No DROP/ALTER on
* existing data. Columns are lowercase snake_case. Hand-written to guarantee
* zero destructive instruction.
*/
final class Version20260620180000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Directory: add timestampable/blamable columns to client (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE client ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C744045516FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_C7440455DE12AB56 ON client (created_by)');
$this->addSql('CREATE INDEX IDX_C744045516FE72E1 ON client (updated_by)');
$this->addSql("COMMENT ON COLUMN client.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN client.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN client.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN client.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE client DROP CONSTRAINT FK_C7440455DE12AB56');
$this->addSql('ALTER TABLE client DROP CONSTRAINT FK_C744045516FE72E1');
$this->addSql('DROP INDEX IDX_C7440455DE12AB56');
$this->addSql('DROP INDEX IDX_C744045516FE72E1');
$this->addSql('ALTER TABLE client DROP created_at');
$this->addSql('ALTER TABLE client DROP updated_at');
$this->addSql('ALTER TABLE client DROP created_by');
$this->addSql('ALTER TABLE client DROP updated_by');
}
}
+1 -1
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\DataFixtures; namespace App\DataFixtures;
use App\Entity\Client;
use App\Entity\MailConfiguration; use App\Entity\MailConfiguration;
use App\Entity\ZimbraConfiguration; use App\Entity\ZimbraConfiguration;
use App\Enum\ContractType; use App\Enum\ContractType;
@@ -15,6 +14,7 @@ use App\Module\Absence\Domain\Enum\AbsenceStatus;
use App\Module\Absence\Domain\Enum\AbsenceType; use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Core\Application\Rbac\RbacSeeder; use App\Module\Core\Application\Rbac\RbacSeeder;
use App\Module\Core\Domain\Entity\User; use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\ProjectManagement\Domain\Entity\Project; use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort; use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
+1 -1
View File
@@ -4,11 +4,11 @@ declare(strict_types=1);
namespace App\Mcp\Tool; namespace App\Mcp\Tool;
use App\Entity\Client;
use App\Module\Absence\Domain\Entity\AbsenceBalance; use App\Module\Absence\Domain\Entity\AbsenceBalance;
use App\Module\Absence\Domain\Entity\AbsencePolicy; use App\Module\Absence\Domain\Entity\AbsencePolicy;
use App\Module\Absence\Domain\Entity\AbsenceRequest; use App\Module\Absence\Domain\Entity\AbsenceRequest;
use App\Module\Core\Domain\Entity\User; use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\ProjectManagement\Domain\Entity\Project; use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument; use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Module\Directory;
use App\Shared\Domain\Module\ModuleInterface;
final class DirectoryModule implements ModuleInterface
{
public static function id(): string
{
return 'directory';
}
public static function label(): string
{
return 'Répertoire';
}
public static function isRequired(): bool
{
return false;
}
/**
* Permissions RBAC fin du Module Directory.
*
* Additif : alimente le catalogue RBAC. La sécurité des opérations API
* reste en ROLE_USER/ROLE_ADMIN (non recâblée ici).
*
* @return list<array{code: string, label: string}>
*/
public static function permissions(): array
{
return [
['code' => 'directory.clients.view', 'label' => 'Voir les clients'],
['code' => 'directory.clients.manage', 'label' => 'Gérer les clients'],
];
}
}
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace App\Module\Directory\Domain\Entity;
use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Delete;
@@ -10,14 +10,19 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Post;
use App\Module\ProjectManagement\Domain\Entity\Project; use App\Module\Directory\Infrastructure\Doctrine\DoctrineClientRepository;
use App\Repository\ClientRepository; use App\Shared\Domain\Attribute\Auditable;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\ClientInterface; use App\Shared\Domain\Contract\ClientInterface;
use App\Shared\Domain\Contract\ProjectInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\Groups;
#[Auditable]
#[ApiResource( #[ApiResource(
operations: [ operations: [
new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"), new GetCollection(paginationEnabled: false, security: "is_granted('ROLE_USER')"),
@@ -30,9 +35,11 @@ use Symfony\Component\Serializer\Attribute\Groups;
denormalizationContext: ['groups' => ['client:write']], denormalizationContext: ['groups' => ['client:write']],
order: ['name' => 'ASC'], order: ['name' => 'ASC'],
)] )]
#[ORM\Entity(repositoryClass: ClientRepository::class)] #[ORM\Entity(repositoryClass: DoctrineClientRepository::class)]
class Client implements ClientInterface class Client implements ClientInterface, TimestampableInterface, BlamableInterface
{ {
use TimestampableBlamableTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
@@ -63,8 +70,8 @@ class Client implements ClientInterface
#[Groups(['client:read', 'client:write'])] #[Groups(['client:read', 'client:write'])]
private ?string $postalCode = null; private ?string $postalCode = null;
/** @var Collection<int, Project> */ /** @var Collection<int, ProjectInterface> */
#[ORM\OneToMany(targetEntity: Project::class, mappedBy: 'client')] #[ORM\OneToMany(targetEntity: ProjectInterface::class, mappedBy: 'client')]
private Collection $projects; private Collection $projects;
public function __construct() public function __construct()
@@ -149,7 +156,7 @@ class Client implements ClientInterface
return $this; return $this;
} }
/** @return Collection<int, Project> */ /** @return Collection<int, ProjectInterface> */
public function getProjects(): Collection public function getProjects(): Collection
{ {
return $this->projects; return $this->projects;
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\Directory\Domain\Repository;
use App\Module\Directory\Domain\Entity\Client;
interface ClientRepositoryInterface
{
public function findById(int $id): ?Client;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return Client[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\Directory\Infrastructure\Doctrine;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Client>
*/
final class DoctrineClientRepository extends ServiceEntityRepository implements ClientRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Client::class);
}
public function findById(int $id): ?Client
{
return $this->find($id);
}
}
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mcp\Tool\Reference; namespace App\Module\Directory\Infrastructure\Mcp\Tool;
use App\Entity\Client;
use App\Mcp\Tool\Serializer; use App\Mcp\Tool\Serializer;
use App\Module\Directory\Domain\Entity\Client;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
@@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mcp\Tool\Reference; namespace App\Module\Directory\Infrastructure\Mcp\Tool;
use App\Repository\ClientRepository; use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteClientTool class DeleteClientTool
{ {
public function __construct( public function __construct(
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -28,7 +28,7 @@ class DeleteClientTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.'); throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
} }
$client = $this->clientRepository->find($id); $client = $this->clientRepository->findById($id);
if (null === $client) { if (null === $client) {
throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id)); throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id));
} }
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mcp\Tool\Reference; namespace App\Module\Directory\Infrastructure\Mcp\Tool;
use App\Mcp\Tool\Serializer; use App\Mcp\Tool\Serializer;
use App\Repository\ClientRepository; use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
@@ -17,7 +17,7 @@ use function sprintf;
class GetClientTool class GetClientTool
{ {
public function __construct( public function __construct(
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -27,7 +27,7 @@ class GetClientTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.'); throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
} }
$client = $this->clientRepository->find($id); $client = $this->clientRepository->findById($id);
if (null === $client) { if (null === $client) {
throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id)); throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id));
} }
@@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mcp\Tool\Reference; namespace App\Module\Directory\Infrastructure\Mcp\Tool;
use App\Repository\ClientRepository; use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -13,7 +13,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListClientsTool class ListClientsTool
{ {
public function __construct( public function __construct(
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mcp\Tool\Reference; namespace App\Module\Directory\Infrastructure\Mcp\Tool;
use App\Mcp\Tool\Serializer; use App\Mcp\Tool\Serializer;
use App\Repository\ClientRepository; use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
@@ -18,7 +18,7 @@ use function sprintf;
class UpdateClientTool class UpdateClientTool
{ {
public function __construct( public function __construct(
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -36,7 +36,7 @@ class UpdateClientTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.'); throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
} }
$client = $this->clientRepository->find($id); $client = $this->clientRepository->findById($id);
if (null === $client) { if (null === $client) {
throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id)); throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $id));
} }
@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project; namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Mcp\Tool\Serializer; use App\Mcp\Tool\Serializer;
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use App\Module\ProjectManagement\Domain\Entity\Project; use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Repository\ClientRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
@@ -20,7 +20,7 @@ class CreateProjectTool
{ {
public function __construct( public function __construct(
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -46,7 +46,7 @@ class CreateProjectTool
$project->setColor($color); $project->setColor($color);
} }
if (null !== $clientId) { if (null !== $clientId) {
$client = $this->clientRepository->find($clientId); $client = $this->clientRepository->findById($clientId);
if (null === $client) { if (null === $client) {
throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $clientId)); throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $clientId));
} }
@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project; namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Mcp\Tool\Serializer; use App\Mcp\Tool\Serializer;
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface; use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use App\Repository\ClientRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
@@ -20,7 +20,7 @@ class UpdateProjectTool
{ {
public function __construct( public function __construct(
private readonly ProjectRepositoryInterface $projectRepository, private readonly ProjectRepositoryInterface $projectRepository,
private readonly ClientRepository $clientRepository, private readonly ClientRepositoryInterface $clientRepository,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly Security $security, private readonly Security $security,
) {} ) {}
@@ -57,7 +57,7 @@ class UpdateProjectTool
$project->setColor($color); $project->setColor($color);
} }
if (null !== $clientId) { if (null !== $clientId) {
$client = $this->clientRepository->find($clientId); $client = $this->clientRepository->findById($clientId);
if (null === $client) { if (null === $client) {
throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $clientId)); throw new InvalidArgumentException(sprintf('Client with ID %d not found.', $clientId));
} }
-17
View File
@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\Client;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class ClientRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Client::class);
}
}