Membersactivities framework basic example

12 Sep 2026 - dirkvm


MembersActivities Framework 1.0.31 — Basic Example Guide

MembersActivities Framework 1.0.31

Basic Example Application Guide
FrameworkMembersActivities 1.0.31
Controller Framework1.0.31
PurposeLearning and starting-point application
DatabaseMySQL / MariaDB

Contents

  1. Purpose of the Example
  2. Example Folder Structure
  3. Prerequisites
  4. Create the Application
  5. Install Dependencies
  6. Create the Database
  7. Create the First Administrator
  8. Configure the Application
  9. Understand index.php
  10. Understand Client Autoloading
  11. Understand controls.xml
  12. The Default Command
  13. Create Client Model Classes
  14. Activity and Costitem Types
  15. Subscription Validation Strategies
  16. CommandDecorator Example
  17. Public Activity Example
  18. Payment Example
  19. Mollie Example
  20. Administration Example
  21. Run and Test the Example
  22. How to Turn the Example into Your Application
  23. Basic Implementation Checklist

1. Purpose of the Example

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:

Important: the example is intentionally application-specific. It contains sample values, sample texts and example business rules. Replace those with the rules and configuration of your own application.

Example Files

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.

2. Example Folder Structure

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/
    └── ...

3. Prerequisites

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.

4. Create the Application

Step 1. Copy the complete example directory to a working application directory.
Step 2. Rename the directory if desired.
Step 3. Do not publish the configuration directory or credential files directly as downloadable web resources.
Step 4. Keep the application's framework-specific classes below 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.

5. Install Dependencies

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.

6. Create the Database

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.

For an existing application: do not simply import the reference schema over a production database. Back up the database and apply controlled schema changes.

7. Create the First Administrator

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.

Step 1. Generate a password hash using PHP.
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.

Step 2. Insert the first administrator into the 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:

FieldValuePurpose
classificationRGLRUses the regular member type implementation supplied by the example.
roleAIdentifies the member as an administrator for the administrator login strategy.
passwordPHP password_hash() resultStores the password as a secure password hash rather than plain text.
ownpwd1Indicates that the member has their own password.
active1Activates the member account.
Security: 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.

Why this step is necessary: the example's configuration uses _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.

8. Configure the Application

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

8.1 Application identity

APP=BM_test
_LOGO=assets/[Your logo].jpg

Replace these with the identity of your own application.

8.2 Login policy

_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.

8.3 Payment policy

_WTALLOWED='Y'

This controls whether wire transfer is available as an alternative to online payment.

8.4 Mollie

_MOLLIECONFIG="test_[your mollie key]"

Use a test key during development and a live key only in a properly secured production configuration.

.5 Google Wallet

_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.

9. Understand index.php

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:

index.php │ ├── Composer autoloader │ ├── client application autoloader │ └── Controller::run() │ ▼ framework request processing

10. Understand Client Autoloading

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.

11. Understand controls.xml

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

12. The Default Command

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.

13. Create Client Model Classes

The example supplies concrete client classes for the abstract MembersActivities models.

Activity

namespace model;

class Activity
    extends \membersactivities\model\activities\Activity
{
    // client-specific code
}

Costitem

namespace model;

class Costitem
    extends \membersactivities\model\activities\Costitem
{
    // client-specific code
}

Subscription

namespace model;

class Subscription
    extends \membersactivities\model\subscriptions\Subscription
{
    // client-specific code
}

Payment

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.

14. Activity, Member and Costitem Types

The example demonstrates classification-specific type implementations.

Activity_RGLR

class Activity_RGLR
    extends \membersactivities\model\activities\ActivityTypeImplementation
{
}

This represents a normal activity without additional rules.

Activity_STMP

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.

Costitem_RGLR

class Costitem_RGLR
    extends \membersactivities\model\activities\CostitemTypeImplementation
{
}

Member types

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.

15. Subscription Validation Strategies

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.

16. CommandDecorator Example

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.

17. Public Activity Example

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.

/activity?id=123 │ ▼ commands\user\PublicActivityCommand │ ├── validate ID ├── load Activity └── determine seatmap │ ▼ membersactivities\commands\user\PublicActivityCommand │ ▼ views/user/activity.php

The view renders activity information, cost items and, when enabled, the seat-map interface.

18. Payment Example

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.

19. Mollie Example

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:

Client PaymentToMollieCommand │ ├── determines paymentConfirmation route └── determines orderDescription │ ▼ Framework OrderToMollieCommand │ ├── retrieves server-side Payment ├── uses server-side amount └── creates Mollie payment

The order description is therefore client-specific and travels through the Request. The payment amount must remain server-side.

18.1 Payment status processing

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.

20. Administration Example

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.

21. Run and Test the Example

20.1 Development server

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.

20.2 First test

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.

20.3 Test an activity

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.

20.4 Test payment

Use the test Mollie configuration. Verify the complete sequence:

Activity │ ▼ Create Payment │ ▼ Mollie │ ├── customer return │ └── webhook │ ▼ Payment status update │ ▼ Payment_RGLR::statusReceived()

20.5 Test administration

Log in as an administrator and verify activity, cost item, member and payment management.

22. How to Turn the Example into Your Application

The recommended approach is incremental.

1. Keep the framework dependency unchanged.
Start with MembersActivities Framework 1.0.31.
2. Replace configuration.
Set application name, domain, database, mail and integration credentials.
3. Replace the client models.
Adapt Member, Activity, Costitem, Subscription and Payment to your domain.
4. Define classifications.
Create type implementations such as Activity_RGLR or your own application-specific types.
5. Define validation strategies.
Implement the rules for public users, members and administrators.
6. Adapt views.
Replace example branding, texts and layout.
7. Add decorators.
Use CommandDecorators for client-specific behaviour around framework commands.
8. Configure payments.
Keep amounts server-side and use the Request mechanism for client-specific payment descriptions.
9. Remove unused integrations.
If you do not use Google Wallet, seat maps or another optional feature, do not retain unnecessary configuration or code.
10. Test before production.
Test authentication, CSRF, subscriptions, payments, webhooks, exports and error paths.

23. Basic Implementation Checklist

AreaCheck
Composersamoscon/membersactivities-framework is set to 1.0.31.
Controller Framework1.0.31 is installed through Composer.
ConfigurationApplication, database and integration settings are replaced with client values.
Secrets_SALTRAND, database passwords and API credentials are protected.
DatabaseReference schema is imported only for a new application or migrated deliberately.
ModelsRequired client model classes exist.
Type implementationsEvery classification used by the database has a corresponding client type class.
ValidationPublic/user/admin subscription rules are explicitly defined.
RoutingAll required commands and views are registered in controls.xml.
SecurityPOST/CSRF, login levels and AccessToken flows are tested.
PaymentsPayment amounts come from server-side Payment objects.
MollieTest webhook and payment confirmation flow works before live deployment.
MailSMTP settings and mail queue processing are tested.
CronAbsolute paths are used.
ProductionHTTPS, protected configuration and production credentials are configured.