|null */ public ?array $userRow = null; /** lockout_until renvoye pour la porte de throttling IP ; null = pas de verrou. */ public ?string $ipLockoutUntil = null; /** * Compteur login_throttle relu apres l'upsert atomique (sert au calcul du * backoff IP en PHP) ; null => 1 par defaut cote service. * * @var array|null */ public ?array $throttleRow = null; /** * Reponse de la recherche par token de reinitialisation (12.3) ; null = aucun. * * @var array|null */ public ?array $resetUserRow = null; /** * Reponse de la recherche par email (phase demande de reinitialisation) ; null = inconnu. * * @var array|null */ public ?array $emailLookupRow = null; /** * Reponse de la verification is_active du SessionGuard (RG-T02) ; null = absent. * * @var array|null */ public ?array $guardUserRow = null; /** Si non nul, execute() leve cette exception (simulation panne DB -> fail-closed). */ public ?RuntimeException $failOnExecute = null; /** @var list}> */ public array $writes = []; /** @var list */ public array $transactionEvents = []; public function fetch(string $sql, array $params = []): ?array { if (str_contains($sql, 'FROM user u JOIN role')) { return $this->userRow; } if (str_contains($sql, 'password_reset_token_hash')) { return $this->resetUserRow; } if (str_contains($sql, 'SELECT id FROM user WHERE email')) { return $this->emailLookupRow; } if (str_contains($sql, 'SELECT is_active FROM user WHERE id')) { return $this->guardUserRow; } if (str_contains($sql, 'SELECT lockout_until FROM login_throttle')) { return ['lockout_until' => $this->ipLockoutUntil]; } if (str_contains($sql, 'SELECT failed_attempts FROM login_throttle')) { return $this->throttleRow; } return null; } public function fetchAll(string $sql, array $params = []): array { return []; } public function execute(string $sql, array $params = []): int { if ($this->failOnExecute !== null) { throw $this->failOnExecute; } $this->writes[] = ['sql' => $sql, 'params' => $params]; return 1; } public function transaction(callable $fn): void { $this->transactionEvents[] = 'begin'; try { $fn($this); $this->transactionEvents[] = 'commit'; } catch (\Throwable $exception) { $this->transactionEvents[] = 'rollback'; throw $exception; } } public function wrote(string $needle): bool { foreach ($this->writes as $write) { if (str_contains($write['sql'], $needle)) { return true; } } return false; } /** * Codes d'action audit_log inseres (dans l'ordre). * * @return list */ public function auditActions(): array { $codes = []; foreach ($this->writes as $write) { if (str_contains($write['sql'], 'INSERT INTO audit_log')) { $code = $write['params']['code'] ?? null; $codes[] = is_string($code) ? $code : ''; } } return $codes; } }