Membersactivities framework client application development guide
16 Sep 2026 - dirkvm
The MembersActivities Framework is an application framework for building web applications that manage members, activities, subscriptions, payments and related functionality.
It is built on top of the Controller Framework and provides reusable domain functionality while allowing each client application to define its own user interface, business rules, subscription validation, activity types, payment descriptions, authentication configuration, mail configuration and optional integrations.
The client application should normally extend and configure the framework rather than modify framework source code.
| Component | Requirement |
|---|---|
| PHP | 8.3 or compatible PHP 8.x |
| Controller Framework | 1.0.31 |
| MembersActivities Framework | 1.0.31 |
| Database | MySQL / MariaDB |
| Database access | PDO |
| Web server | Apache or compatible PHP web server |
| Composer | Required |
The framework uses a MySQL/MariaDB PDO connection. Oracle is not a supported database platform for this release.
A client application installs the framework through Composer:
{
"require": {
"samoscon/membersactivities-framework": "^1.0.31"
}
}
The MembersActivities Framework requires samoscon/controller-framework ^1.0.31. Composer installs that dependency automatically.
composer install
Use composer install for deployments based on a committed composer.lock.
application/
│
├── index.php
├── composer.json
├── composer.lock
│
├── config/
│ └── app_options.ini
│
├── MVCFramework/
│ ├── controls.xml
│ ├── commands/
│ │ ├── DefaultCommand.php
│ │ ├── admin/
│ │ ├── user/
│ │ └── mollie/
│ ├── model/
│ └── views/
│
└── assets/
The exact directory structure may be adapted, but framework classes should remain in the Composer package and client-specific classes should remain in the client application.
Configuration contains application-specific values such as environment, paths, database credentials, mail settings and security secrets.
[config]
environment=development
templatepath=/MVCFramework/views
controlsfile=/MVCFramework/controls.xml
loggingpath=/assets/logging/
[globals]
APP=MyApplication
_APPDIR=https://www.example.com/
_HOMEPAGE=https://www.example.com/
_ASSETDIR=https://www.example.com/assets/
_SALTRAND=...
_RAND=...
_DBUSER=...
_DBPASSWORD="..."
_DBNAME=...
_DBHOST=localhost
_SALTRAND must remain secret. The configuration file must not be publicly downloadable._SALTRAND is used by the Controller Framework's AccessToken implementation. It should be a long, cryptographically random secret.
The reference schema is intended for a new client application. Principal tables include:
member
activity
costitem
subscription
payment
remember_tokens
mail_queue
Commands return statuses such as CMD_DEFAULT, CMD_OK, CMD_CONTINUE and CMD_ERROR. Routing and rendering are defined through the application's controller configuration.
A client command normally extends:
\controllerframework\controllers\Command
Example:
namespace commands\user;
class MyCommand extends \controllerframework\controllers\Command
{
public function doExecute(
\controllerframework\registry\Request $request
): int {
// Application logic
return self::CMD_DEFAULT;
}
protected function getLevelOfLoginRequired(): void
{
$this->setLoginLevel(
new \controllerframework\sessions\NoLoginRequired()
);
}
}
A command should obtain and validate request data, perform the required operation, prepare response data and return an appropriate command status.
The CommandDecorator is one of the most important extension mechanisms. It allows client-specific behaviour to be added around a reusable MembersActivities command.
class PublicActivityCommand
extends \controllerframework\controllers\CommandDecorator
{
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
$request->set(
'validator',
new \model\SubscriptionValidationPublic()
);
return null;
}
public function initCommand(): void
{
$this->setCommand(
new \membersactivities\commands\user\ActivityCommand()
);
}
protected function getLevelOfLoginRequired(): void
{
$this->setLoginLevel(
new \controllerframework\sessions\NoLoginRequired()
);
}
}
This pattern avoids modifying generic framework commands for every possible client application's business rule.
Subscription validation uses SubscriptionValidationStrategy. A client application provides a concrete strategy containing its own business rules.
class SubscriptionValidationUser
extends \membersactivities\model\subscriptions\
SubscriptionValidationStrategy
{
protected function doCheckSubscription(
\controllerframework\members\Member $member,
\membersactivities\model\activities\Costitem $costitem
): array {
if (!$member->active) {
return $this->errorcode(
200,
'Your membership is inactive.'
);
}
return $this->errorcode(0);
}
}
The framework can then call the strategy before creating a subscription.
CommandDecorator to select the concrete strategy.The Controller Framework's Request object transports information through the command chain.
$request->get('id');
$request->set('validator', $validator);
$request->addFeedback('Something went wrong.');
$request->set(
'forwardqueryparams',
['id' => $id]
);
The Request object is a transport mechanism, not a trust boundary. Data originating from a browser must still be validated.
State-changing requests should use POST and CSRF protection.
// Generate token
$responses['csrf_token'] = $this->getCsrfToken();
// Validate token
if (!$this->validateCsrfToken($request)) {
$request->set('errorcode', 'InvalidCsrfToken');
return self::CMD_ERROR;
}
Do not implement state-changing operations through GET requests.
CSRF protection requires an active session. Production applications should use secure session cookies with appropriate Secure, HttpOnly and SameSite settings and should use HTTPS.
Commands define the required authentication level. Typical levels are:
new \controllerframework\sessions\NoLoginRequired()
new \controllerframework\sessions\UserLogin()
new \controllerframework\sessions\AdminLogin()
Administrative commands that expose or modify sensitive data should normally require AdminLogin.
Controller Framework 1.0.31 provides \controllerframework\security\AccessToken for protected public operations.
$accessToken =
\controllerframework\security\AccessToken::generate(
'mollie-order',
(string) $orderid
);
\controllerframework\security\AccessToken::validate(
'mollie-order',
(string) $orderid,
$accessToken
);
The purpose and identifier form part of the security boundary. Tokens should only be transmitted over HTTPS and should not be unnecessarily logged.
Payment amounts must come from trusted server-side data. A browser must never be allowed to determine the amount sent to Mollie.
The payment object should be loaded from the database and its server-side amount used when creating the payment.
Mollie integration is optional. Relevant commands include:
PaymentToMollieCommand
OrderToMollieCommand
WebhookFromMollieCommand
PaymentConfirmationCommand
The generic payment command can be decorated by the client application.
The payment amount is obtained from the server-side Payment object. The order description can be supplied by the client-specific CommandDecorator through the Request:
$request->set(
'orderDescription',
'Concert tickets - May 2027'
);
The underlying OrderToMollieCommand reads this value. This allows different order types to have different descriptions without modifying the generic framework command.
The webhook is a machine-to-machine endpoint rather than a normal user interface. It should retrieve the current payment status from Mollie and update the corresponding server-side payment.
The customer redirect is not the authoritative source of payment status.
Payment confirmation should validate the protected order identifier and its AccessToken before displaying information associated with a payment.
Core domain models include:
Activity
ActivityComposite
Costitem
Member
Subscription
Payment
GoogleWalletTicket
The client application should use the model API where possible instead of directly manipulating database records.
The framework uses mapper classes such as ActivityMapper, CostitemMapper, PaymentMapper and SubscriptionMapper.
For example:
$payment = \model\Payment::find($id);
Special care is required with Mapper::findAll(), because it accepts a free SQL select clause.
$mapper->findAll($selectClause);
Views are responsible for presentation. Commands should prepare data and pass it to the view.
Command routing is defined in controls.xml. A simplified example:
<command
path="/activity"
class="\commands\user\PublicActivityCommand">
<view name="/user/activity" />
<status value="CMD_ERROR">
<view name="/errorView" />
</status>
</command>
Use the Controller Framework's centralized error handling for unexpected exceptions.
try {
$activity = \model\Activity::find($id);
}
catch (\Throwable $ex) {
\controllerframework\error\ErrorHandler
::handleException($ex);
$request->addFeedback(
'Unable to retrieve the requested activity.'
);
return self::CMD_ERROR;
}
Do not expose stack traces or exception dumps to users. Most importantly, do not catch an exception and then continue as if the failed operation had succeeded.
Take special care with values used in:
Location headers;Content-Disposition;Validate or whitelist such values before placing them in HTTP headers. When constructing query strings, prefer http_build_query() to manual concatenation.
The framework supports asynchronous mail processing through the mail_queue table. Typical statuses include:
pending
sending
sent
failed
A typical workflow is:
Cron jobs should use absolute paths:
php /absolute/path/to/application/index.php cleanupMailQueue
Do not rely on the current working directory in cron jobs.
Google Wallet integration is optional. Applications using it require appropriate Google credentials, an issuer ID and application configuration. Credential files should be kept outside publicly accessible locations whenever possible.
The framework provides extension points such as:
ActivityTypeImplementation
CostitemTypeImplementation
PaymentTypeImplementation
SubscriptionTypeImplementation
Client-specific business rules should be implemented in these extension points where appropriate rather than accumulating all business logic in controllers.
Add the route to controls.xml.
namespace commands\user;
class MyActivityCommand
extends \controllerframework\controllers\CommandDecorator
{
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
// Application-specific preparation
return null;
}
public function initCommand(): void
{
$this->setCommand(
new \membersactivities\commands\user\ActivityCommand()
);
}
protected function getLevelOfLoginRequired(): void
{
$this->setLoginLevel(
new \controllerframework\sessions\UserLogin()
);
}
}
$request->set(
'validator',
new \model\SubscriptionValidationUser()
);
Create the appropriate template.
<status value="CMD_ERROR">
<view name="/errorView" />
</status>
Test authenticated and unauthenticated users, invalid identifiers, invalid POST data, invalid CSRF tokens and expected failure paths.
app_options.ini protected_SALTRAND generated and secretMembersActivities Framework 1.0.31 is intended to be used as a reusable domain framework rather than as a complete application.
The key development principle is:
This makes client applications easier to maintain and allows framework upgrades without unnecessarily modifying application code.