consentementParental; $this->connection->executeStatement( <<<'SQL' INSERT INTO users ( id, tenant_id, email, first_name, last_name, roles, hashed_password, statut, school_name, date_naissance, created_at, activated_at, invited_at, blocked_at, blocked_reason, consentement_parent_id, consentement_eleve_id, consentement_date, consentement_ip, updated_at ) VALUES ( :id, :tenant_id, :email, :first_name, :last_name, :roles, :hashed_password, :statut, :school_name, :date_naissance, :created_at, :activated_at, :invited_at, :blocked_at, :blocked_reason, :consentement_parent_id, :consentement_eleve_id, :consentement_date, :consentement_ip, NOW() ) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, first_name = EXCLUDED.first_name, last_name = EXCLUDED.last_name, roles = EXCLUDED.roles, hashed_password = EXCLUDED.hashed_password, statut = EXCLUDED.statut, school_name = EXCLUDED.school_name, date_naissance = EXCLUDED.date_naissance, activated_at = EXCLUDED.activated_at, invited_at = EXCLUDED.invited_at, blocked_at = EXCLUDED.blocked_at, blocked_reason = EXCLUDED.blocked_reason, consentement_parent_id = EXCLUDED.consentement_parent_id, consentement_eleve_id = EXCLUDED.consentement_eleve_id, consentement_date = EXCLUDED.consentement_date, consentement_ip = EXCLUDED.consentement_ip, updated_at = NOW() SQL, [ 'id' => (string) $user->id, 'tenant_id' => (string) $user->tenantId, 'email' => (string) $user->email, 'first_name' => $user->firstName, 'last_name' => $user->lastName, 'roles' => json_encode(array_map(static fn (Role $r) => $r->value, $user->roles)), 'hashed_password' => $user->hashedPassword, 'statut' => $user->statut->value, 'school_name' => $user->schoolName, 'date_naissance' => $user->dateNaissance?->format('Y-m-d'), 'created_at' => $user->createdAt->format(DateTimeImmutable::ATOM), 'activated_at' => $user->activatedAt?->format(DateTimeImmutable::ATOM), 'invited_at' => $user->invitedAt?->format(DateTimeImmutable::ATOM), 'blocked_at' => $user->blockedAt?->format(DateTimeImmutable::ATOM), 'blocked_reason' => $user->blockedReason, 'consentement_parent_id' => $consentement?->parentId, 'consentement_eleve_id' => $consentement?->eleveId, 'consentement_date' => $consentement?->dateConsentement->format(DateTimeImmutable::ATOM), 'consentement_ip' => $consentement?->ipAddress, ], ); } #[Override] public function get(UserId $id): User { $user = $this->findById($id); if ($user === null) { throw UserNotFoundException::withId($id); } return $user; } #[Override] public function findById(UserId $id): ?User { $row = $this->connection->fetchAssociative( 'SELECT * FROM users WHERE id = :id', ['id' => (string) $id], ); if ($row === false) { return null; } return $this->hydrate($row); } #[Override] public function findByEmail(Email $email, TenantId $tenantId): ?User { $row = $this->connection->fetchAssociative( 'SELECT * FROM users WHERE tenant_id = :tenant_id AND email = :email', [ 'tenant_id' => (string) $tenantId, 'email' => (string) $email, ], ); if ($row === false) { return null; } return $this->hydrate($row); } #[Override] public function findAllByTenant(TenantId $tenantId): array { $rows = $this->connection->fetchAllAssociative( 'SELECT * FROM users WHERE tenant_id = :tenant_id ORDER BY created_at ASC', ['tenant_id' => (string) $tenantId], ); return array_map(fn (array $row) => $this->hydrate($row), $rows); } /** * @param array $row */ private function hydrate(array $row): User { /** @var string $id */ $id = $row['id']; /** @var string $tenantId */ $tenantId = $row['tenant_id']; /** @var string $email */ $email = $row['email']; /** @var string $firstName */ $firstName = $row['first_name']; /** @var string $lastName */ $lastName = $row['last_name']; /** @var string $rolesJson */ $rolesJson = $row['roles']; /** @var string|null $hashedPassword */ $hashedPassword = $row['hashed_password']; /** @var string $statut */ $statut = $row['statut']; /** @var string $schoolName */ $schoolName = $row['school_name']; /** @var string|null $dateNaissance */ $dateNaissance = $row['date_naissance']; /** @var string $createdAt */ $createdAt = $row['created_at']; /** @var string|null $activatedAt */ $activatedAt = $row['activated_at']; /** @var string|null $invitedAt */ $invitedAt = $row['invited_at']; /** @var string|null $blockedAt */ $blockedAt = $row['blocked_at']; /** @var string|null $blockedReason */ $blockedReason = $row['blocked_reason']; /** @var string|null $consentementParentId */ $consentementParentId = $row['consentement_parent_id']; /** @var string|null $consentementEleveId */ $consentementEleveId = $row['consentement_eleve_id']; /** @var string|null $consentementDate */ $consentementDate = $row['consentement_date']; /** @var string|null $consentementIp */ $consentementIp = $row['consentement_ip']; /** @var string[]|null $roleValues */ $roleValues = json_decode($rolesJson, true); if (!is_array($roleValues)) { throw new RuntimeException(sprintf('Invalid roles JSON for user %s: %s', $id, $rolesJson)); } $roles = array_map(static fn (string $r) => Role::from($r), $roleValues); $consentement = null; if ($consentementParentId !== null && $consentementEleveId !== null && $consentementDate !== null) { $consentement = ConsentementParental::accorder( parentId: $consentementParentId, eleveId: $consentementEleveId, at: new DateTimeImmutable($consentementDate), ipAddress: $consentementIp ?? '', ); } return User::reconstitute( id: UserId::fromString($id), email: new Email($email), roles: $roles, tenantId: TenantId::fromString($tenantId), schoolName: $schoolName, statut: StatutCompte::from($statut), dateNaissance: $dateNaissance !== null ? new DateTimeImmutable($dateNaissance) : null, createdAt: new DateTimeImmutable($createdAt), hashedPassword: $hashedPassword, activatedAt: $activatedAt !== null ? new DateTimeImmutable($activatedAt) : null, consentementParental: $consentement, firstName: $firstName, lastName: $lastName, invitedAt: $invitedAt !== null ? new DateTimeImmutable($invitedAt) : null, blockedAt: $blockedAt !== null ? new DateTimeImmutable($blockedAt) : null, blockedReason: $blockedReason, ); } }