{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Persistence\/StopwordRepository.php",
"content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\Persistence;\n\n\/\/ @responsibility: CRUD-Operationen für Stopwords (Entity-Extraction Filter)\n\nuse Domain\\Repository\\StopwordRepositoryInterface;\nuse PDO;\n\nfinal class StopwordRepository implements StopwordRepositoryInterface\n{\n private PDO $db;\n\n \/** @var array<string>|null Cached canonical forms *\/\n private ?array $canonicalCache = null;\n\n public function __construct(PDO $pdo)\n {\n $this->db = $pdo;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function findAll(bool $activeOnly = true, ?string $category = null): array\n {\n $sql = 'SELECT * FROM stopwords WHERE 1=1';\n $params = [];\n\n if ($activeOnly) {\n $sql .= ' AND is_active = 1';\n }\n\n if ($category !== null) {\n $sql .= ' AND category = :category';\n $params['category'] = $category;\n }\n\n $sql .= ' ORDER BY word ASC';\n\n $stmt = $this->db->prepare($sql);\n $stmt->execute($params);\n\n return $stmt->fetchAll();\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function find(int $id): ?array\n {\n $stmt = $this->db->prepare('SELECT * FROM stopwords WHERE id = :id');\n $stmt->execute(['id' => $id]);\n $result = $stmt->fetch();\n\n return $result === false ? null : $result;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function getCanonicalForms(bool $activeOnly = true): array\n {\n if ($this->canonicalCache !== null && $activeOnly) {\n return $this->canonicalCache;\n }\n\n $sql = 'SELECT canonical_form FROM stopwords';\n if ($activeOnly) {\n $sql .= ' WHERE is_active = 1';\n }\n\n $stmt = $this->db->query($sql);\n $forms = $stmt->fetchAll(PDO::FETCH_COLUMN);\n\n if ($activeOnly) {\n $this->canonicalCache = $forms;\n }\n\n return $forms;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function isStopword(string $word): bool\n {\n $canonical = $this->normalize($word);\n $forms = $this->getCanonicalForms();\n\n return in_array($canonical, $forms, true);\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function create(array $data): int\n {\n $canonical = $this->normalize($data['word'] ?? '');\n\n $stmt = $this->db->prepare(\n 'INSERT INTO stopwords (word, canonical_form, category, reason, is_active)\n VALUES (:word, :canonical, :category, :reason, :active)'\n );\n\n $stmt->execute([\n 'word' => $data['word'],\n 'canonical' => $canonical,\n 'category' => $data['category'] ?? 'generic',\n 'reason' => $data['reason'] ?? null,\n 'active' => $data['is_active'] ?? 1,\n ]);\n\n $this->canonicalCache = null;\n\n return (int) $this->db->lastInsertId();\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function update(int $id, array $data): bool\n {\n $sets = [];\n $params = ['id' => $id];\n\n if (isset($data['word'])) {\n $sets[] = 'word = :word';\n $sets[] = 'canonical_form = :canonical';\n $params['word'] = $data['word'];\n $params['canonical'] = $this->normalize($data['word']);\n }\n\n if (isset($data['category'])) {\n $sets[] = 'category = :category';\n $params['category'] = $data['category'];\n }\n\n if (array_key_exists('reason', $data)) {\n $sets[] = 'reason = :reason';\n $params['reason'] = $data['reason'];\n }\n\n if (isset($data['is_active'])) {\n $sets[] = 'is_active = :active';\n $params['active'] = $data['is_active'];\n }\n\n if ($sets === []) {\n return false;\n }\n\n $sql = 'UPDATE stopwords SET ' . implode(', ', $sets) . ' WHERE id = :id';\n $stmt = $this->db->prepare($sql);\n $result = $stmt->execute($params);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function delete(int $id): bool\n {\n $stmt = $this->db->prepare('DELETE FROM stopwords WHERE id = :id');\n $result = $stmt->execute(['id' => $id]);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function toggleActive(int $id): bool\n {\n $stmt = $this->db->prepare(\n 'UPDATE stopwords SET is_active = NOT is_active WHERE id = :id'\n );\n $result = $stmt->execute(['id' => $id]);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function getStats(): array\n {\n $stmt = $this->db->query(\n 'SELECT category, COUNT(*) as count, SUM(is_active) as active\n FROM stopwords GROUP BY category'\n );\n\n $stats = ['total' => 0, 'active' => 0, 'by_category' => []];\n\n foreach ($stmt->fetchAll() as $row) {\n $stats['by_category'][$row['category']] = [\n 'count' => (int) $row['count'],\n 'active' => (int) $row['active'],\n ];\n $stats['total'] += (int) $row['count'];\n $stats['active'] += (int) $row['active'];\n }\n\n return $stats;\n }\n\n \/**\n * Normalize word to canonical form.\n *\/\n private function normalize(string $word): string\n {\n $result = mb_strtolower(trim($word));\n\n \/\/ German umlauts\n $replacements = [\n 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',\n ];\n $result = str_replace(array_keys($replacements), array_values($replacements), $result);\n\n \/\/ Remove special chars, keep only alphanumeric and underscore\n $result = preg_replace('\/[^a-z0-9_]\/', '', $result);\n\n return $result ?? '';\n }\n}\n"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Persistence\/StopwordRepository.php",
"content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\Persistence;\n\n\/\/ @responsibility: CRUD-Operationen für Stopwords (Entity-Extraction Filter)\n\nuse Domain\\Repository\\StopwordRepositoryInterface;\nuse PDO;\n\nfinal class StopwordRepository implements StopwordRepositoryInterface\n{\n private PDO $db;\n\n \/** @var array<string>|null Cached canonical forms *\/\n private ?array $canonicalCache = null;\n\n public function __construct(PDO $pdo)\n {\n $this->db = $pdo;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function findAll(bool $activeOnly = true, ?string $category = null): array\n {\n $sql = 'SELECT * FROM stopwords WHERE 1=1';\n $params = [];\n\n if ($activeOnly) {\n $sql .= ' AND is_active = 1';\n }\n\n if ($category !== null) {\n $sql .= ' AND category = :category';\n $params['category'] = $category;\n }\n\n $sql .= ' ORDER BY word ASC';\n\n $stmt = $this->db->prepare($sql);\n $stmt->execute($params);\n\n return $stmt->fetchAll();\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function find(int $id): ?array\n {\n $stmt = $this->db->prepare('SELECT * FROM stopwords WHERE id = :id');\n $stmt->execute(['id' => $id]);\n $result = $stmt->fetch();\n\n return $result === false ? null : $result;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function getCanonicalForms(bool $activeOnly = true): array\n {\n if ($this->canonicalCache !== null && $activeOnly) {\n return $this->canonicalCache;\n }\n\n $sql = 'SELECT canonical_form FROM stopwords';\n if ($activeOnly) {\n $sql .= ' WHERE is_active = 1';\n }\n\n $stmt = $this->db->query($sql);\n $forms = $stmt->fetchAll(PDO::FETCH_COLUMN);\n\n if ($activeOnly) {\n $this->canonicalCache = $forms;\n }\n\n return $forms;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function isStopword(string $word): bool\n {\n $canonical = $this->normalize($word);\n $forms = $this->getCanonicalForms();\n\n return in_array($canonical, $forms, true);\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function create(array $data): int\n {\n $canonical = $this->normalize($data['word'] ?? '');\n\n $stmt = $this->db->prepare(\n 'INSERT INTO stopwords (word, canonical_form, category, reason, is_active)\n VALUES (:word, :canonical, :category, :reason, :active)'\n );\n\n $stmt->execute([\n 'word' => $data['word'],\n 'canonical' => $canonical,\n 'category' => $data['category'] ?? 'generic',\n 'reason' => $data['reason'] ?? null,\n 'active' => $data['is_active'] ?? 1,\n ]);\n\n $this->canonicalCache = null;\n\n return (int) $this->db->lastInsertId();\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function update(int $id, array $data): bool\n {\n $sets = [];\n $params = ['id' => $id];\n\n if (isset($data['word'])) {\n $sets[] = 'word = :word';\n $sets[] = 'canonical_form = :canonical';\n $params['word'] = $data['word'];\n $params['canonical'] = $this->normalize($data['word']);\n }\n\n if (isset($data['category'])) {\n $sets[] = 'category = :category';\n $params['category'] = $data['category'];\n }\n\n if (array_key_exists('reason', $data)) {\n $sets[] = 'reason = :reason';\n $params['reason'] = $data['reason'];\n }\n\n if (isset($data['is_active'])) {\n $sets[] = 'is_active = :active';\n $params['active'] = $data['is_active'];\n }\n\n if ($sets === []) {\n return false;\n }\n\n $sql = 'UPDATE stopwords SET ' . implode(', ', $sets) . ' WHERE id = :id';\n $stmt = $this->db->prepare($sql);\n $result = $stmt->execute($params);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function delete(int $id): bool\n {\n $stmt = $this->db->prepare('DELETE FROM stopwords WHERE id = :id');\n $result = $stmt->execute(['id' => $id]);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function toggleActive(int $id): bool\n {\n $stmt = $this->db->prepare(\n 'UPDATE stopwords SET is_active = NOT is_active WHERE id = :id'\n );\n $result = $stmt->execute(['id' => $id]);\n\n $this->canonicalCache = null;\n\n return $result && $stmt->rowCount() > 0;\n }\n\n \/**\n * {@inheritDoc}\n *\/\n public function getStats(): array\n {\n $stmt = $this->db->query(\n 'SELECT category, COUNT(*) as count, SUM(is_active) as active\n FROM stopwords GROUP BY category'\n );\n\n $stats = ['total' => 0, 'active' => 0, 'by_category' => []];\n\n foreach ($stmt->fetchAll() as $row) {\n $stats['by_category'][$row['category']] = [\n 'count' => (int) $row['count'],\n 'active' => (int) $row['active'],\n ];\n $stats['total'] += (int) $row['count'];\n $stats['active'] += (int) $row['active'];\n }\n\n return $stats;\n }\n\n \/**\n * Normalize word to canonical form.\n *\/\n private function normalize(string $word): string\n {\n $result = mb_strtolower(trim($word));\n\n \/\/ German umlauts\n $replacements = [\n 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',\n ];\n $result = str_replace(array_keys($replacements), array_values($replacements), $result);\n\n \/\/ Remove special chars, keep only alphanumeric and underscore\n $result = preg_replace('\/[^a-z0-9_]\/', '', $result);\n\n return $result ?? '';\n }\n}\n",
"structuredPatch": [],
"originalFile": null
}
}