Skip to content

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 targetGroups that determine who receives updates about its content.
  • Digest Scope: Used by NotionDigestService to 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 OneToMany relationship to Knowledge items.
  • PromptTemplates: A OneToMany relationship 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

PropertyTypeRequiredDescription
idintAutoPrimary key
namestringYesCategory name (2-255 chars)
descriptionstringNoCategory description (max 255 chars)
iconstringNoEmoji icon (default: 📁)
isActiveboolNoWhether category is active (default: true)
sortOrderintNoDisplay order (default: 0)
createdAtDateTimeImmutableAutoCreation timestamp

Relationships

PropertyTypeDescription
knowledgesCollection<Knowledge>OneToMany relationship to Knowledge items

Metadata

PropertyTypeDescription
targetGroupsarrayTarget audience groups for this category

Notion Integration

PropertyTypeDescription
notionIdstringNotion database ID
lastSyncedAtDateTimeImmutableLast sync timestamp
notionLastEditedAtDateTimeImmutableLast 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

php
$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

php
$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

php
$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

php
$category = $categoryRepository->find(1);
$category->setIsActive(false);
$entityManager->flush();

Repository Methods

The CategoryRepository provides custom query methods:

php
// 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

php
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

php
public function activate(): void
{
    $this->isActive = true;
}

public function deactivate(): void
{
    $this->isActive = false;
}

public function isActive(): bool
{
    return $this->isActive ?? true;
}

Sorting

php
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

php
$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

php
$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();
php
$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

json
{
  "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"
}