| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- <?php
- declare(strict_types=1);
- namespace App\Repositories;
- use App\Domain\Worker;
- use PDO;
- use RuntimeException;
- final class WorkerRepository
- {
- /** Whitelisted updatable columns. Do not extend without also auditing intent. */
- private const UPDATABLE = ['name', 'is_active', 'default_rtb'];
- public function __construct(private readonly PDO $pdo)
- {
- }
- /** @return list<Worker> */
- public function all(): array
- {
- $stmt = $this->pdo->query(
- 'SELECT * FROM workers ORDER BY is_active DESC, LOWER(name) ASC'
- );
- $out = [];
- foreach ($stmt as $row) {
- $out[] = self::hydrate($row);
- }
- return $out;
- }
- public function find(int $id): ?Worker
- {
- $stmt = $this->pdo->prepare('SELECT * FROM workers WHERE id = ?');
- $stmt->execute([$id]);
- $row = $stmt->fetch();
- return is_array($row) ? self::hydrate($row) : null;
- }
- public function findByName(string $name): ?Worker
- {
- $stmt = $this->pdo->prepare('SELECT * FROM workers WHERE name = ?');
- $stmt->execute([$name]);
- $row = $stmt->fetch();
- return is_array($row) ? self::hydrate($row) : null;
- }
- /**
- * Active workers that are NOT yet members of the given sprint. Used by
- * the sprint-settings page to populate the "available" list.
- *
- * @return list<Worker>
- */
- public function activeNotInSprint(int $sprintId): array
- {
- $stmt = $this->pdo->prepare(
- 'SELECT w.* FROM workers w
- WHERE w.is_active = 1
- AND w.id NOT IN (
- SELECT sw.worker_id FROM sprint_workers sw WHERE sw.sprint_id = ?
- )
- ORDER BY LOWER(w.name) ASC'
- );
- $stmt->execute([$sprintId]);
- $out = [];
- foreach ($stmt as $row) {
- $out[] = self::hydrate($row);
- }
- return $out;
- }
- /**
- * Insert a new worker. Throws if the unique name constraint is violated.
- */
- public function create(string $name, bool $isActive, float $defaultRtb): Worker
- {
- $now = gmdate('Y-m-d\TH:i:s\Z');
- $stmt = $this->pdo->prepare(
- 'INSERT INTO workers (name, is_active, default_rtb, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?)'
- );
- $stmt->execute([$name, $isActive ? 1 : 0, $defaultRtb, $now, $now]);
- $id = (int) $this->pdo->lastInsertId();
- $worker = $this->find($id);
- if ($worker === null) {
- throw new RuntimeException('Inserted worker not found');
- }
- return $worker;
- }
- /**
- * Apply the given changes (only whitelisted columns). Returns before/after
- * snapshots so the caller can drive the AuditLogger.
- *
- * @param array<string,mixed> $changes subset of {name, is_active, default_rtb}
- * @return array{before: Worker, after: Worker}
- */
- public function update(int $id, array $changes): array
- {
- $before = $this->find($id);
- if ($before === null) {
- throw new RuntimeException("Worker {$id} not found");
- }
- $changes = array_intersect_key($changes, array_flip(self::UPDATABLE));
- if ($changes === []) {
- return ['before' => $before, 'after' => $before];
- }
- $sets = [];
- $vals = [];
- foreach ($changes as $col => $v) {
- $sets[] = "{$col} = ?";
- if ($col === 'is_active') {
- $vals[] = (bool) $v ? 1 : 0;
- } elseif ($col === 'default_rtb') {
- $vals[] = (float) $v;
- } else {
- $vals[] = (string) $v;
- }
- }
- $sets[] = 'updated_at = ?';
- $vals[] = gmdate('Y-m-d\TH:i:s\Z');
- $vals[] = $id;
- $stmt = $this->pdo->prepare(
- 'UPDATE workers SET ' . implode(', ', $sets) . ' WHERE id = ?'
- );
- $stmt->execute($vals);
- $after = $this->find($id) ?? $before;
- return ['before' => $before, 'after' => $after];
- }
- /**
- * @param array<string,mixed> $row
- */
- private static function hydrate(array $row): Worker
- {
- return new Worker(
- id: (int) $row['id'],
- name: (string) $row['name'],
- isActive: ((int) $row['is_active']) === 1,
- defaultRtb: (float) $row['default_rtb'],
- createdAt: (string) $row['created_at'],
- updatedAt: (string) $row['updated_at'],
- );
- }
- }
|