Membersactivities framework client application development guide

16 Sep 2026 - dirkvm


MembersActivities Framework 1.1.0 — Client Application Developer Guide

MembersActivities Framework 1.1.0

Client Application Developer Guide
Version1.1.0
Previous Version1.0.31
Controller Framework1.0.31
PHP8.3 or compatible PHP 8.x
DatabaseMySQL / MariaDB
AuthorDirk Van Meirvenne

Contents

  1. Introduction
  2. What's New in Version 1.1.0
  3. Technology Requirements
  4. Installing the Framework
  5. Recommended Client Application Structure
  6. Application Configuration
  7. Database
  8. MVC Application Flow
  9. Commands
  10. CommandDecorator
  11. Subscription Validation Strategy
  12. Request Object
  13. CSRF Protection
  14. Sessions
  15. Authentication and Login Levels
  16. AccessToken
  17. Payment Security
  18. Mollie Integration
  19. Mollie Webhook
  20. Models and the Composite Pattern
  21. Tickets
  22. Ticket Scanning
  23. Seat Reservations
  24. Mapper Usage and SQL Security
  25. Views and controls.xml
  26. Error Handling
  27. Header and Redirect Security
  28. Mail Queue and Cron
  29. Google Wallet
  30. Application-Specific Types
  31. Designing a New Client Screen
  32. Migration from 1.0.31
  33. Testing
  34. Deployment Checklist
  35. Architectural Summary

1. Introduction

The MembersActivities Framework is an application framework for building web applications that manage members, activities, subscriptions, payments, tickets 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.

Version 1.1.0 introduces persistent ticket entities and ticket scanning as first-class framework concepts.

Client Application │ ├── Client Commands ├── Client CommandDecorators ├── Client Views ├── Client Strategies ├── Client Ticket Types └── Client configuration │ ▼ MembersActivities Framework 1.1.0 │ ▼ Controller Framework 1.0.31

The client application should normally extend and configure the framework rather than modify framework source code.

2. What's New in Version 1.1.0

MembersActivities Framework 1.1.0 introduces several changes compared with version 1.0.31.

2.1 Persistent tickets

Tickets are now represented by individual Ticket domain objects and stored in the ticket database table.

Previously, a subscription's quantity represented the number of tickets. In version 1.1.0, each ticket receives its own persistent identity and token.

Subscription │ ├── Ticket #152 ├── Ticket #153 └── Ticket #154

This makes it possible to individually identify, validate, cancel and scan tickets.

2.2 Ticket scanning

The new TicketScan model records ticket scan information and supports scan results such as:

valid
already_used
cancelled
invalid
wrong_activity

The framework also provides an atomic ticket-claim operation so that two scanners cannot successfully use the same ticket at the same time.

2.3 Ticket scanning AccessToken

Version 1.1.0 adds a dedicated ticket_scan AccessToken for an activity.

$token = \controllerframework\security\AccessToken::generate(
    'ticket_scan',
    (string) $activityId
);

This token can be used by a client ticket scanner to protect ticket-scanning operations for a specific activity.

2.4 Seat reservation conflict detection

When an activity uses a seat map, the framework now checks whether requested seats have been reserved between the time the seat map was displayed and the time the subscription is submitted.

This reduces the possibility of two users purchasing the same seat concurrently.

2.5 Google Wallet uses real ticket IDs

Google Wallet integration now uses persistent ticket records rather than generating ticket numbers from the subscription quantity.

Each Google Wallet object therefore corresponds to an actual ticket entity.

2.6 Payment status handling

The administrator payment edit command no longer updates the payment status through the generic update operation. Payment status transitions should be handled through the dedicated payment status mechanism, such as statusReceived.

2.7 Database changes

Version 1.1.0 adds:

3. Technology Requirements

ComponentRequirement
PHP8.3 or compatible PHP 8.x
Controller Framework1.0.31
MembersActivities Framework1.1.0
DatabaseMySQL / MariaDB
Database accessPDO
Web serverApache or compatible PHP web server
ComposerRequired

The framework uses a MySQL/MariaDB PDO connection. Oracle is not a supported database platform for this release.

4. Installing the Framework

A client application installs the framework through Composer:

{
    "require": {
        "samoscon/membersactivities-framework": "^1.1.0"
    }
}

MembersActivities Framework 1.1.0 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.

Upgrade note: version 1.1.0 introduces database structures for tickets and ticket scanning. Existing production applications should therefore perform a controlled database migration before enabling ticket functionality.

5. Recommended Client Application Structure

application/
│
├── index.php
├── composer.json
├── composer.lock
│
├── config/
│   └── app_options.ini
│
├── MVCFramework/
│   ├── controls.xml
│   ├── commands/
│   │   ├── DefaultCommand.php
│   │   ├── admin/
│   │   ├── user/
│   │   ├── ajax/
│   │   ├── downloads/
│   │   └── mollie/
│   ├── model/
│   └── views/
│
└── assets/

For ticket-enabled applications, client-specific ticket implementations may be added to the model directory:

model/
├── Ticket.php
├── Ticket_RGLR.php
├── TicketMapper.php
├── TicketScan.php
├── TicketScan_RGLR.php
└── TicketScanMapper.php

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.

6. Application Configuration

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
Security: values such as database passwords, Mollie credentials, Google credentials and especially _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.

7. Database

The reference schema contains the following principal tables:

member
activity
costitem
subscription
payment
ticket
ticket_scan
remember_tokens
mail_queue
Member ├── Subscriptions ├── Payments └── Remember Tokens Activity ├── Cost Items └── child Activities Subscription ├── Member ├── Cost Item ├── Payment └── Tickets ├── Ticket ├── Ticket └── Ticket Ticket └── Ticket Scans

7.1 Ticket table

The ticket table contains the persistent identity and state of each ticket.

FieldPurpose
idUnique ticket identifier.
descriptionTicket description.
classificationTicket type, defaulting to RGLR.
subscription_idSubscription to which the ticket belongs.
tokenUnique ticket token used for ticket identification.
statusTicket state: valid or cancelled.
usedIndicates whether the ticket has already been claimed.
used_atDate and time at which the ticket was claimed.
created_atTicket creation timestamp.

7.2 Ticket scan table

The ticket_scan table records scans associated with a ticket.

FieldPurpose
idUnique scan identifier.
descriptionScan description.
classificationScanner type, defaulting to RGLR.
ticket_idTicket that was scanned.
scannerIdentifier of the scanner or scanning application.
scanned_atScan timestamp.
resultResult of the scan.

7.3 Remember tokens

remember_tokens.expires_at is now nullable:

expires_at DATETIME DEFAULT NULL

A client application should document how a null expiration value is interpreted by its authentication policy.

7.4 Mail queue

The following fields are nullable:

subject
body
recipient

This allows mail queue records to represent processing states where these values are not yet available or are supplied by another part of the mail-processing workflow.

Existing production database: do not replace it with the reference schema. Back it up and apply controlled schema changes or migrations.

8. MVC Application Flow

Browser │ ▼ index.php │ ▼ Controller Framework │ ▼ Command Resolver │ ▼ Client Command / CommandDecorator │ ▼ MembersActivities Command │ ▼ Model │ ▼ Database

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.

9. Commands

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.

10. CommandDecorator

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.

11. Subscription Validation Strategy

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.

Security rule: never instantiate a validator class directly from a request parameter. Use a controlled CommandDecorator to select the concrete strategy.

12. Request Object

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.

13. CSRF Protection

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;
}
GET │ ├── display form └── generate CSRF token │ ▼ POST │ ├── validate CSRF token ├── validate input ├── perform state change └── return status

Do not implement state-changing operations through GET requests.

14. Sessions

CSRF protection requires an active session. Production applications should use secure session cookies with appropriate Secure, HttpOnly and SameSite settings and should use HTTPS.

15. Authentication and Login Levels

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.

16. AccessToken

Controller Framework 1.0.31 provides \controllerframework\security\AccessToken for protected public operations.

16.1 Payment AccessToken

$accessToken =
    \controllerframework\security\AccessToken::generate(
        'mollie-order',
        (string) $orderid
    );

\controllerframework\security\AccessToken::validate(
    'mollie-order',
    (string) $orderid,
    $accessToken
);

16.2 Ticket scanning AccessToken

MembersActivities Framework 1.1.0 introduces a dedicated purpose for ticket scanning:

$ticketScanToken =
    \controllerframework\security\AccessToken::generate(
        'ticket_scan',
        (string) $activityId
    );

The generated token is returned by the activity administration command as:

ticket_scantoken

The token identifies the activity for which ticket scanning is authorized.

Security: ticket scanning tokens should only be transmitted over HTTPS. Do not place them in logs, publicly accessible documents or URLs where they can unnecessarily be disclosed.

17. Payment Security

Payment amounts must come from trusted server-side data. A browser must never be allowed to determine the amount sent to Mollie.

Browser │ │ order ID ▼ Server │ ├── retrieve Payment ├── read trusted amount └── create Mollie payment

The payment object should be loaded from the database and its server-side amount used when creating the payment.

In version 1.1.0, administrator payment editing should not use the generic update operation to change payment status. Payment status changes should go through the dedicated payment status mechanism.

18. Mollie Integration

Mollie integration is optional. Relevant commands include:

PaymentToMollieCommand
OrderToMollieCommand
WebhookFromMollieCommand
PaymentConfirmationCommand

The generic payment command can be decorated by the client application.

18.1 Application-specific order descriptions

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.

19. Mollie Webhook and Payment Confirmation

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.

Applications using tickets should create or update ticket state based on the confirmed server-side payment state rather than trusting client-side payment information.

20. Models and the Composite Pattern

Core domain models include:

Activity
ActivityComposite
Costitem
Member
MemberComposite
Subscription
Payment
Ticket
TicketScan
GoogleWalletTicket

The client application should use the model API where possible instead of directly manipulating database records.

20.1 Composite Domain Objects

The MembersActivities Framework uses the Composite design pattern for two important domain concepts: Activities and Members.

This allows a domain object to represent either an individual object or a parent containing child objects, while exposing a consistent object-oriented model to the client application.

Composite │ ┌─────────┴─────────┐ │ │ Activity Member │ │ children children │ │ Activity Member Activity Member Activity Member

20.2 Activity Composite

An Activity can have child activities. This is represented by the ActivityComposite concept.

A parent activity can therefore act as a container for one or more child activities without requiring the client application to handle parent and child activities as fundamentally different objects.

Activity │ ├── Activity ├── Activity └── Activity

This is useful when an application wants to group related activities under a common parent.

For example, an application could model a concert series as a parent activity with individual concerts as child activities:

2026 Concert Season │ ├── Concert 1 ├── Concert 2 ├── Concert 3 └── Concert 4

The exact interpretation of the parent-child relationship is application-specific. The framework provides the object model and persistence mechanism; the client application defines how the hierarchy is presented and used.

20.3 Member Composite

The same Composite principle is also used for Member.

A member can have child members. This allows a client application to represent relationships where one member acts as a parent of one or more other members.

Member │ ├── Member ├── Member └── Member

This can, for example, be used to model a household or another application-specific member hierarchy.

The child members remain normal Member objects. The parent-child relationship does not require the client application to introduce a separate domain concept for every level of the hierarchy.

20.4 Common Composite principle

The Activity and Member hierarchies follow the same object-oriented principle:

Concept Parent Children
Activity Parent Activity Child Activities
Member Parent Member Child Members

Conceptually, both can be represented as:

Domain Object │ ┌─────────┴─────────┐ │ │ Parent Child │ │ └───────┬───────────┘ │ same domain type

This is an application of the Composite Object-Oriented Design Pattern: a parent object and its child objects belong to the same domain type and can therefore be treated consistently by application code.

Client application principle: do not assume that every Activity or Member is a leaf object. When implementing business logic, views or navigation, always consider that an Activity or Member may participate in a parent-child hierarchy.

20.5 Working with hierarchies

Client applications should use the framework's model relationships rather than reproducing parent-child logic in controllers or views.

For Activities, this means that application code should take the ActivityComposite relationship into account when displaying or processing activities.

For Members, the same principle applies to the Member hierarchy.

The hierarchy can be visualized as:

Activity hierarchy Parent Activity │ ├── Child Activity │ ├── Child Activity │ └── Child Activity │ └── Child Activity Member hierarchy Parent Member │ ├── Child Member │ ├── Child Member │ └── Child Member │ └── Child Member

Because the relationship is recursive, more than one level of nesting can be represented where the application's business rules require it.

20.6 Why the Composite pattern is useful

21. Tickets

Version 1.1.0 introduces Ticket as a first-class domain object.

The framework provides:

\membersactivities\model\subscriptions\Ticket
\membersactivities\model\subscriptions\TicketMapper
\membersactivities\model\subscriptions\TicketTypeImplementation

These classes are framework components. A client application can provide concrete implementations where application-specific ticket behaviour is required.

21.1 Ticket lifecycle

Ticket created │ ▼ VALID │ ├──────────────┐ │ │ ▼ ▼ scanned cancelled │ ▼ USED

The database represents the active state using status and used.

StateMeaning
status = valid, used = 0Ticket can be used.
status = valid, used = 1Ticket has already been used.
status = cancelledTicket has been cancelled and must not be accepted.

21.2 Ticket token

Every ticket has a unique token stored in the token field.

The framework provides:

$ticket = \model\Ticket::findByToken($token);

This allows a scanner to identify a ticket without exposing or relying on internal database IDs.

21.3 Atomic ticket claiming

The framework provides:

$success = $ticket->claim();

The operation is atomic. Internally it only changes the ticket when:

status = 'valid'
AND
used = 0

This is important for ticket scanning because two scanners may attempt to validate the same ticket almost simultaneously.

Only the scanner that successfully performs the atomic claim should treat the ticket as newly accepted.

21.4 Ticket type implementation

TicketTypeImplementation provides an extension point for application-specific ticket types.

The framework uses the ticket classification field to select the corresponding implementation.

The default classification is:

RGLR

A client application may therefore provide a corresponding regular ticket implementation where required.

22. Ticket Scanning

Version 1.1.0 introduces the TicketScan domain model.

The framework provides:

\membersactivities\model\subscriptions\TicketScan
\membersactivities\model\subscriptions\TicketScanMapper
\membersactivities\model\subscriptions\TicketScanTypeImplementation

As with Ticket, these are framework components that can be specialized by the client application.

22.1 Scan results

The database supports the following scan results:

valid
already_used
cancelled
invalid
wrong_activity

The client scanner should distinguish between these results so that the operator can understand why a ticket was accepted or rejected.

22.2 Recommended scanning flow

Scanner │ │ ticket token ▼ Server │ ├── validate ticket_scan AccessToken ├── find Ticket by token ├── verify activity ├── verify ticket status ├── atomically claim ticket │ ├── success ───────► valid │ └── failure ───────► already_used / cancelled / invalid │ ▼ TicketScan record

The exact user interface is application-specific, but the server should remain authoritative for ticket validation.

22.3 Recording scans

A client application can create a TicketScan record containing information such as:

ticket_id
scanner
result
scanned_at

This provides an audit trail of ticket scanning activity.

22.4 TicketScan type implementation

TicketScanTypeImplementation provides an extension point for different scanner implementations.

The default classification is:

RGLR

A client application may therefore provide a corresponding regular scan implementation where required.

23. Seat Reservations

Version 1.1.0 adds server-side conflict detection for seat reservations.

When an activity uses a seat map, the framework checks requested seats against reservations that already exist for the relevant cost item.

User opens activity │ ▼ Seat map displayed │ ▼ User selects seats │ ▼ User submits subscription │ ▼ Server checks current reservations │ ├── seats available │ │ │ ▼ │ create subscription │ └── seat already reserved │ ▼ return error ask user to retry

This check is important because the seat map shown to the user may become outdated while another user is completing a purchase.

Important: a client application must not assume that a seat remains available merely because it was available when the seat map was initially displayed. The server-side validation is authoritative.

24. Mapper Usage and SQL Security

The framework uses mapper classes such as ActivityMapper, CostitemMapper, PaymentMapper, SubscriptionMapper, TicketMapper and TicketScanMapper.

For example:

$payment = \model\Payment::find($id);

$ticket = \model\Ticket::findByToken($token);

24.1 TicketMapper

TicketMapper provides operations including:

claimTicket(int $ticketId): bool

findByToken(string $token): ?\model\Ticket

claimTicket() uses a database update with conditions on both the ticket status and used flag. This provides atomic protection against simultaneous scans.

24.2 Free SQL clauses

Special care is required with Mapper::findAll(), because it accepts a free SQL select clause.

$mapper->findAll($selectClause);
SQL security: never concatenate untrusted user input into a free SQL clause. Validate and whitelist values before they are incorporated into SQL, and prefer the framework's model/database mechanisms where possible.

25. Views and controls.xml

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>

26. Error Handling

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.

27. Header and Redirect Security

Take special care with values used in:

Validate or whitelist such values before placing them in HTTP headers. When constructing query strings, prefer http_build_query() to manual concatenation.

28. Mail Queue and Cron

The framework supports asynchronous mail processing through the mail_queue table. Typical statuses include:

pending
sending
sent
failed

A typical workflow is:

Create mail queue records │ ▼ Return response to user │ ▼ Cron job │ ▼ Process mail queue

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.

29. Google Wallet

Google Wallet integration is optional. Applications using it require appropriate Google credentials, an issuer ID and application configuration.

Version 1.1.0 changes the relationship between subscriptions and Google Wallet tickets.

29.1 Version 1.0.31 behaviour

Previously, the framework generated Google Wallet ticket objects based on the subscription quantity.

29.2 Version 1.1.0 behaviour

The framework now obtains the actual ticket records and creates a Wallet object for each persistent ticket.

Subscription │ ├── Ticket ID 152 ──► Google Wallet Object 152 │ ├── Ticket ID 153 ──► Google Wallet Object 153 │ └── Ticket ID 154 ──► Google Wallet Object 154

The ticket ID is used as the ticket number/object identifier.

Migration consideration: applications upgrading from 1.0.31 should determine how existing subscriptions are converted into individual Ticket records before relying on the new Google Wallet behaviour.

30. Application-Specific Types

The framework provides extension points such as:

ActivityTypeImplementation
CostitemTypeImplementation
PaymentTypeImplementation
SubscriptionTypeImplementation
TicketTypeImplementation
TicketScanTypeImplementation

Client-specific business rules should be implemented in these extension points where appropriate rather than accumulating all business logic in controllers.

31. Designing a New Client Screen

Step 1 — Define the route

Add the route to controls.xml.

Step 2 — Create a client CommandDecorator

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()
        );
    }
}

Step 3 — Add application-specific strategies

$request->set(
    'validator',
    new \model\SubscriptionValidationUser()
);

Step 4 — Add the view

Create the appropriate template.

Step 5 — Configure status handling

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

Step 6 — Test

Test authenticated and unauthenticated users, invalid identifiers, invalid POST data, invalid CSRF tokens and expected failure paths.

32. Migration from 1.0.31

Version 1.1.0 introduces database and application-level changes. Existing applications should therefore be upgraded in a controlled sequence.

32.1 Update Composer

{
    "require": {
        "samoscon/membersactivities-framework": "^1.1.0"
    }
}

The Controller Framework remains at version 1.0.31.

32.2 Update the database

Apply the new definitions from Basic Example guide -> DatabaseSetup.sql for:

32.3 Create tickets for existing subscriptions

Existing subscriptions previously represented multiple tickets through the quantity field.

For applications that use tickets, each relevant subscription should now have corresponding ticket records.

Before 1.1.0 Subscription │ └── quantity = 3 After 1.1.0 Subscription │ ├── Ticket ├── Ticket └── Ticket

The migration process must generate unique ticket tokens and preserve any application-specific information required by the client.

32.4 Update client models

Applications using the ticket functionality should provide concrete implementations of the new framework models and their mappers where required.

32.5 Update Google Wallet

Applications using Google Wallet should verify that existing subscriptions have corresponding ticket records before generating new Wallet objects.

32.6 Update ticket scanning

Applications introducing ticket scanning should:

Important: the framework does not automatically convert every historical subscription into individual tickets. The migration strategy is application-specific because existing applications may have different ticket, seat and payment requirements.

33. Testing a New Release

Public functionality

Member functionality

Composite functionality

Ticket functionality

Seat reservation functionality

Administrator functionality

Integration functionality

Security

34. Deployment Checklist

35. Architectural Summary

MembersActivities Framework 1.1.0 is intended to be used as a reusable domain framework rather than as a complete application.

┌────────────────────────────────────────────┐ │ Client Application │ │ │ │ Commands / Decorators / Views │ │ Strategies / Business Rules │ │ Ticket Types / Scanner Types │ │ Configuration / Integrations │ └──────────────────────┬─────────────────────┘ │ ┌──────────────────────▼─────────────────────┐ │ MembersActivities Framework 1.1.0 │ │ │ │ Members / Activities / Subscriptions │ │ Payments / Tickets / Ticket Scans │ │ Seat Reservations / Mail / Mollie / Wallet │ └──────────────────────┬─────────────────────┘ │ ┌──────────────────────▼─────────────────────┐ │ Controller Framework 1.0.31 │ │ │ │ MVC / Commands / Request / Sessions │ │ Security / CSRF / AccessToken / PDO │ │ Error handling / Audit / Rendering │ └────────────────────────────────────────────┘

35.1 Domain relationships

Activity │ ├── Child Activity │ └── Child Activity │ └── Child Activity Member │ ├── Child Member │ └── Child Member │ └── Child Member Activity │ └── CostItem │ └── Subscription │ ├── Payment │ └── Ticket │ └── TicketScan

Both Activity and Member can participate in recursive parent-child relationships following the Composite design pattern.

The ticket model introduces another important domain relationship: a subscription can contain multiple persistent tickets, and each ticket can have multiple scan records.

The key development principle remains:

Keep generic functionality in the framework and implement client-specific behaviour through decorators, strategies, models, commands, configuration and views.

Version 1.1.0 extends this principle to tickets and ticket scanning while retaining the existing Composite-based domain model for Activities and Members.

This makes client applications easier to maintain and allows framework upgrades without unnecessarily modifying application code.