feat: Gestion des matières scolaires
Les établissements ont besoin de définir leur référentiel de matières pour pouvoir ensuite les associer aux enseignants et aux classes. Cette fonctionnalité permet aux administrateurs de créer, modifier et archiver les matières avec leurs propriétés (nom, code court, couleur). L'architecture suit le pattern DDD avec des Value Objects utilisant les property hooks PHP 8.5 pour garantir l'immutabilité et la validation. L'isolation multi-tenant est assurée par vérification dans les handlers.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Infrastructure\Persistence\Doctrine;
|
||||
|
||||
use App\Administration\Domain\Exception\SubjectNotFoundException;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolId;
|
||||
use App\Administration\Domain\Model\Subject\Subject;
|
||||
use App\Administration\Domain\Model\Subject\SubjectCode;
|
||||
use App\Administration\Domain\Model\Subject\SubjectColor;
|
||||
use App\Administration\Domain\Model\Subject\SubjectId;
|
||||
use App\Administration\Domain\Model\Subject\SubjectName;
|
||||
use App\Administration\Domain\Model\Subject\SubjectStatus;
|
||||
use App\Administration\Domain\Repository\SubjectRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Override;
|
||||
|
||||
final readonly class DoctrineSubjectRepository implements SubjectRepository
|
||||
{
|
||||
public function __construct(
|
||||
private Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function save(Subject $subject): void
|
||||
{
|
||||
$data = [
|
||||
'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),
|
||||
];
|
||||
|
||||
$exists = $this->findById($subject->id) !== null;
|
||||
|
||||
if ($exists) {
|
||||
$this->connection->update('subjects', $data, ['id' => (string) $subject->id]);
|
||||
} else {
|
||||
$this->connection->insert('subjects', $data);
|
||||
}
|
||||
}
|
||||
|
||||
#[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<string, mixed> $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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user