connection->executeStatement( 'INSERT INTO subjects (id, tenant_id, school_id, name, code, color, status, description, created_at, updated_at, deleted_at) VALUES (:id, :tenant_id, :school_id, :name, :code, :color, :status, :description, :created_at, :updated_at, :deleted_at) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, code = EXCLUDED.code, color = EXCLUDED.color, status = EXCLUDED.status, description = EXCLUDED.description, updated_at = EXCLUDED.updated_at, deleted_at = EXCLUDED.deleted_at', [ 'id' => (string) $subject->id, 'tenant_id' => (string) $subject->tenantId, 'school_id' => (string) $subject->schoolId, 'name' => (string) $subject->name, 'code' => (string) $subject->code, 'color' => $subject->color !== null ? (string) $subject->color : null, 'status' => $subject->status->value, 'description' => $subject->description, 'created_at' => $subject->createdAt->format(DateTimeImmutable::ATOM), 'updated_at' => $subject->updatedAt->format(DateTimeImmutable::ATOM), 'deleted_at' => $subject->deletedAt?->format(DateTimeImmutable::ATOM), ], ); } #[Override] public function get(SubjectId $id): Subject { $subject = $this->findById($id); if ($subject === null) { throw SubjectNotFoundException::withId($id); } return $subject; } #[Override] public function findById(SubjectId $id): ?Subject { $row = $this->connection->fetchAssociative( 'SELECT * FROM subjects WHERE id = :id', ['id' => (string) $id], ); if ($row === false) { return null; } return $this->hydrate($row); } #[Override] public function findByCode( SubjectCode $code, TenantId $tenantId, SchoolId $schoolId, ): ?Subject { $row = $this->connection->fetchAssociative( 'SELECT * FROM subjects WHERE tenant_id = :tenant_id AND school_id = :school_id AND code = :code AND deleted_at IS NULL', [ 'tenant_id' => (string) $tenantId, 'school_id' => (string) $schoolId, 'code' => (string) $code, ], ); if ($row === false) { return null; } return $this->hydrate($row); } #[Override] public function findActiveByTenantAndSchool( TenantId $tenantId, SchoolId $schoolId, ): array { $rows = $this->connection->fetchAllAssociative( 'SELECT * FROM subjects WHERE tenant_id = :tenant_id AND school_id = :school_id AND status = :status ORDER BY name ASC', [ 'tenant_id' => (string) $tenantId, 'school_id' => (string) $schoolId, 'status' => SubjectStatus::ACTIVE->value, ], ); return array_map(fn ($row) => $this->hydrate($row), $rows); } #[Override] public function delete(SubjectId $id): void { $this->connection->delete('subjects', ['id' => (string) $id]); } /** * @param array $row */ private function hydrate(array $row): Subject { /** @var string $id */ $id = $row['id']; /** @var string $tenantId */ $tenantId = $row['tenant_id']; /** @var string $schoolId */ $schoolId = $row['school_id']; /** @var non-empty-string $name */ $name = $row['name']; /** @var non-empty-string $code */ $code = $row['code']; /** @var non-empty-string|null $color */ $color = $row['color']; /** @var string $status */ $status = $row['status']; /** @var string|null $description */ $description = $row['description']; /** @var string $createdAt */ $createdAt = $row['created_at']; /** @var string $updatedAt */ $updatedAt = $row['updated_at']; /** @var string|null $deletedAt */ $deletedAt = $row['deleted_at']; return Subject::reconstitute( id: SubjectId::fromString($id), tenantId: TenantId::fromString($tenantId), schoolId: SchoolId::fromString($schoolId), name: new SubjectName($name), code: new SubjectCode($code), color: $color !== null ? new SubjectColor($color) : null, status: SubjectStatus::from($status), description: $description, createdAt: new DateTimeImmutable($createdAt), updatedAt: new DateTimeImmutable($updatedAt), deletedAt: $deletedAt !== null ? new DateTimeImmutable($deletedAt) : null, ); } }