Category Entity (Thematic Lists)
Last Updated: 2026-03-09 Version: 2.0 (Consolidated)
Category entities (represented as "Thematic Lists" in the UI) provide the organizational backbone for the Knowledge Hub. They group related knowledge items and define the scope for periodic digests.
📘 Core Purpose
Categories serve several architectural roles:
- Taxonomy: Logical grouping of knowledge items (e.g., "Backend", "Frontend", "HR").
- Audience Definition: Each category has a set of
targetGroupsthat determine who receives updates about its content. - Digest Scope: Used by
NotionDigestServiceto aggregate recent additions into weekly or monthly reports. - AI Template Context: Categories can override global AI summary templates to provide more domain-specific instructions.
🏗️ Data Architecture
Notion Mapping
Categories are synced with a master "Categories" database in Notion.
- Name: The primary label (e.g., "Architecture").
- Icon: A Slack-compatible emoji (default:
:file_folder:). - Notion ID: Unique identifier for bidirectional sync.
- Sort Order: Determines the display priority in Slack Home and Notion views.
Relationships
- Knowledges: A
OneToManyrelationship toKnowledgeitems. - PromptTemplates: A
OneToManyrelationship to role-specific overrides.
⚖️ Business Logic
Active Status
Categories can be toggled via isActive. Inactive categories:
- Are hidden from Slack Home.
- Do not trigger new AI summary generations for their items.
- Are excluded from automatic sync cycles unless explicitly requested.
Membership Management
The entity provides fluent methods for managing knowledge items (addKnowledge, removeKnowledge), ensuring that the inverse side of the relationship is always kept in sync.
Technical Details
- Location:
src/Entity/Category.php - Repository:
src/Repository/CategoryRepository.php
NOTE
When a category is deleted in Notion, the local system marks it as isActive = false rather than hard-deleting to preserve history for existing knowledge items.
Properties
Core Properties
| Property | Type | Required | Description |
|---|---|---|---|
id | int | Auto | Primary key |
name | string | Yes | Category name (2-255 chars) |
description | string | No | Category description (max 255 chars) |
icon | string | No | Emoji icon (default: 📁) |
isActive | bool | No | Whether category is active (default: true) |
sortOrder | int | No | Display order (default: 0) |
createdAt | DateTimeImmutable | Auto | Creation timestamp |
Relationships
| Property | Type | Description |
|---|---|---|
knowledges | Collection<Knowledge> | OneToMany relationship to Knowledge items |
Metadata
| Property | Type | Description |
|---|---|---|
targetGroups | array | Target audience groups for this category |
Notion Integration
| Property | Type | Description |
|---|---|---|
notionId | string | Notion database ID |
lastSyncedAt | DateTimeImmutable | Last sync timestamp |
notionLastEditedAt | DateTimeImmutable | Last edit in Notion |
Validation Rules
Name
- Required: Cannot be blank
- Min Length: 2 characters
- Max Length: 255 characters
Description
- Max Length: 255 characters
Usage Examples
Creating a Category
$category = new Category();
$category->setName('Development');
$category->setDescription('Software development resources');
$category->setIcon(':computer:');
$category->setIsActive(true);
$category->setSortOrder(10);
$entityManager->persist($category);
$entityManager->flush();With Target Groups
$category = new Category();
$category->setName('Design');
$category->setDescription('Design resources and guidelines');
$category->setIcon(':art:');
$category->setTargetGroups([
['name' => 'designers', 'icon' => ':art:'],
['name' => 'frontend-developers', 'icon' => ':computer:']
]);Managing Knowledge Items
$category = new Category();
$category->setName('DevOps');
$knowledge1 = new Knowledge();
$knowledge1->setTitle('Docker Tutorial');
$knowledge2 = new Knowledge();
$knowledge2->setTitle('Kubernetes Guide');
// Add knowledge to category
$category->addKnowledge($knowledge1);
$category->addKnowledge($knowledge2);
// Access all knowledge in category
foreach ($category->getKnowledges() as $knowledge) {
echo $knowledge->getTitle();
}
// Remove knowledge from category
$category->removeKnowledge($knowledge1);Deactivating a Category
$category = $categoryRepository->find(1);
$category->setIsActive(false);
$entityManager->flush();Repository Methods
The CategoryRepository provides custom query methods:
// Find active categories
$activeCategories = $repository->findActive();
// Find by sort order
$orderedCategories = $repository->findAllOrdered();
// Find with knowledge count
$categoriesWithCount = $repository->findWithKnowledgeCount();
// Find by target group
$categories = $repository->findByTargetGroup('developers');Business Logic
Knowledge Management
public function addKnowledge(Knowledge $knowledge): static
{
if (!$this->knowledges->contains($knowledge)) {
$this->knowledges->add($knowledge);
$knowledge->setCategory($this);
}
return $this;
}
public function removeKnowledge(Knowledge $knowledge): static
{
if ($this->knowledges->removeElement($knowledge)) {
if ($knowledge->getCategory() === $this) {
$knowledge->setCategory(null);
}
}
return $this;
}Activation/Deactivation
public function activate(): void
{
$this->isActive = true;
}
public function deactivate(): void
{
$this->isActive = false;
}
public function isActive(): bool
{
return $this->isActive ?? true;
}Sorting
public function moveUp(): void
{
$this->sortOrder = max(0, $this->sortOrder - 1);
}
public function moveDown(): void
{
$this->sortOrder++;
}Common Queries
Get All Active Categories with Knowledge Count
$qb = $repository->createQueryBuilder('c')
->select('c', 'COUNT(k.id) as knowledgeCount')
->leftJoin('c.knowledges', 'k')
->where('c.isActive = :active')
->setParameter('active', true)
->groupBy('c.id')
->orderBy('c.sortOrder', 'ASC');
$results = $qb->getQuery()->getResult();Find Categories by Target Group
$qb = $repository->createQueryBuilder('c')
->where('JSON_CONTAINS(c.targetGroups, :targetGroup) = 1')
->setParameter('targetGroup', json_encode(['name' => 'developers']))
->orderBy('c.name', 'ASC');
$categories = $qb->getQuery()->getResult();Get Most Popular Categories
$qb = $repository->createQueryBuilder('c')
->select('c', 'COUNT(k.id) as HIDDEN knowledgeCount')
->leftJoin('c.knowledges', 'k')
->where('c.isActive = :active')
->setParameter('active', true)
->groupBy('c.id')
->orderBy('knowledgeCount', 'DESC')
->setMaxResults(10);
$popularCategories = $qb->getQuery()->getResult();API Response Example
{
"id": 1,
"name": "Development",
"description": "Software development resources",
"icon": ":computer:",
"isActive": true,
"sortOrder": 10,
"targetGroups": [
{
"name": "developers",
"icon": ":computer:"
}
],
"knowledgeCount": 42,
"createdAt": "2024-01-01T00:00:00+00:00"
}Related Documentation
- Knowledge Entity
- Entity Relationships
- Category Service
- Category API