|null Cached canonical forms */ private ?array $canonicalCache = null; public function __construct(PDO $pdo) { $this->db = $pdo; } /** * {@inheritDoc} */ public function findAll(bool $activeOnly = true, ?string $category = null): array { $sql = 'SELECT * FROM stopwords WHERE 1=1'; $params = []; if ($activeOnly) { $sql .= ' AND is_active = 1'; } if ($category !== null) { $sql .= ' AND category = :category'; $params['category'] = $category; } $sql .= ' ORDER BY word ASC'; $stmt = $this->db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } /** * {@inheritDoc} */ public function find(int $id): ?array { $stmt = $this->db->prepare('SELECT * FROM stopwords WHERE id = :id'); $stmt->execute(['id' => $id]); $result = $stmt->fetch(); return $result === false ? null : $result; } /** * {@inheritDoc} */ public function getCanonicalForms(bool $activeOnly = true): array { if ($this->canonicalCache !== null && $activeOnly) { return $this->canonicalCache; } $sql = 'SELECT canonical_form FROM stopwords'; if ($activeOnly) { $sql .= ' WHERE is_active = 1'; } $stmt = $this->db->query($sql); $forms = $stmt->fetchAll(PDO::FETCH_COLUMN); if ($activeOnly) { $this->canonicalCache = $forms; } return $forms; } /** * {@inheritDoc} */ public function isStopword(string $word): bool { $canonical = $this->normalize($word); $forms = $this->getCanonicalForms(); return in_array($canonical, $forms, true); } /** * {@inheritDoc} */ public function create(array $data): int { $canonical = $this->normalize($data['word'] ?? ''); $stmt = $this->db->prepare( 'INSERT INTO stopwords (word, canonical_form, category, reason, is_active) VALUES (:word, :canonical, :category, :reason, :active)' ); $stmt->execute([ 'word' => $data['word'], 'canonical' => $canonical, 'category' => $data['category'] ?? 'generic', 'reason' => $data['reason'] ?? null, 'active' => $data['is_active'] ?? 1, ]); $this->canonicalCache = null; return (int) $this->db->lastInsertId(); } /** * {@inheritDoc} */ public function update(int $id, array $data): bool { $sets = []; $params = ['id' => $id]; if (isset($data['word'])) { $sets[] = 'word = :word'; $sets[] = 'canonical_form = :canonical'; $params['word'] = $data['word']; $params['canonical'] = $this->normalize($data['word']); } if (isset($data['category'])) { $sets[] = 'category = :category'; $params['category'] = $data['category']; } if (array_key_exists('reason', $data)) { $sets[] = 'reason = :reason'; $params['reason'] = $data['reason']; } if (isset($data['is_active'])) { $sets[] = 'is_active = :active'; $params['active'] = $data['is_active']; } if ($sets === []) { return false; } $sql = 'UPDATE stopwords SET ' . implode(', ', $sets) . ' WHERE id = :id'; $stmt = $this->db->prepare($sql); $result = $stmt->execute($params); $this->canonicalCache = null; return $result && $stmt->rowCount() > 0; } /** * {@inheritDoc} */ public function delete(int $id): bool { $stmt = $this->db->prepare('DELETE FROM stopwords WHERE id = :id'); $result = $stmt->execute(['id' => $id]); $this->canonicalCache = null; return $result && $stmt->rowCount() > 0; } /** * {@inheritDoc} */ public function toggleActive(int $id): bool { $stmt = $this->db->prepare( 'UPDATE stopwords SET is_active = NOT is_active WHERE id = :id' ); $result = $stmt->execute(['id' => $id]); $this->canonicalCache = null; return $result && $stmt->rowCount() > 0; } /** * {@inheritDoc} */ public function getStats(): array { $stmt = $this->db->query( 'SELECT category, COUNT(*) as count, SUM(is_active) as active FROM stopwords GROUP BY category' ); $stats = ['total' => 0, 'active' => 0, 'by_category' => []]; foreach ($stmt->fetchAll() as $row) { $stats['by_category'][$row['category']] = [ 'count' => (int) $row['count'], 'active' => (int) $row['active'], ]; $stats['total'] += (int) $row['count']; $stats['active'] += (int) $row['active']; } return $stats; } /** * Normalize word to canonical form. */ private function normalize(string $word): string { $result = mb_strtolower(trim($word)); // German umlauts $replacements = [ 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', ]; $result = str_replace(array_keys($replacements), array_values($replacements), $result); // Remove special chars, keep only alphanumeric and underscore $result = preg_replace('/[^a-z0-9_]/', '', $result); return $result ?? ''; } }