Membersactivities framework basic example
12 Sep 2026 - dirkvm
The example directory included in MembersActivities Framework 1.0.31 is a complete starting-point client application. It demonstrates how the framework is intended to be integrated rather than merely showing isolated API calls.
The example contains:
The complete example application is available as a ZIP archive, including the folder structure and all underlying example files: Download the Example Folder Structure and Files (ZIP).
The database setup is provided separately and is not included in the ZIP archive: Download DatabaseSetup.sql.
example/
│
├── index.php
├── composer.json
├── DatabaseSetup.sql
├── .htaccess
│
├── config/
│ ├── app_options.ini
│ └── [Your google wallet keyfile].json
│
├── MVCFramework/
│ ├── controls.xml
│ │
│ ├── commands/
│ │ ├── DefaultCommand.php
│ │ ├── admin/
│ │ ├── ajax/
│ │ ├── cron/
│ │ ├── downloads/
│ │ ├── mollie/
│ │ └── user/
│ │
│ ├── model/
│ │ ├── Activity.php
│ │ ├── ActivityMapper.php
│ │ ├── Activity_RGLR.php
│ │ ├── Activity_STMP.php
│ │ ├── Costitem.php
│ │ ├── CostitemMapper.php
│ │ ├── Member.php
│ │ ├── MemberMapper.php
│ │ ├── Member_RGLR.php
│ │ ├── Member_PRTN.php
│ │ ├── Payment.php
│ │ ├── PaymentMapper.php
│ │ ├── Payment_RGLR.php
│ │ ├── Payment_YRLY.php
│ │ ├── Subscription.php
│ │ ├── SubscriptionMapper.php
│ │ ├── Subscription_RGLR.php
│ │ └── SubscriptionValidation*.php
│ │
│ └── views/
│ ├── defaultView.php
│ ├── errorView.php
│ ├── admin/
│ ├── login/
│ └── user/
│
└── assets/
├── logging/
├── qrcodes/
└── ...
Before running the example, install:
The example's Composer configuration requires MembersActivities Framework 1.0.31. The framework in turn uses Controller Framework 1.0.31.
example directory to a working application directory.MVCFramework/.A suitable starting point is to keep the example structure intact until the application is understood, then progressively replace the example's client-specific code.
The example's composer.json contains:
{
"require": {
"setasign/fpdf": "^1.8",
"blueimp/jquery-file-upload": "9.22.*",
"tinymce/tinymce": "^8.0",
"chillerlan/php-qrcode": "*",
"samoscon/membersactivities-framework": "^1.0.31",
"google/auth": "^1.53",
"guzzlehttp/guzzle": "^7.10",
"google/apiclient": "^2.19",
"google/apiclient-services": "~0.350"
}
}
Run:
composer install
The result is a vendor/ directory containing the framework and third-party dependencies.
The example provides DatabaseSetup.sql. Import it into a new MySQL/MariaDB database.
CREATE DATABASE your_database
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Then import the supplied SQL script using your preferred database administration tool.
After importing DatabaseSetup.sql, the database contains the required tables,
but the application still needs at least one member account that can log in as an administrator.
This is especially important for the example because the default configuration uses
_MINLEVELTOLOGIN='A', which means that the login environment requires an administrator account.
php -r "echo password_hash('Demo123', PASSWORD_DEFAULT), PHP_EOL;"
Copy the generated hash and use it in the password field of the first member.
member table.INSERT INTO member
(classification, name, lastname, email, role, password, ownpwd, active)
VALUES
(
'RGLR',
'Demo',
'Administrator',
'admin@example.com',
'A',
'[PASTE THE PASSWORD HASH HERE]',
1,
1
);
The important values for the initial administrator are:
| Field | Value | Purpose |
|---|---|---|
classification | RGLR | Uses the regular member type implementation supplied by the example. |
role | A | Identifies the member as an administrator for the administrator login strategy. |
password | PHP password_hash() result | Stores the password as a secure password hash rather than plain text. |
ownpwd | 1 | Indicates that the member has their own password. |
active | 1 | Activates the member account. |
Demo123 is only an example password.
Generate your own password hash and use a strong, unique password for any real installation.
Never store the plain-text password in the database.For a real application, replace the example name and email address with the details of the administrator who will perform the initial setup. After the first successful login, use the application's member administration facilities to create the remaining members and administrators.
_MINLEVELTOLOGIN='A', so without an active administrator member there is no account
that can satisfy the configured administrator login requirement. The administration commands
themselves also use the framework's AdminLogin strategy.Edit config/app_options.ini. The example contains placeholders such as:
_APPDIR=https://[(sub)domeinnaam]/
_HOMEPAGE=https://[your client homepage]
_ASSETDIR=https://[(sub)domeinnaam]/assets/
_SALTRAND=[long random secret]
_RAND=[random integer]
_DBUSER=[database username]
_DBPASSWORD="[database password]"
_DBNAME=[database name]
_DBHOST=localhost
APP=BM_test
_LOGO=assets/[Your logo].jpg
Replace these with the identity of your own application.
_MINLEVELTOLOGIN='A'
The example uses A to indicate an administrator-only login environment. The alternative U allows administrators and members to log in as users.
_WTALLOWED='Y'
This controls whether wire transfer is available as an alternative to online payment.
_MOLLIECONFIG="test_[your mollie key]"
Use a test key during development and a live key only in a properly secured production configuration.
_WALLETORIGIN=...
_WALLETCREDENTIALS=config/[your keyfile].json
_WALLETISSUERID=...
Google Wallet is optional. If it is not used, the corresponding configuration can remain disabled/empty according to the application's conventions.
The example's entry point is deliberately small:
<?php
include __DIR__ . '/vendor/autoload.php';
spl_autoload_register(function ($class_name) {
if (preg_match('/\\\\/', $class_name)) {
$class_name = str_replace('\\', DIRECTORY_SEPARATOR, $class_name);
}
$file = __DIR__ . DIRECTORY_SEPARATOR .
'MVCFramework' . DIRECTORY_SEPARATOR .
$class_name . '.php';
if (file_exists($file)) {
require_once $file;
}
});
try {
controllerframework\controllers\Controller::run();
} catch (\Throwable $e) {
http_response_code(500);
echo '<h1>Application initialization failed</h1>';
echo '<p>' .
htmlspecialchars(
$e->getMessage(),
ENT_QUOTES,
'UTF-8'
) .
'</p>';
exit;
}
The sequence is:
The example automatically maps namespaced client classes below MVCFramework/.
For example:
\model\Activity_RGLR
maps to:
MVCFramework/model/Activity_RGLR.php
and:
\commands\user\PublicActivityCommand
maps to:
MVCFramework/commands/user/PublicActivityCommand.php
This makes it possible for the client application to provide concrete classes expected by the abstract framework classes.
MVCFramework/controls.xml connects URL paths to commands and views.
The root route is:
<command path="/" class="\commands\DefaultCommand">
<view name="/defaultView" />
...
</command>
The activity route is:
<command path="/activity"
class="\commands\user\PublicActivityCommand">
<view name="/user/activity" />
...
</command>
The payment flow contains:
/createPayment
│
▼
/paymentToMollie
│
▼
Mollie
Mollie webhook
│
▼
/webhookFromMollie
Payment confirmation
│
▼
/paymentConfirmation
The example's DefaultCommand is a client CommandDecorator around the framework's default command.
class DefaultCommand
extends \controllerframework\controllers\CommandDecorator
{
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
$this->addResponses($request, [
'title' => 'Inloggen'
]);
return null;
}
public function initCommand(): void {
$this->setCommand(
new \membersactivities\commands\DefaultCommand()
);
}
protected function getLevelOfLoginRequired(): void {
$this->setLoginLevel(
new \controllerframework\sessions\NoLoginRequired()
);
}
}
The client changes only what it needs: here, the page title.
The example supplies concrete client classes for the abstract MembersActivities models.
namespace model;
class Activity
extends \membersactivities\model\activities\Activity
{
// client-specific code
}
namespace model;
class Costitem
extends \membersactivities\model\activities\Costitem
{
// client-specific code
}
namespace model;
class Subscription
extends \membersactivities\model\subscriptions\Subscription
{
// client-specific code
}
namespace model;
class Payment
extends \membersactivities\model\subscriptions\Payment
{
// client-specific code
}
Most of these classes are intentionally almost empty. Their existence allows the framework's abstract model architecture to be specialized by the client.
The example demonstrates classification-specific type implementations.
class Activity_RGLR
extends \membersactivities\model\activities\ActivityTypeImplementation
{
}
This represents a normal activity without additional rules.
class Activity_STMP
extends \membersactivities\model\activities\ActivityTypeImplementation
{
public function seatmap(): bool {
return true;
}
}
This type tells the activity command/view that a seat map should be used.
class Costitem_RGLR
extends \membersactivities\model\activities\CostitemTypeImplementation
{
}
The example contains:
Member_RGLR
Member_PRTN
These extend MemberTypeImplementation and demonstrate how client-specific member rules can be added.
For example, Member_RGLR implements a yearly participation fee, while Member_PRTN contains partner-specific logic.
The example contains three strategies:
SubscriptionValidationPublic
SubscriptionValidationUser
SubscriptionValidationAdmin
The user strategy checks:
if($subscribableitem->activity->subscriptionPeriodOver()) {
return $this->errorcode(
100,
'Inschrijving of annuleren is jammer genoeg niet langer mogelijk.'
);
}
if(!$member->active) {
return $this->errorcode(
200,
'Je lidmaatschap is gedeactiveerd.'
);
}
if($member->subscriptionuntil <
$subscribableitem->activity->date) {
return $this->errorcode(
201,
'Gelieve eerst je lidgeld voor volgend jaar te betalen'
);
}
return $this->errorcode(0);
The admin strategy extends the user strategy and deliberately applies different rules.
The example's PublicActivityCommand illustrates the recommended extension pattern.
class PublicActivityCommand
extends \controllerframework\controllers\CommandDecorator
{
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
$id = filter_var(
$request->get('id'),
FILTER_VALIDATE_INT
);
if(!$id) {
$request->set('errorcode', 'wrongID');
$request->addFeedback("Wrong ID");
return self::CMD_ERROR;
}
$activity = \model\Activity::find($id);
$seatmap =
$activity->activitytypeimplementation->seatmap();
$this->addResponses($request, [
'seatmap' => $seatmap
]);
return null;
}
public function initCommand(): void {
$this->setCommand(
new \membersactivities\commands\user\PublicActivityCommand
);
}
}
The decorator performs client-specific work and then delegates the generic activity operation to the MembersActivities command.
The route is:
/activity?id=123
The client decorator validates the ID, retrieves the activity and determines whether the activity's type requires a seat map.
The generic framework command then prepares the activity, member and cost-item information for the view.
The view renders activity information, cost items and, when enabled, the seat-map interface.
The example uses a separate CreatePaymentCommand decorator. It currently delegates without adding extra logic.
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
return null;
}
The wrapped framework command performs the generic payment creation.
This is a useful template: client code can remain empty until application-specific behaviour is actually required.
The example's PaymentToMollieCommand extends the framework Mollie command.
class PaymentToMollieCommand
extends \membersactivities\commands\mollie\PaymentToMollieCommand
{
public function doExecuteDecorator(
\controllerframework\registry\Request $request
): ?int {
$id = filter_var(
$request->get('id'),
FILTER_VALIDATE_INT
);
$request->set(
'paymentConfirmation',
'paymentConfirmation'
);
$request->set(
'orderDescription',
APP . " orderid=" . $id
);
return null;
}
}
This demonstrates an important separation:
The order description is therefore client-specific and travels through the Request. The payment amount must remain server-side.
The example's Payment_RGLR implements statusReceived(). When the status becomes paid, it can:
This is an example of a client-specific payment type implementation.
The example contains administrative commands for:
A typical client decorator explicitly requires administrator login:
protected function getLevelOfLoginRequired(): void {
$this->setLoginLevel(
new \controllerframework\sessions\AdminLogin()
);
}
This should be the normal pattern for commands exposing administrative data or state-changing administrative operations.
For a local PHP development environment, the application's public entry point should be index.php. Depending on the Controller Framework's routing requirements and your web-server setup, configure the document root and rewrite rules according to the supplied .htaccess.
Open the configured application homepage:
https://your-domain.example/
The root command forwards to the configured login/activity/admin flow depending on the returned command status.
Use an activity ID from the database:
https://your-domain.example/activity?id=1
Verify that the activity, cost items and appropriate seat-map behaviour are displayed.
Use the test Mollie configuration. Verify the complete sequence:
Log in as an administrator and verify activity, cost item, member and payment management.
The recommended approach is incremental.
Member, Activity, Costitem, Subscription and Payment to your domain.Activity_RGLR or your own application-specific types.| Area | Check |
|---|---|
| Composer | samoscon/membersactivities-framework is set to 1.0.31. |
| Controller Framework | 1.0.31 is installed through Composer. |
| Configuration | Application, database and integration settings are replaced with client values. |
| Secrets | _SALTRAND, database passwords and API credentials are protected. |
| Database | Reference schema is imported only for a new application or migrated deliberately. |
| Models | Required client model classes exist. |
| Type implementations | Every classification used by the database has a corresponding client type class. |
| Validation | Public/user/admin subscription rules are explicitly defined. |
| Routing | All required commands and views are registered in controls.xml. |
| Security | POST/CSRF, login levels and AccessToken flows are tested. |
| Payments | Payment amounts come from server-side Payment objects. |
| Mollie | Test webhook and payment confirmation flow works before live deployment. |
| SMTP settings and mail queue processing are tested. | |
| Cron | Absolute paths are used. |
| Production | HTTPS, protected configuration and production credentials are configured. |