Controller framework technical guide

11 Nov 2025 - dirkvm


Controller Framework 1.0.31 — Technical Reference
Controller Framework 1.0.31

Technical Reference

PHPDoc-style technical documentation for framework developers and maintainers building client applications on top of Controller Framework 1.0.31.

Source basis: Release 31 source code, Composer definition and supplied example application. This document describes the actual class hierarchy and object relationships in Release 31 rather than a generic MVC framework.

Related documentation: Controller Framework 1.0.31 — Client Application Developer Guide contains the practical, application-oriented usage guide.

UML class diagram overview

The following diagram gives a high-level UML view of the Controller Framework 1.0.31. It shows the principal class hierarchies and relationships between the controller, request, rendering, authentication, security, domain/database, and infrastructure parts of the framework. The diagram is intended as an orientation map; the detailed API and class relationships are documented in the sections that follow.

Controller Framework 1.0.31 UML class diagram overview
Figure 1 — Controller Framework 1.0.31 UML class diagram overview

Reading the diagram: inheritance is shown through the class hierarchies, while the main framework collaborations are grouped by functional area. For detailed attributes, operations, signatures, and contracts, use the corresponding package sections below.

1. Architecture

The Application Controller variant of Release 31 is the primary framework configuration used by the supplied example application. The application entry point calls Controller::run(). The framework initializes configuration and the request, compiles controls.xml into descriptors, resolves the Command for the requested path, executes it and finally renders the result.

Browser / CLIRequest ControllerErrorHandlerInitControllerHandleRequestController RequestHttpRequest / CliRequestpath + properties + status RenderCompilercontrols.xml→ Conf of descriptors Registryapplication-wide servicesDB / Login / config / commands Commandlogin strategydoExecute() RenderComponentView / Forward / Datarequestselected by command status Domain ModelDomainObject / MemberMapper / ObjectMap ServicesLoginManager / MailerCSRF / AuditTrace

2. Correct object models

2.1 Controller object model

Controller InitController <<abstract>>init() / setupCommands() HandleRequestController<<abstract>> Command<<abstract>> InitApplicationController InitFrontController HandleRequestApplicationController HandleRequestFrontController DefaultCommand CommandDecorator<<abstract>> Client Command RenderComponent ViewRenderComponent ForwardRenderComponent DatarequestRenderComponent Ajax Download Agenda Mollie NoRender

Important: CommandDecorator is itself a Command. It does not subclass a client command; instead it contains another Command in $command and delegates to it unless doExecuteDecorator() returns a non-null status.

2.2 Database object model

DomainObject<<abstract>>id + dynamic properties Client DomainObjecte.g. \model\Activityone class per domain entity Mapper<<abstract>>PDO + ObjectMap cache Client Mappere.g. \model\ActivityMappertable + object creation + fields ObjectMapextends SplObjectStorageobject → database id PDOMySQL / MariaDBapplication database

2.3 Member object model

DomainObject<<abstract>> controllerframework\members\Member<<abstract>> \model\Memberclient concrete Member MemberCompositecontains ObjectMap of children MemberTypeImplementation<<abstract>> Bridge \model\Member_RGLRtype = RGLR example \model\Member_PRTNtype = PRTN example membertypeimplementation
Client model contract: the framework's DomainObject::mapper() derives the Mapper class from the short class name and the \model\ namespace. For example, \model\Activity maps to \model\ActivityMapper. For Members the framework explicitly uses \model\Member. A client application should therefore keep this namespace convention unless the framework itself is changed.

2.4 Session object model

Login<<abstract Strategy>> NoLoginRequiredvalidate() = true LoginRequired<<abstract Template>> LoginManagersession + remember-me UserLogin60 minute inactivity AdminLogin15 minute inactivity

3. Controllers package

controllerframework\controllers\Controller

The public application entry point. The constructor is private; applications use the static run() method.

final public static function run(): void

Runtime sequence: register ErrorHandler → initialize the Registry through the configured InitController → set the configured environment → delegate the current Request to the configured HandleRequestController.

Command

class Command

Namespace: controllerframework\controllers
Type: abstract class
Pattern: Command + Template Method + Strategy

abstract class Command
{
    protected Registry $reg;
    protected Login $loginLevel;

    public const CMD_DEFAULT = 0;
    public const CMD_OK = 1;
    public const CMD_ERROR = 2;
    public const CMD_INSUFFICIENT_DATA = 3;
    public const CMD_ADMIN = 4;
    public const CMD_CHANGE_PASSWORD = 5;
    public const CMD_CONTINUE = 6;

    final public function __construct(): void;
    public function execute(Request $request): int;

    protected function setLoginLevel(Login $loginLevel): void;
    protected function loginChecks(Member $user): int;
    protected function addResponses(Request $request, array $responses): void;
    protected function getCsrfToken(): string;
    protected function validateCsrfToken(Request $request): bool;

    abstract public function doExecute(Request $request): int;
    abstract protected function getLevelOfLoginRequired(): void;
}
Client implementation rule: implement doExecute() and getLevelOfLoginRequired(). Do not override execute(); it is the framework Template Method that performs login validation, invokes the application logic and stores the command status in the Request.

CommandDecorator

abstract class CommandDecorator extends Command
{
    protected ?Command $command = null;

    protected function setCommand(Command $command): void;

    final public function doExecute(Request $request): int;

    abstract public function initCommand(): void;
    abstract public function doExecuteDecorator(Request $request): ?int;
}

Execution first calls initCommand(). If doExecuteDecorator() returns a non-null/non-zero status, that status becomes the result. Otherwise the decorated Command is executed.

Conf

class Conf
{
    public function __construct(array $conf = []): void;
    public function set(string $key, mixed $value): void;
    public function get(string $key): mixed;
}

Conf is a small object wrapper around a named PHP array. It is used for application configuration and the compiled command map.

RenderComponent and renderers

interface RenderComponent
{
    public function render(Request $request): void;
}

abstract class DatarequestRenderComponent implements RenderComponent
{
    public static function init(string $type): DatarequestRenderComponent;
}

class ViewRenderComponent implements RenderComponent
{
    public function __construct(string $name);
    public function render(Request $request): void;
}

class ForwardRenderComponent implements RenderComponent
{
    public function __construct(string $path);
    public function render(Request $request): void;
}

class AjaxRenderComponent extends DatarequestRenderComponent
class DownloadRenderComponent extends DatarequestRenderComponent
class AgendaRenderComponent extends DatarequestRenderComponent
class MollieRenderComponent extends DatarequestRenderComponent
class NoRenderRenderComponent extends DatarequestRenderComponent
RendererExpected Request stateResponse
ViewRenderComponentview configured in controls.xmlIncludes PHP view
ForwardRenderComponentoptional forwardqueryparams arrayHTTP redirect
AjaxRenderComponentresults arrayJSON
DownloadRenderComponentfilename, columnNames, resultsCSV
AgendaRenderComponentfilename, resultsiCalendar
MollieRenderComponentresultsHTTP Location redirect
NoRenderRenderComponentnoneEnds response without rendering

RenderComponentDescriptor

class RenderComponentDescriptor
{
    public function __construct(string $path, string $cmdstr);
    public function getCommand(): Command;
    public function setRenderer(int $status, RenderComponent $renderer): void;
    public function getRenderer(Request $request): RenderComponent;
}

A descriptor represents one path from controls.xml. It contains the Command class name and a renderer map indexed by Command status. A renderer matching the current status is preferred; status CMD_DEFAULT (numeric 0) is used as fallback.

RenderCompiler

class RenderCompiler
{
    public function parseFile(string $file): Conf;
}

The compiler parses XML using simplexml_load_file(), converts each command into a RenderComponentDescriptor, resolves status constants from Command and creates the configured renderer objects.

CommandResolver

class CommandResolver
{
    public function __construct();
    public function getCommand(Request $request): Command;
}

It resolves a command from the request/command configuration and validates that the configured class exists and is a subclass of Command.

HandleRequestController

abstract class HandleRequestController
{
    protected function getCommand(Request $request): Command;
    protected function getRenderer(Request $request): RenderComponent;
    abstract public function handleRequest(Request $request): void;
}
class HandleRequestApplicationController
    extends HandleRequestController
{
    public function handleRequest(Request $request): void
    {
        $this->getCommand($request)->execute($request);
        $this->getRenderer($request)->render($request);
    }
}

4. Registry and Request

Registry

class Registry
{
    public static function instance(): self;
    public static function reset(): void;

    public function getRequest(): Request;
    public function setRequest(Request $request): void;

    public function getInitController(): InitController;
    public function getHandleRequestController(): HandleRequestController;

    public function setAppConfig(Conf $conf): void;
    public function getAppConfig(): Conf;

    public function setCommands(Conf $commands): void;
    public function getCommands(): Conf;

    public function getLoginManager(): LoginManager;
    public function getDb(): \PDO;
}

Registry is a Singleton. It lazily creates the application database PDO connection and LoginManager and stores the Request, application configuration and compiled command map.

Request

abstract class Request
{
    protected array $properties;
    protected int $status = 0;
    protected array $feedback = [];
    protected string $path = "/";

    public function __construct();

    abstract public function init(): void;
    abstract public function forward(string $path): void;

    public function setPath(string $path): void;
    public function getPath(): string;

    public function get(string $key): mixed;
    public function set(string $key, mixed $val): void;

    public function addFeedback(string $msg): void;
    public function getFeedback(): array;
    public function getFeedbackString(string $seperator = "\n"): string;
    public function clearFeedback(): void;

    public function setCmdStatus(int $status): void;
    public function getCmdStatus(): int;
}

Concrete Requests

class HttpRequest extends Request
{
    public function init(): void;
    public function forward(string $path): void;
}

class CliRequest extends Request
{
    public function init(): void;
    public function forward(string $path): void;
}

InitController selects HttpRequest when $_SERVER['REQUEST_METHOD'] exists and CliRequest otherwise.

InitController

abstract class InitController
{
    public function __construct();
    public function init(): void;

    abstract protected function setupCommands(
        array $options,
        string $controlsfile
    ): Conf;
}

The constructor determines the application root from the framework installation location and derives /config/app_options.ini. init() reads configuration, defines global constants, determines the controls file and stores the Request.

InitApplicationController

class InitApplicationController extends InitController
{
    protected function setupCommands(
        array $options,
        string $controlsfile
    ): Conf;
}

It uses RenderCompiler to create the application command map.

5. Database object model

DomainObject

abstract class DomainObject
{
    public ?Conf $properties = null;

    public function __construct(int $id);

    abstract public static function getInstance(array $row): DomainObject;

    public static function find(int $id): DomainObject;
    public static function findAll(string $selectclause = ''): ObjectMap;
    public static function insert(array $properties): DomainObject;

    public function update(array $properties): DomainObject;
    public function delete(): void;

    protected static function mapper(): Mapper;

    public function isComposite(): bool;
    public function getId(): int;

    protected function initProperties(array $row): void;

    public function __get(string $key): mixed;
    public function __set(string $key, mixed $value): void;
}

Every database table represented by a DomainObject is expected to have an id column that uniquely identifies a row. Additional database columns are represented dynamically through the public properties configuration object and __get()/__set().

Mapper

abstract class Mapper implements AuditableItem
{
    public function __construct();

    public function find(string $classname, int $id): DomainObject;
    public function findAll(
        string $classname,
        string $selectclause = ''
    ): ObjectMap;

    public function createObject(
        string $classname,
        array $row
    ): DomainObject;

    public function insert(
        string $classname,
        array $properties
    ): DomainObject;

    protected function getAllowedFields(): array;
    protected function validateFields(array $updates): void;

    public function update(
        DomainObject $obj,
        array $updates
    ): DomainObject;

    public function delete(DomainObject $obj): void;
    public function checkForChildren(int $id): array;

    abstract protected function tablename(): string;
    abstract protected function doCreateObject(
        string $classname,
        array $row
    ): DomainObject;
}

The Mapper owns the PDO connection and an ObjectMap cache. find() first checks the cache, then loads the row and creates the object. Updates validate every field against getAllowedFields() before executing the update.

findAll() security contract: $selectclause is intentionally a free SQL fragment. It is not a bound parameter. Never put untrusted GET, POST, cookie or other external input directly into this argument. Use application-controlled/whitelisted SQL fragments or redesign the query when dynamic user input is required.

ObjectMap

class ObjectMap extends \SplObjectStorage
{
    public function getObjectBy(int $info): ?DomainObject;
}

The mapper uses the database ID as the SplObjectStorage info value. It therefore provides an object collection that can also retrieve a DomainObject by database ID.

6. Member object model

controllerframework\members\Member

abstract class Member extends \controllerframework\db\DomainObject
{
    public ?MemberTypeImplementation $membertypeimplementation = null;

    public function __construct(int $id);
    public static function getInstance(array $row): Member;

    public function getYearlyfee(): float;
    public function isRejected(): bool;
    public function isAdministrator(): bool;
    public function shouldExtendMembership(): bool;
    public function extendMembership(): void;

    protected function getMembershipFee(): float;

    public function subscriptionPeriodOver(): bool;
    public function getTotalAmountReceived(): float;

    abstract public function initiatePassword(
        string $pwd = ''
    ): string;
}

Member::getInstance() determines whether the database row has children. If so, it returns a MemberComposite; otherwise it creates the concrete Member class and attaches a MemberTypeImplementation based on the row's classification value.

MemberComposite

class MemberComposite extends Member
{
    protected ObjectMap $children;

    public function __construct(int $id);
    public static function getInstance(array $row): Member;

    public function getChildren(): ObjectMap;
    public function isComposite(): bool;

    public function initiatePassword(string $pwd = ''): string;
}

A composite Member cannot be deleted through DomainObject::delete(), because isComposite() returns true.

MemberMapper

abstract class MemberMapper extends \controllerframework\db\Mapper
{
    public function tablename(): string;
    protected function doCreateObject(
        string $classname,
        array $row
    ): Member;

    protected function getAllowedFields(): array;

    public function getChildren(
        MemberComposite $membercomposite
    ): ObjectMap;
}

The framework MemberMapper is tied to the database table member. A client application normally creates a concrete \model\MemberMapper subclass.

MemberTypeImplementation

abstract class MemberTypeImplementation
{
    public function getMembershipFee(
        \model\Member $member
    ): float;

    abstract public function getYearlyParticipationFee(
        \model\Member $member
    ): int;

    public function calculateProRataFee(float $fee): float;
}

Correct client model structure

namespace model;

class Member
    extends \controllerframework\members\Member
{
    public function initiatePassword(
        string $pwd = ''
    ): string {
        // application-specific mail body
    }
}

final class MemberMapper
    extends \controllerframework\members\MemberMapper
{
}

class Member_RGLR
    extends \controllerframework\members\MemberTypeImplementation
{
    public function getYearlyParticipationFee(
        \model\Member $member
    ): int {
        return 350;
    }
}

class Member_PRTN
    extends \controllerframework\members\MemberTypeImplementation
{
    public function getYearlyParticipationFee(
        \model\Member $member
    ): int {
        // application-specific calculation
    }
}

The framework constructs the type implementation dynamically as \model\Member_{classification}. Consequently a classification such as RGLR requires \model\Member_RGLR.

7. Session object model

Login

abstract class Login
{
    abstract public function validate(): bool;
}

LoginRequired

abstract class LoginRequired extends Login
{
    public function __construct();
    public function validate(): bool;

    protected function setLastActive(
        int $numberOfMinutes
    ): void;

    abstract protected function initLastActive(): void;
    abstract protected function checkMemberRights(): bool;
}

The Template Method validate() obtains the current Member from LoginManager, checks inactivity and then delegates rights validation to the concrete strategy.

StrategyRightsNormal inactivity
NoLoginRequiredNo authentication requiredNot applicable
UserLoginMember must be active60 minutes
AdminLoginMember must be active and role A15 minutes

If $_SESSION['rememberMe'] is true, the inactivity test is bypassed. Persistent authentication is then controlled by the 30-day remember-token mechanism.

LoginManager

class LoginManager implements AuditableItem
{
    public function __construct();

    public function getUser(): ?Member;

    public function validateUsername(
        string $username
    ): int|false;

    public function validatePassword(
        int $memberid,
        string $password
    ): bool;

    public function login(
        int $memberid,
        bool $keepLoggedin = true
    ): Member;

    public function logout(): void;

    public function initiatePassword(
        int $memberid,
        int $pwdlength = 8,
        bool $requestedByAdmin = false
    ): void;

    public function changePassword(
        Member $user,
        string $password
    ): void;
}

Session security implemented by Release 31

User

class User
{
    public static function getInstance(
        int $memberid = 0
    ): ?\model\Member;
}

This singleton-like helper resolves the Member represented by the current session member ID.

8. Security API

controllerframework\security\Csrf

class Csrf
{
    public const TOKEN_PARAMETER = 'csrf_token';

    public static function getToken(): string;
    public static function validate(?string $token): bool;
    public static function regenerateToken(): string;
}

The token is generated with random_bytes(32), stored in the session and compared using hash_equals().

Command CSRF helpers

protected function getCsrfToken(): string;

protected function validateCsrfToken(
    Request $request
): bool;

The helper reads Csrf::TOKEN_PARAMETER from the Request. Client Commands performing state-changing POST operations should validate it before processing the operation.

controllerframework\security\AccessToken

class AccessToken
{
    public static function generate(
        string $purpose,
        string $identifier
    ): string;

    public static function validate(
        string $purpose,
        string $identifier,
        ?string $token
    ): bool;
}

The AccessToken class provides an additional token-based security mechanism for protecting specific URLs or application operations. A token is bound to a purpose and an application-specific identifier.

Access tokens are generated and validated using a server-side secret and HMAC-SHA-256. Validation uses a timing-safe comparison. The token does not require a database table or other server-side token storage.

The same purpose and identifier produce the same token. Consequently, AccessToken is not a session-token mechanism and does not by itself provide expiration, one-time use or revocation.

Security rule: the server-side secret used by AccessToken must remain confidential. Access tokens should only be transmitted over HTTPS and should not be written to application or audit logs. The token must be validated before the protected operation is performed.

Example

use controllerframework\security\AccessToken;

$token = AccessToken::generate(
    'mollie-order',
    (string) $orderid
);

if (!AccessToken::validate(
    'mollie-order',
    (string) $orderid,
    $token
)) {
    throw new \RuntimeException(
        'Invalid access token.'
    );
}

9. Mail API

Mailer

class Mailer
{
    public static function sendMail(
        string $subject,
        string $body,
        string $toBcc,
        ?string $to = null
    ): void;
}

The SMTP DSN is constructed from _MAILHOST, _MAILHOSTPORT, _MAILUSERNAME and _MAILPASSWORD. Sender and reply-to values come from the configured mail constants.

MailQueue

class MailQueue
{
    public static function add(
        string $subject,
        string $body,
        string $recipient,
        string $bcc = ''
    ): int;
}

MailQueue::add() inserts a message into the mail_queue database table and returns its inserted ID.

MailerQueue

class MailerQueue
{
    public static function sendMail(
        $subject,
        $body,
        $toBcc,
        $to = null
    ): void;
}

This helper sends a message through Symfony Mailer. A scheduled queue processor can retrieve queued records and pass their values to MailerQueue::sendMail().

10. Audit API

AuditableItem

interface AuditableItem
{
    public function notifyAuditTrace(
        string $functionname,
        array $arglist = []
    ): void;
}

AuditableItemTrait

trait AuditableItemTrait
{
    public function notifyAuditTrace(
        string $functionname,
        array $arglist = []
    ): void;
}

AuditTrace

class AuditTrace
{
    public function notify(
        string $classname,
        string $functionname,
        array $arglist = []
    ): void;
}

The trait is used by framework classes such as Mapper and LoginManager. The logging path is supplied through the application's loggingpath configuration.

11. Error handling

ErrorHandler

class ErrorHandler
{
    public static function register(): void;
    public static function setEnvironment(
        string $environment
    ): void;
    public static function handleException(
        \Throwable $exception
    ): void;
}
EnvironmentPurpose
developmentDetailed exception diagnostics for development.
productionGeneric HTTP 500 behavior without exposing internal details.

12. Client application contracts

12.1 Application entry point

<?php

require __DIR__ . '/vendor/autoload.php';

controllerframework\controllers\Controller::run();

12.2 Client command

namespace commands;

class ActivityCommand
    extends \controllerframework\controllers\Command
{
    public function doExecute(
        \controllerframework\registry\Request $request
    ): int {
        $activity = \model\Activity::find(
            (int) $request->get('id')
        );

        $this->addResponses($request, [
            'activity' => $activity
        ]);

        return self::CMD_OK;
    }

    protected function getLevelOfLoginRequired(): void
    {
        $this->setLoginLevel(
            new \controllerframework\sessions\UserLogin()
        );
    }
}

12.3 Client DomainObject

namespace model;

class Activity
    extends \controllerframework\db\DomainObject
{
    public static function getInstance(
        array $row
    ): \controllerframework\db\DomainObject {
        $activity = new self($row['id']);
        $activity->initProperties($row);
        return $activity;
    }
}

12.4 Client Mapper

namespace model;

final class ActivityMapper
    extends \controllerframework\db\Mapper
{
    protected function tablename(): string
    {
        return 'activity';
    }

    protected function doCreateObject(
        string $classname,
        array $row
    ): \controllerframework\db\DomainObject {
        return $classname::getInstance($row);
    }

    protected function getAllowedFields(): array
    {
        return array_merge(
            parent::getAllowedFields(),
            [
                'name',
                'date',
                'active'
            ]
        );
    }
}
Mapper naming: DomainObject::mapper() derives \model\ActivityMapper from \model\Activity. The same convention is used for other client DomainObjects.

12.5 Controls XML contract

<command
    path="/activity"
    class="\commands\ActivityCommand">

    <view name="/activity/activityView" />

    <status value="CMD_OK">
        <forward path="/activity/list" />
    </status>

    <status value="CMD_ERROR">
        <view name="/errorView" />
    </status>

</command>

12.6 Response flow

HTTP request
    ↓
Request::getPath()
    ↓
RenderComponentDescriptor
    ↓
Command::execute()
    ├── Login::validate()
    └── Command::doExecute()
            ↓
      Request::setCmdStatus()
            ↓
RenderComponentDescriptor::getRenderer()
            ↓
RenderComponent::render()

13. API reference by package

controllerframework\controllers

Class / interfaceRole
ControllerApplication entry point.
CommandBase class for application commands.
CommandDecoratorDecorates another Command.
DefaultCommandFramework default command.
ConfNamed-array configuration object.
CommandResolverCommand resolution and class validation.
RenderCompilerCompiles controls.xml.
RenderComponentDescriptorPath → Command + renderer mapping.
RenderComponentRendering interface.
ViewRenderComponentPHP view renderer.
ForwardRenderComponentHTTP forward renderer.
DatarequestRenderComponentBase for data response renderers.
AjaxRenderComponentJSON renderer.
DownloadRenderComponentCSV download renderer.
AgendaRenderComponentiCalendar renderer.
MollieRenderComponentPayment redirect renderer.
NoRenderRenderComponentResponse terminator without rendering.
InitControllerBase initialization controller.
InitApplicationControllerApplication Controller initialization.
InitFrontControllerFront-controller initialization variant.
HandleRequestControllerBase request handler.
HandleRequestApplicationControllerApplication request handler.
HandleRequestFrontControllerFront-controller request handler.

controllerframework\db

ClassRole
DomainObjectBase persistent domain object.
MapperBase database mapper.
ObjectMapObject collection keyed by database ID.

controllerframework\members

ClassRole
MemberBase Member domain object.
MemberCompositeMember containing child Members.
MemberMapperBase Mapper for the member table.
MemberTypeImplementationMember-type-specific behavior.

controllerframework\registry

ClassRole
RegistryApplication-wide Singleton registry.
RequestBase request abstraction.
HttpRequestHTTP request implementation.
CliRequestCLI request implementation.

controllerframework\sessions

ClassRole
LoginLogin validation Strategy base.
LoginRequiredTemplate for authenticated strategies.
UserLoginActive-user strategy.
AdminLoginActive-administrator strategy.
NoLoginRequiredPublic command strategy.
LoginManagerSession, authentication and password management.
UserSession-member resolver.

controllerframework\security

ClassRole
CsrfSession-based CSRF token generation and validation.
AccessTokenProvides a token-based mechanism for protecting specific URLs

controllerframework\mail

ClassRole
MailerImmediate SMTP mail sending.
MailQueuePersist mail for asynchronous sending.
MailerQueueSend a queued message through SMTP.

controllerframework\audit

TypeRole
AuditableItemAudit notification contract.
AuditableItemTraitStandard audit notification implementation.
AuditTraceAudit log writer.

14. Technical invariants for client developers

Release 31 technical baseline: the diagrams and API signatures in this document reflect the actual Release 31 class hierarchy and the supplied Application Controller example. Application frameworks layered on top of Controller Framework may introduce additional classes, but they should preserve these framework contracts.