Skip to the content.

← Back to Table of Contents

3. Architecture

This section provides an overview of the KMP application architecture, explaining its structure, components, and design principles.

3.1 Application Structure

KMP follows the CakePHP directory structure with additional customizations for plugins and services. Understanding this structure is essential for effective development.

Directory Structure Overview

app/
├── config/             # Configuration files
├── src/                # Core application code
│   ├── Application.php # Main application class
│   ├── Command/        # CLI commands
│   ├── Controller/     # Request controllers
│   ├── Event/          # Event listeners
│   ├── Form/           # Form classes
│   ├── Identifier/     # Authentication identifiers
│   ├── KMP/            # KMP-specific core code
│   ├── Mailer/         # Email templates and logic
│   ├── Model/          # Data models and entities
│   ├── Policy/         # Authorization policies
│   ├── Services/       # Business logic services
│   └── View/           # View templates and helpers
├── assets/             # Source asset files
│   ├── css/            # CSS source files
│   └── js/             # JavaScript source files
│       └── controllers/ # Stimulus controllers
├── plugins/            # Application plugins
│   ├── Activities/     # Activities management
│   ├── Awards/         # Awards management
│   ├── Bootstrap/      # UI components
│   ├── GitHubIssueSubmitter/ # User feedback
│   ├── Officers/       # Officers management
│   └── Queue/          # Background processing
├── templates/          # View templates
└── tests/              # Test cases

MVC+ Implementation

KMP extends the traditional MVC pattern with services and events, creating a more modular and maintainable architecture:

graph TD
    Client[Client] --> Controller
    Controller --> Service[Service Layer]
    Controller --> View
    Service --> Model
    Model --> Database[(Database)]
    Service --> Events[Event System]
    Events --> Listeners[Event Listeners]
    View --> Helper[View Helpers]
    View --> Element[UI Elements]
    Controller --> Component[Controller Components]
    Model --> Behavior[Model Behaviors]

3.2 Core Components

KMP extends CakePHP with several core components that provide essential functionality throughout the application.

Application Class

The Application.php class serves as the entry point for the application, handling bootstrapping, middleware configuration, and plugin registration.

Key responsibilities:

Service Layer

KMP implements a service layer pattern to encapsulate business logic:

graph TD
    Controller --> Service
    Service --> Repository[Model/Repository]
    Service --> ExternalAPI[External APIs]
    Service --> Events[Event Dispatching]
    Service --> Cache

Key services include:

Event System

The event system enables loose coupling between components, allowing for extensible architecture:

sequenceDiagram
    participant Component as Component/Controller
    participant EventManager
    participant Listener1 as Plugin Listener
    participant Listener2 as Core Listener
    
    Component->>EventManager: Dispatch Event
    EventManager->>Listener1: Notify
    EventManager->>Listener2: Notify
    Listener1-->>Component: Return Result (optional)
    Listener2-->>Component: Return Result (optional)

Common events include:

StaticHelpers

The StaticHelpers class provides global utility functions throughout the KMP application, particularly for managing application settings:

// Example of StaticHelpers usage
StaticHelpers::getAppSetting("KMP.ShortSiteTitle", "KMP", null, true);

Key features include:

3.3 Plugin System

KMP uses CakePHP’s plugin system extensively to organize features into cohesive, maintainable modules.

Plugin Architecture

graph TD
    Core[Core Application] --> PluginInterface[KMPPluginInterface]
    PluginInterface --> Activities[Activities Plugin]
    PluginInterface --> Officers[Officers Plugin]
    PluginInterface --> Awards[Awards Plugin]
    PluginInterface --> Queue[Queue Plugin]
    PluginInterface --> GitHubIssue[GitHubIssueSubmitter]
    PluginInterface --> Bootstrap[Bootstrap UI]

Each plugin follows a standard structure:

PluginName/
├── config/         # Plugin configuration and migrations
├── src/            # Plugin PHP code
│   ├── PluginNamePlugin.php  # Main plugin class (NamePlugin.php pattern)
│   ├── Controller/ # Plugin controllers
│   ├── Model/      # Plugin models
│   ├── Services/   # Plugin services (including NavigationProvider)
│   ├── Event/      # Event listeners (including CallForCellsHandler)
│   └── View/       # View-related code
├── templates/      # Template files
├── assets/         # Source asset files for the plugin
│   ├── css/        # CSS source files
│   └── js/         # JavaScript source files
│       └── controllers/ # Stimulus controllers (plugin-specific)
└── tests/          # Plugin tests

Note: The main plugin class follows the pattern PluginNamePlugin.php (e.g., ActivitiesPlugin.php, OfficersPlugin.php). For plugin JavaScript, use plugins/PluginName/assets/js/controllers/ for Stimulus controllers.

Plugin Registration

Plugins are registered in config/plugins.php and loaded in the Application.php bootstrap method. Each plugin can specify its migration order for database setup:

'Activities' => [
    'migrationOrder' => 1,
],
'Officers' => [
    'migrationOrder' => 2,
],
'Awards' => [
    'migrationOrder' => 3,
],

Plugin Integration Points

Plugins integrate with the core application through:

  1. NavigationProvider: Registering navigation items for menus
  2. CallForCellsHandler: Registering view cells/tabs for UI integration
  3. Events: Listening for and dispatching events
  4. Services: Exposing APIs for other plugins

3.4 Authentication & Authorization

KMP implements a comprehensive security system based on CakePHP’s Authentication and Authorization plugins.

Authentication Flow

sequenceDiagram
    participant User
    participant AuthMiddleware as Authentication Middleware
    participant AuthService as Authentication Service
    participant Identifier as User Identifier
    participant Controller
    
    User->>AuthMiddleware: HTTP Request
    AuthMiddleware->>AuthService: Authenticate Request
    AuthService->>Identifier: Identify User
    Identifier-->>AuthService: User Entity or null
    AuthService-->>AuthMiddleware: Authentication Result
    AuthMiddleware->>Controller: Authenticated Request
    Controller-->>User: Response

Authentication features:

Authorization System

KMP uses a policy-based authorization system with role-based access control (RBAC):

graph TD
    Request[Request + User] --> AuthMiddleware[Authorization Middleware]
    AuthMiddleware --> AuthService[Authorization Service]
    AuthService --> PolicyResolver[Policy Resolver]
    PolicyResolver --> ControllerPolicy[Controller Policy]
    PolicyResolver --> EntityPolicy[Entity Policy]
    ControllerPolicy --> Decision{Can Access?}
    EntityPolicy --> Decision
    Decision -->|Yes| Controller[Controller Action]
    Decision -->|No| Forbidden[403 Forbidden]

Authorization components:

The authorization flow evaluates whether a user has permission to perform specific actions on resources, checking both static permissions and dynamic policies.

Permission and Policy Scoping

KMP supports fine-grained authorization using permissions and policies, with flexible scoping rules to ensure users only see and do what they are allowed to.

Permission Scoping

Each permission can be scoped in one of three ways:

Policies and Scoping

Policies are mapped to permissions and define what actions a user can perform (e.g., canView, canEdit). The scoping of a permission determines both:

How it works:

Example: Viewing Warrants

Entity Relationship Diagram (ERD)

erDiagram
    Members ||--o{ MemberRoles : "has"
    MemberRoles }o--|| Roles : "grants"
    Roles }o--o{ RolesPermissions : "has"
    RolesPermissions }o--|| Permissions : "grants"
    Permissions ||--o{ PermissionPolicies : "defines"
    PermissionPolicies }o--|| Policies : "maps to"
    MemberRoles }o--|| Branches : "scoped to"

3.5 Model Behaviors

KMP implements custom CakePHP behaviors to provide reusable model functionality across the application. These behaviors encapsulate common data management patterns and ensure consistency in how temporal data, JSON fields, and sortable lists are handled.

Behavior Architecture Overview

graph TD
    Table[Table Class] --> Behavior[Model Behavior]
    Behavior --> Database[(Database)]
    Behavior --> Events[ORM Events]
    Events --> BeforeSave[beforeSave]
    Events --> AfterSave[afterSave]
    Behavior --> Finder[Custom Finders]
    Finder --> Query[Query Builder]

ActiveWindow Behavior

The ActiveWindowBehavior provides temporal filtering capabilities for entities with date-bounded lifecycles. This behavior is essential for managing time-sensitive data throughout KMP.

Key Features

Database Schema Requirements

-- Tables using ActiveWindow behavior must have:
start_on    DATETIME NOT NULL,  -- When record becomes active
expires_on  DATETIME NULL       -- When record expires (NULL = never expires)

Usage Examples

// In Table initialize() method
$this->addBehavior('ActiveWindow');

// Find currently active records
$activeOfficers = $this->Officers->find('current');

// Find upcoming assignments
$upcomingWarrants = $this->Warrants->find('upcoming');

// Historical queries with specific date
$historicalDate = new DateTime('2024-01-01');
$activeAtDate = $this->Officers->find('current', effectiveDate: $historicalDate);

KMP Use Cases

JsonField Behavior

The JsonFieldBehavior provides enhanced JSON field handling capabilities for tables with JSON columns, enabling deep querying into JSON structures using database-native functions.

Key Features

Database Requirements

Usage Examples

// In Table initialize() method  
$this->addBehavior('JsonField');

// Query nested JSON data
$membersWithNotifications = $this->Members->find()
    ->addJsonWhere('additional_info', '$.preferences.notifications', true);

// Search complex JSON structures
$emergencyContacts = $this->Members->find()
    ->addJsonWhere('additional_info', '$.emergency.relationship', 'spouse');

JSON Path Syntax

KMP Use Cases

Sortable Behavior

The SortableBehavior provides comprehensive sortable list management for entities requiring position-based ordering. This behavior automatically manages position values and handles conflicts during reordering operations.

Key Features

Configuration Options

$this->addBehavior('Sortable', [
    'field' => 'position',           // Position field name (default: 'position')
    'group' => ['category_id'],      // Grouping fields for separate lists
    'start' => 1,                    // Starting position value (default: 1)  
    'step' => 1,                     // Position increment (default: 1)
]);

Database Schema Requirements

-- Tables using Sortable behavior need:
position     INT NOT NULL,           -- Position field (configurable name)
category_id  INT NULL,               -- Optional: grouping fields
status       VARCHAR(50) NULL        -- Optional: additional grouping

Usage Examples

// Basic reordering operations
$this->Recommendations->toTop($itemId);
$this->Recommendations->toBottom($itemId);
$this->Recommendations->moveBefore($sourceId, $targetId);
$this->Recommendations->moveAfter($sourceId, $targetId);
$this->Recommendations->move($itemId, 5); // Move to position 5

// Group-based sorting with multiple criteria
$this->addBehavior('Sortable', [
    'field' => 'stack_rank',
    'group' => ['category_id', 'status'], // Each category+status combination is separate
]);

KMP Use Cases

Group-Based Sorting

When group fields are configured, sorting operates independently within each group:

// Each combination of grouping fields maintains its own sorted list
// Example: category_id=1,status='active' vs category_id=1,status='pending'
$this->addBehavior('Sortable', [
    'field' => 'priority',
    'group' => ['category_id', 'status']
]);

Behavior Integration Patterns

ORM Event Handling

Behaviors integrate with CakePHP’s ORM events to provide transparent functionality:

// Automatic position assignment on save
public function beforeSave(EventInterface $event, EntityInterface $entity): void
{
    if ($entity->isNew()) {
        $entity->position = $this->getNew($this->_getConditions());
    }
}

// Custom finders for specialized queries  
public function findCurrent(SelectQuery $query, ?DateTime $effectiveDate = null): SelectQuery
{
    return $query->where([
        $this->_table->getAlias() . '.start_on <=' => $effectiveDate,
        'OR' => [
            $this->_table->getAlias() . '.expires_on >=' => $effectiveDate,
            $this->_table->getAlias() . '.expires_on IS' => null
        ]
    ]);
}

Performance Considerations

Testing Behaviors

// Test behavior integration in unit tests
public function testActiveWindowCurrentFinder()
{
    $currentDate = new DateTime('2024-01-15');
    $result = $this->Officers->find('current', effectiveDate: $currentDate);
    $this->assertNotEmpty($result);
}

public function testSortableMoveToTop()  
{
    $success = $this->Recommendations->toTop($this->recommendation->id);
    $this->assertTrue($success);
    
    $updated = $this->Recommendations->get($this->recommendation->id);
    $this->assertEquals(1, $updated->stack_rank);
}

Sequence Diagram: Authorization with Scoping

sequenceDiagram
    participant User
    participant App
    participant Policy
    participant DB

    User->>App: Request to view/edit resource
    App->>DB: Load user permissions and roles
    App->>Policy: Check policy for action (e.g., canView)
    Policy->>App: Return required permission and scope
    App->>DB: Check if user has permission for resource's scope
    DB-->>App: Return result (allowed/denied)
    App-->>User: Allow or deny action

Implementation Notes