3.1.1 Application foundation
This guide describes the reusable application layer below KMP’s domain modules. It complements the multi-tenant architecture, which is the authority for datasource and scope rules.
Bootstrap and dependency injection
app/src/Application.php owns plugin loading, middleware ordering, service
container bindings, authentication/authorization services, workflow handler
registration, and shared registries. app/config/bootstrap.php loads
configuration and connects it to CakePHP facilities.
Prefer constructor injection for services registered in the container. A controller may retrieve a CakePHP component or table through established framework APIs, but business services should not depend on controller state or the service locator. Avoid adding process-global mutable state; long-running workers make state leaks visible across tenants and jobs.
Base classes
Controllers
Tenant-facing web controllers extend AppController; API controllers extend
ApiController. Their responsibilities are to:
- parse and validate the transport-level request;
- load the target model/resource;
- call
authorize(),authorizeModel(), and/orapplyScope(); - invoke the owning table or service; and
- produce HTML, Turbo Frame, redirect, or API responses.
Use TurboResponseTrait for established Turbo response behavior,
DataverseGridTrait for grids, and WorkflowDispatchTrait where an existing
domain flow uses it. Do not put multi-record workflows, plugin discovery, or
cross-tenant loops in controllers.
Tables and entities
Tables extend App\Model\Table\BaseTable; entities extend
App\Model\Entity\BaseEntity. These provide common project conventions and
audit/public-ID integration used throughout the schema. Tables own associations,
validation, build rules, finders, persistence callbacks, and domain invariants
that are local to their aggregate.
Entities expose record behavior and virtual fields but should not open service connections or orchestrate external side effects. Use services for workflows that span aggregates, plugins, mail, storage, or queues.
Policies
Policies extend App\Policy\BasePolicy. Resource methods answer whether an
identity may perform an action. Table/query policies constrain which records an
identity may see. Branch-aware permission reach is expressed through the
existing permission policy and scoping helpers, not copied SQL predicates.
A view may hide unavailable controls for usability, but server-side policy checks remain mandatory.
Service layer
app/src/Services is organized by capability. Important boundaries include:
Platform/*for tenant registry, lifecycle, provisioning, fleet work, central jobs/schedules, health, audit, and platform administration;WorkflowEngine/*andWorkflowRegistry/*for reusable workflow execution;Backups/*for managed encrypted tenant and platform recovery;Secrets/*,Cache/*, andStorage/*for infrastructure abstractions;- domain services for members, gatherings, action items, registration, and other multi-step application operations.
A service should have one clear owner, explicit inputs/outputs, transactional boundaries where needed, and retry-safe side effects. It must not silently choose a tenant; callers enter the tenant scope first.
Registries and plugin integration
Registries let plugins contribute capabilities without core code naming each plugin:
| Registry | Purpose |
|---|---|
NavigationRegistry |
navigation items and placement |
ViewCellRegistry |
plugin-owned detail/sidebar/tab fragments |
ApiDataRegistry |
approved API data contributions |
ActionItemCompletionFormRegistry |
action-item completion UI/providers |
Workflow*Registry classes |
workflow entities, triggers, conditions, actions, approvers |
ApprovalContextRendererRegistry |
domain rendering for approval context |
Registration occurs during application/plugin bootstrap using stable keys and provider contracts. Rendered cells and navigation items still perform normal authorization. Avoid hard-coding plugin controllers, templates, or table names into core UI.
Grid foundation
Grid controllers use DataverseGridTrait, while column definitions extend
App\KMP\GridColumns\BaseGridColumns. The grid provider describes fields,
labels, formatters, sortability, search, filters, and system views. Controllers
apply policy scope before returning rows.
Keep filtering metadata declarative. Date ranges use the current filterable
and filterType: date-range column metadata; do not add controller-specific
filter parsers when the shared grid can express the behavior. Treat column HTML
as user-facing UI and preserve labels, focus, status announcements, and
non-color cues.
Events and side effects
CakePHP events are appropriate for decoupled notifications where ordering and transaction semantics are explicit. Use a direct service call when the caller requires a result or failure. Queue slow or retryable work through the tenant queue inside tenant context, or through a platform job for an explicitly central operation.
Do not dispatch an irreversible external effect before the owning transaction commits. Use idempotency keys or durable state when retries may occur.
Caching
Cache only data whose ownership and invalidation are understood. Tenant-derived
values go through TenantAwareCache; registry/operations values use an explicit
platform namespace. Cache keys should describe the semantic input, not just an
integer record ID. Mutation paths must invalidate all affected representations,
including navigation, permissions, grids, and plugin contributions where
applicable.
CakePHP core/model metadata caches are process/application caches and are not a substitute for tenant-aware result caching.
Dates and timezones
Persist timestamps in UTC. Use immutable CakePHP date/time values for domain logic where practical. Convert user-entered and displayed date-times through the project timezone utilities, and pre-format values before handing them to mailers. Date-only business boundaries may intentionally use the configured application/kingdom timezone; the owning feature guide should state that rule.
Error and audit behavior
Expected validation and authorization failures should produce useful, non-sensitive responses. Unexpected failures flow to CakePHP error handling and structured logs. Attach correlation and safe domain context, but never log secret values, raw credentials, recovery keys, entire request bodies, or member records.
Use existing audit and Footprint attribution. Platform lifecycle, secret, backup, and operator actions have separate central audit expectations and should not be represented only in a tenant log.
How to add a feature
- Identify the owning core domain or active plugin.
- Define its tenant/platform data ownership before creating a table.
- Reuse a base class, registry, behavior, grid, or workflow provider where it fits.
- Put transport logic in controllers, persistence rules in tables/entities, authorization in policies, and orchestration in services.
- Add a forward migration to the correct platform/core/plugin track.
- Test policy denial, branch scope, tenant A/B isolation, retries, and UI accessibility as applicable.
- Update the nearest durable guide rather than adding a temporary task note.
Source map
app/src/Application.phpapp/src/Controller/AppController.phpapp/src/Controller/Api/ApiController.phpapp/src/Model/Table/BaseTable.phpapp/src/Model/Entity/BaseEntity.phpapp/src/Policy/BasePolicy.phpapp/src/Controller/DataverseGridTrait.phpapp/src/KMP/GridColumns/BaseGridColumns.phpapp/src/Services/NavigationRegistry.phpapp/src/Services/ViewCellRegistry.php