diff --git a/src/Shared/Infrastructure/Mcp/Serializer.php b/src/Shared/Infrastructure/Mcp/Serializer.php index 21e3fbc..6f2dd65 100644 --- a/src/Shared/Infrastructure/Mcp/Serializer.php +++ b/src/Shared/Infrastructure/Mcp/Serializer.php @@ -23,11 +23,13 @@ use App\Module\ProjectManagement\Domain\Entity\TaskPriority; use App\Module\ProjectManagement\Domain\Entity\TaskStatus; use App\Module\ProjectManagement\Domain\Entity\TaskTag; use App\Module\TimeTracking\Domain\Entity\TimeEntry; +use App\Shared\Domain\Contract\ClientInterface; use App\Shared\Domain\Contract\ProjectInterface; use App\Shared\Domain\Contract\TaskInterface; use App\Shared\Domain\Contract\TaskTagInterface; use App\Shared\Domain\Contract\UserInterface; use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\EntityNotFoundException; /** * Shared serialization helpers for MCP tools. @@ -59,11 +61,8 @@ final class Serializer 'name' => $project->getName(), 'description' => $project->getDescription(), 'color' => $project->getColor(), - 'client' => $project->getClient() ? [ - 'id' => $project->getClient()->getId(), - 'name' => $project->getClient()->getName(), - ] : null, - 'archived' => $project->isArchived(), + 'client' => self::clientRef($project->getClient()), + 'archived' => $project->isArchived(), ]; } @@ -516,4 +515,31 @@ final class Serializer 'initialLeaveBalance' => $u->getInitialLeaveBalance(), ]; } + + /** + * Safely serialize a project's client reference. + * + * The client association uses ON DELETE SET NULL, but a stale row may leave + * a dangling foreign key (e.g. data imported with the constraint disabled). + * In that case Doctrine returns an uninitialized proxy whose hydration + * throws EntityNotFoundException; we treat such a reference as absent rather + * than letting it crash the whole tool. + * + * @return null|array{id: ?int, name: ?string} + */ + private static function clientRef(?ClientInterface $client): ?array + { + if (null === $client) { + return null; + } + + try { + return [ + 'id' => $client->getId(), + 'name' => $client->getName(), + ]; + } catch (EntityNotFoundException) { + return null; + } + } }