5.6.6 Activity Groups Entity Reference
Last Updated: December 3, 2025
Status: Complete
Scope: Activities Plugin - ActivityGroup Entity
Comprehensive technical reference for the ActivityGroup entity, covering data model, relationships, field definitions, and integration patterns with the Activities system.
Table of Contents
- ActivityGroup Entity Overview
- Database Schema
- Entity Properties
- Relationships
- Mass Assignment Security
- Usage Examples
- Integration Points
- Performance Considerations
ActivityGroup Entity Overview
The ActivityGroup entity provides categorical organization for related activities within the Activities plugin. Activity groups serve as logical containers for activities, enabling administrative organization by type, department, skill level, or other meaningful categorizations.
Core Responsibilities:
- Provide categorical organization for related activities
- Enable simplified activity discovery and navigation
- Support administrative management without restricting activity functionality
- Maintain audit trail for group changes
- Provide foundation for future group-level features
Location: plugins/Activities/src/Model/Entity/ActivityGroup.php
Extends: App\Model\Entity\BaseEntity
Table Class: Activities\Model\Table\ActivityGroupsTable
Database Schema
Table: activities_activity_groups
The ActivityGroup entity maps to the activities_activity_groups database table with the following structure:
| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
id |
INT(11) | NO | Auto | Primary key, auto-incrementing |
name |
VARCHAR(255) | NO | Unique activity group identifier | |
created |
DATETIME | NO | Creation timestamp | |
created_by |
INT(11) | YES | NULL | Creator member ID |
modified |
DATETIME | YES | NULL | Last modification timestamp |
modified_by |
INT(11) | YES | NULL | Last modifier member ID |
deleted |
DATETIME | YES | NULL | Soft deletion timestamp |
Indexes:
- PRIMARY:
id - UNIQUE:
name - INDEX:
deleted(for soft deletion queries)
Special Features:
- Soft Deletion: Trash behavior preserves historical data while logically removing records
- Audit Trail: Timestamp and Footprint behaviors track all changes with user attribution
- Plugin Scoping: Table name uses plugin prefix for namespace isolation
Entity Properties
Core Properties
id
- Type:
int - Required: No (auto-generated)
- Behavior: Auto-increment on insert
- Description: Primary key identifier
- Notes: Set by database on creation
name
- Type:
string - Required: Yes
- Max Length: 255 characters
- Validation: Unique constraint, non-empty
- Description: Display name for the activity group
- Examples:
- “Combat Activities”
- “Administrative Activities”
- “Arts & Sciences”
- “Support Activities”
- Usage: Serves as the human-readable identifier in navigation and lists
Audit Trail Properties
created
- Type:
\Cake\I18n\DateTime - Required: Yes
- Set By: Timestamp behavior on insert
- Description: Activity group creation timestamp
created_by
- Type:
int|null - Description: Member ID of group creator
- Set By: Footprint behavior on insert
modified
- Type:
\Cake\I18n\DateTime|null - Set By: Timestamp behavior on update
- Description: Last activity group modification timestamp
modified_by
- Type:
int|null - Description: Member ID of last modifier
- Set By: Footprint behavior on update
deleted
- Type:
\Cake\I18n\DateTime|null - Description: Soft deletion timestamp
- Behavior: Trash behavior - NULL for active records, timestamp for deleted records
- Note: Automatically filtered from default queries via Trash behavior
Relationships
Child Relationships (hasMany)
activities
- Related Entity:
Activities\Model\Entity\Activity - Foreign Key:
activity_group_id - Description: All activities in this group
- Access:
$activityGroup->activities - Query: All associated activities regardless of deletion status
- Load Strategy: Optional eager loading with
contain()
Example Query:
$groupWithActivities = $activityGroupsTable->find()
->contain(['Activities'])
->where(['ActivityGroups.id' => $groupId])
->first();
foreach ($groupWithActivities->activities as $activity) {
echo $activity->name;
}
Mass Assignment Security
The ActivityGroup entity uses CakePHP’s accessible field system to prevent mass assignment vulnerabilities.
Accessible Fields
The following fields can be safely mass assigned through newEntity() or patchEntity():
name- Group display name
Protected Fields
The following fields are NOT accessible via mass assignment:
id- Primary key protectioncreated- Timestamp protection (set by behavior)modified- Timestamp protection (set by behavior)created_by- Creator tracking protectionmodified_by- Modifier tracking protectiondeleted- Soft deletion protection
Usage Examples
Creating Activity Groups
// Create a new activity group for combat activities
$combatGroup = $activityGroupsTable->newEntity([
'name' => 'Combat Activities'
]);
$activityGroupsTable->save($combatGroup);
// Create a group for administrative activities
$adminGroup = $activityGroupsTable->newEntity([
'name' => 'Administrative Activities'
]);
$activityGroupsTable->save($adminGroup);
// Create a group for arts and sciences
$artsGroup = $activityGroupsTable->newEntity([
'name' => 'Arts & Sciences'
]);
$activityGroupsTable->save($artsGroup);
Organizing Activities by Group
// Assign activities to groups during creation
$activity = $activitiesTable->newEntity([
'name' => 'Heavy Weapons Combat',
'activity_group_id' => $combatGroup->id,
'description' => 'Authorization for heavy weapons combat activities'
]);
// Query activities by group
$combatActivities = $activitiesTable->find()
->where(['activity_group_id' => $combatGroup->id])
->all();
// Display activities in group
foreach ($combatActivities as $activity) {
echo "- {$activity->name}\n";
}
Querying Groups with Activities
// Load group with all associated activities
$groupWithActivities = $activityGroupsTable->find()
->contain(['Activities' => [
'sort' => ['Activities.name' => 'ASC']
]])
->where(['ActivityGroups.id' => $groupId])
->first();
// Display group and activities
echo "Group: {$groupWithActivities->name}\n";
foreach ($groupWithActivities->activities as $activity) {
echo " - {$activity->name}\n";
}
Administrative Group Management
// Get all groups with activity counts
$groupsWithStats = $activityGroupsTable->find()
->contain(['Activities'])
->orderBy(['ActivityGroups.name' => 'ASC'])
->all()
->map(function($group) {
return [
'group' => $group,
'activity_count' => count($group->activities)
];
});
// Display group statistics
foreach ($groupsWithStats as $item) {
echo "{$item['group']->name}: {$item['activity_count']} activities\n";
}
Reorganizing Activities Between Groups
// Move all activities from one group to another
$sourceGroup = $activityGroupsTable->get($sourceGroupId, ['contain' => ['Activities']]);
$targetGroupId = $newGroupId;
foreach ($sourceGroup->activities as $activity) {
$activity->activity_group_id = $targetGroupId;
$activitiesTable->save($activity);
}
Group-Based Navigation Structure
// Build navigation menu organized by activity groups
$navigationGroups = $activityGroupsTable->find()
->contain(['Activities' => [
'conditions' => ['Activities.deleted IS' => null],
'sort' => ['Activities.name' => 'ASC']
]])
->where(['ActivityGroups.deleted IS' => null])
->orderBy(['ActivityGroups.name' => 'ASC'])
->all();
$menuStructure = [];
foreach ($navigationGroups as $group) {
$menuStructure[$group->name] = [];
foreach ($group->activities as $activity) {
$menuStructure[$group->name][] = [
'title' => $activity->name,
'url' => ['plugin' => 'Activities', 'controller' => 'Activities', 'action' => 'view', $activity->id]
];
}
}
Group-Based Reporting
// Generate activity participation report by group
$participationByGroup = $activityGroupsTable->find()
->contain(['Activities' => [
'contain' => ['CurrentAuthorizations']
]])
->orderBy(['ActivityGroups.name' => 'ASC'])
->all()
->map(function($group) {
$totalAuthorizations = 0;
foreach ($group->activities as $activity) {
$totalAuthorizations += count($activity->current_authorizations ?? []);
}
return [
'group_name' => $group->name,
'activity_count' => count($group->activities),
'authorization_count' => $totalAuthorizations
];
});
// Display report
foreach ($participationByGroup as $item) {
echo "{$item['group_name']}: ";
echo "{$item['activity_count']} activities, ";
echo "{$item['authorization_count']} authorizations\n";
}
Bulk Operations
// Update multiple groups
$updates = [
['id' => 1, 'name' => 'Combat & Martial Activities'],
['id' => 2, 'name' => 'Administrative & Service Activities'],
['id' => 3, 'name' => 'Arts, Sciences & Research']
];
foreach ($updates as $data) {
$group = $activityGroupsTable->get($data['id']);
$group = $activityGroupsTable->patchEntity($group, $data);
$activityGroupsTable->save($group);
}
// Soft delete unused groups (preserving history)
$unusedGroups = $activityGroupsTable->find()
->leftJoinWith('Activities')
->where(['Activities.id IS' => null])
->all();
foreach ($unusedGroups as $group) {
$activityGroupsTable->delete($group); // Soft delete via Trash behavior
}
Integration Points
Activities Management
- Primary Use: Organizing activities into logical categories
- Integration: Activity entities reference ActivityGroup via
activity_group_id - Scope: Kingdom-wide activity organization
- Impact: Affects activity discovery, navigation, and administrative management
Administrative Interface
- Activity Groups Controller: CRUD operations for group management
- Grid Display: DataverseGrid displays groups with filtering and export
- Permission-Based Access: Authorization policies control access to group operations
- Activity Management: Groups managed alongside activity configuration
Navigation System
- Sidebar Navigation: Groups appear in navigation menu
- Activity Discovery: Users browse activities organized by group
- Breadcrumb Support: Groups provide navigation context
- Dynamic Menus: Group-based filtering for activity display
Reporting System
- Group-Level Analytics: Reports generate statistics by group
- Activity Participation: Aggregates authorization counts by group
- Compliance Tracking: Groups help organize authorization tracking
- Administrative Oversight: Group-based reporting for administrative review
Search and Filtering
- Activity Discovery: Search filters activities by group
- Administrative Queries: Groups provide organizational query filters
- Data Organization: Groups enable systematic activity browsing
Performance Considerations
Query Optimization
Efficient Loading:
- Use
contain()to eager load activities when needed - Avoid N+1 queries by loading relationships once
Example - Efficient Query:
// Good: Single query with eager loading
$groupsWithActivities = $activityGroupsTable->find()
->contain(['Activities'])
->all();
Example - Inefficient Query:
// Bad: N+1 queries - one per group
$groups = $activityGroupsTable->find()->all();
foreach ($groups as $group) {
$activities = $group->activities; // Creates query for each group
}
Index Strategy
- Primary Key: Indexed for fast lookups
- Name Field: Indexed for search operations
- Soft Deletion: Trash behavior indexes
deletedcolumn for efficient filtering
Caching Opportunities
- Activity Group List: Relatively stable and suitable for caching
- Navigation: Group-based navigation can be cached
- Filter Options: Available groups for filter dropdowns can be cached
Schema Simplicity
- Minimal Fields: Simple schema reduces query complexity
- Few Relationships: Single hasMany relationship minimizes JOIN complexity
- Soft Deletion: Trash behavior adds minimal overhead
Architecture Notes
BaseEntity Inheritance
ActivityGroup extends App\Model\Entity\BaseEntity providing:
Audit Trail Features:
- Automatic
createdandmodifiedtimestamps - User attribution via
created_byandmodified_by - Complete change tracking through Footprint behavior
Soft Deletion:
- Trash behavior for logical deletion
- Historical data preservation
- Automatic filtering from default queries
Branch Scoping:
- Compatible with KMP’s branch-based authorization
- Supports branch-specific activity organization
Validation Framework
Validation is enforced through ActivityGroupsTable::validationDefault():
Field Validation:
name: Required, scalar string, maximum 255 characters, non-empty- Ensures data integrity and prevents injection attacks
Validation Triggers:
- Form submission via
newEntity()andpatchEntity() - Manual save operations
- API data validation
Future Extensibility
The simple entity structure supports future enhancements:
Hierarchical Groups: Simple structure allows nested group implementation Group Permissions: Framework ready for group-level authorization features Advanced Categorization: Support for tags, metadata, and complex organization Group Workflows: Ready for group-specific management workflows
References and Related Documentation
Related Entities
- Activity Entity - Activities within groups
- Authorization Entity - Authorizations for activities
Related Documentation
- Activity Groups Controller Reference - Controller implementation
- Activities Plugin Workflows - Authorization lifecycle workflows
- Activities Plugin Architecture - Plugin structure
Source Files
- Entity:
plugins/Activities/src/Model/Entity/ActivityGroup.php - Table:
plugins/Activities/src/Model/Table/ActivityGroupsTable.php - Controller:
plugins/Activities/src/Controller/ActivityGroupsController.php