Membersactivities framework technical guide

14 Sep 2026 - dirkvm


MembersActivities Framework 1.1.0 — Technical Guide

MembersActivities Framework 1.1.0

Technical Guide & Object Model Reference
FrameworkMembersActivities 1.1.0
Previous Version1.0.31
Controller Framework1.0.31
PHP8.3+
DatabaseMySQL / MariaDB via PDO
AuthorDirk Van Meirvenne

Contents

  1. Scope and Architecture
  2. Release 1.1.0 Technical Changes
  3. Package Structure
  4. Architectural Patterns
  5. Object Model Overview
  6. Activity Model
  7. Member Model
  8. Costitem Model
  9. Subscription Model
  10. Payment Model
  11. Ticket Model
  12. TicketScan Model
  13. Google Wallet Model
  14. Mapper Layer
  15. Command Layer
  16. Command Decorators
  17. Strategy and Type Implementations
  18. Subscription Validation
  19. Request / Response Flow
  20. Security Architecture
  21. Payment and Mollie Flow
  22. Ticket Scanning Flow
  23. Seat Reservation Consistency
  24. Database Model
  25. Transactions and Consistency
  26. Mail Queue
  27. Extension Guide
  28. Migration from 1.0.31
  29. API / Class Reference
  30. Technical Review Checklist

1. Scope and Architecture

MembersActivities Framework 1.1.0 is a reusable domain framework for applications managing members, activities, cost items, subscriptions, payments, tickets and ticket scanning. It is implemented as a Composer package and extends the Controller Framework.

┌──────────────────────────────────────────────────────────┐ │ Client Application │ │ │ │ Commands · Decorators · Views · Client Models │ │ Type Implementations · Validation Strategies │ │ Ticket Types · TicketScan Types · Integrations │ └──────────────────────────┬───────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────┐ │ MembersActivities Framework 1.1.0 │ │ │ │ Activities · Members · Costitems · Subscriptions │ │ Payments · Tickets · TicketScans │ │ Mappers · Commands · Mollie · Google Wallet │ └──────────────────────────┬───────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────┐ │ Controller Framework 1.0.31 │ │ │ │ Command · Request · DomainObject · Mapper │ │ Registry · Sessions · CSRF · AccessToken │ │ ErrorHandler · Rendering · Audit │ └──────────────────────────────────────────────────────────┘

The Composer package declares PHP ^8.3, samoscon/controller-framework ^1.0.31 and the Mollie API package. Mollie remains an optional application-level integration even though its package is available to the framework.

Version 1.1.0 extends the domain model with persistent tickets and ticket scans while retaining the existing Activity and Member hierarchical model.

2. Release 1.1.0 Technical Changes

Version 1.1.0 introduces the following principal technical changes compared with 1.0.31.

Area Version 1.1.0 change
Ticket domain Introduces persistent Ticket objects and ticket persistence.
Ticket scanning Introduces TicketScan objects and scan persistence.
Ticket security Introduces an activity-specific ticket_scan AccessToken purpose.
Seat reservations Adds server-side conflict detection before a subscription is created.
Google Wallet Wallet ticket generation is based on actual Ticket objects rather than subscription quantity.
Payment status Payment status changes use the dedicated status mechanism rather than generic payment editing.
Remember tokens remember_tokens.expires_at is nullable.
Mail queue subject, body and recipient are nullable.
Composite model Both Activity and Member explicitly participate in recursive parent-child relationships.

2.1 Ticket domain model

In version 1.0.31, the quantity of tickets was represented by the quantity property of a subscription. Version 1.1.0 introduces an individual persistent Ticket entity.

Version 1.0.31 Subscription │ └── quantity = 3 Version 1.1.0 Subscription │ ├── Ticket ├── Ticket └── Ticket

This allows individual tickets to have their own token, state, scan history and identity.

2.2 Ticket scanning

The new TicketScan model records an attempt to scan a ticket. The framework can distinguish valid, already-used, cancelled, invalid and wrong-activity results.

2.3 Seat reservation conflict detection

Seat availability is no longer only a property of the seat map displayed to the user. The server performs a final check against existing reservations when the subscription is submitted.

2.4 Google Wallet

Google Wallet integration now works from persistent ticket entities. Each ticket can therefore be represented independently in Google Wallet.

3. Package Structure

src/
├── commands/
│   ├── DefaultCommand.php
│   ├── admin/
│   │   ├── AddActivityToCompositeCommand.php
│   │   ├── AddMemberToCompositeCommand.php
│   │   ├── AdminHomeCommand.php
│   │   ├── CreateActivityCommand.php
│   │   ├── CreateCostitemCommand.php
│   │   ├── CreateMemberCommand.php
│   │   ├── DeleteActivityCommand.php
│   │   ├── DeleteCostitemCommand.php
│   │   ├── DeleteMemberCommand.php
│   │   ├── DeletePaymentCommand.php
│   │   ├── EditActivityCommand.php
│   │   ├── EditCostitemCommand.php
│   │   ├── EditMemberCommand.php
│   │   ├── EditPaymentCommand.php
│   │   ├── RemoveActivityFromCompositeCommand.php
│   │   ├── RemoveMemberFromCompositeCommand.php
│   │   └── SearchMembersCommand.php
│   ├── downloads/
│   │   ├── DownloadXlsMembersCommand.php
│   │   └── DownloadXlsParticipantsCommand.php
│   ├── mollie/
│   │   ├── OrderToMollieCommand.php
│   │   ├── PaymentToMollieCommand.php
│   │   └── WebhookFromMollieCommand.php
│   └── user/
│       ├── ActivityCommand.php
│       ├── CreatePaymentCommand.php
│       ├── PaymentConfirmationCommand.php
│       ├── PublicActivityCommand.php
│       └── UserActivityCommand.php
│
├── model/
│   ├── activities/
│   │   ├── Activity.php
│   │   ├── ActivityComposite.php
│   │   ├── ActivityMapper.php
│   │   ├── ActivityTypeImplementation.php
│   │   ├── Costitem.php
│   │   ├── CostitemMapper.php
│   │   └── CostitemTypeImplementation.php
│   ├── subscriptions/
│   │   ├── Payment.php
│   │   ├── PaymentMapper.php
│   │   ├── PaymentTypeImplementation.php
│   │   ├── Subscription.php
│   │   ├── SubscriptionMapper.php
│   │   ├── SubscriptionTypeImplementation.php
│   │   ├── SubscriptionValidationStrategy.php
│   │   ├── Ticket.php
│   │   ├── TicketMapper.php
│   │   ├── TicketScan.php
│   │   ├── TicketScanMapper.php
│   │   ├── TicketScanTypeImplementation.php
│   │   └── TicketTypeImplementation.php
│   └── wallet/
│       └── GoogleWalletTicket.php

The Member model remains supplied by the Controller Framework and is normally specialized by the client application.

4. Architectural Patterns

PatternWhere usedPurpose
Active Record / Domain ObjectActivity, Costitem, Payment, Subscription, Ticket, TicketScan; Member comes from Controller FrameworkDomain objects represent persisted application entities.
Data MapperActivityMapper, CostitemMapper, PaymentMapper, SubscriptionMapper, TicketMapper, TicketScanMapperSeparates persistence operations from domain objects.
Abstract FactorygetInstance() implementationsCreates concrete client model objects from database rows.
Builder / Type implementationActivityTypeImplementation, CostitemTypeImplementation, PaymentTypeImplementation, SubscriptionTypeImplementation, TicketTypeImplementation, TicketScanTypeImplementationAssociates a domain object with classification-specific behaviour.
StrategySubscriptionValidationStrategyAllows client applications to define subscription rules independently of the generic command.
DecoratorCommandDecorator in Controller FrameworkAdds client-specific command behaviour without modifying framework commands.
CompositeActivityComposite and Member parent-child hierarchyRepresents recursive domain hierarchies using objects of the same domain type.
Atomic state transitionTicket claimingPrevents two concurrent scanners from successfully claiming the same ticket.

5. Object Model Overview

┌───────────────────┐ │ Member │ │ Controller FW │ └─────────┬─────────┘ │ parent-child │ │ ┌─────────▼─────────┐ │ Child Member │ └───────────────────┘ Member │ ├── Payment │ └── Subscription │ ├── Costitem │ │ │ └── Activity │ │ │ ├── Child Activity │ └── Child Activity │ └── Ticket │ └── TicketScan

5.1 Domain relationships

Member │ ├── parent_id ─────────► Member │ ├── 1 ──────────────── * Payment │ └── 1 ──────────────── * Subscription │ ├── Costitem │ │ │ └── Activity │ ├── Payment │ └── 1 ──────────────── * Ticket │ └── 1 ─── * TicketScan

5.2 Composite relationships

Both Activity and Member use a recursive parent-child relationship.

Activity Composite Activity │ ├── Activity │ ├── Activity │ └── Activity │ └── Activity Member Composite Member │ ├── Member │ ├── Member │ └── Member │ └── Member

The important design principle is that the parent and child are instances of the same domain type. This allows client code to work with individual objects and composite objects through a consistent abstraction.

6. Activity Model

membersactivities\model\activities\Activity is an abstract domain object extending the Controller Framework's DomainObject.

Responsibilities

Public extension point

public ?ActivityTypeImplementation
    $activitytypeimplementation = null;

Important methods

MethodReturnPurpose
getInstance(array $row)ActivityCreates a concrete activity or ActivityComposite and attaches its type implementation.
getParticipants()ObjectMapReturns paid participants, including participants inherited through the activity hierarchy.
subscriptionPeriodOver()boolChecks the activity due date and, for composites, relevant child activities.
getTotalAmountReceived()floatSums distinct paid payments associated with cost items of the activity.

6.1 ActivityComposite

ActivityComposite extends Activity and implements the Composite pattern.

Children are loaded through the Activity mapper. The composite therefore represents a parent Activity while retaining the same base domain type.

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

This permits an application to treat a single activity and a hierarchy of activities consistently where appropriate.

For a composite, subscriptionPeriodOver() evaluates the relevant child activities.

7. Member Model

Member is supplied by the Controller Framework and specialized by the client application, normally as:

class Member extends \controllerframework\members\Member
{
    // client-specific behaviour
}

7.1 Member Composite

MembersActivities Framework 1.1.0 explicitly uses the same Composite principle for members.

The reference schema supports member groups through parent_id. A Member can therefore act as a parent while one or more other Member objects refer to it as their parent.

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

The relationship is recursive. A child Member is itself a Member and can therefore have its own children where the application permits this.

This design allows client applications to represent structures such as households or other application-specific member hierarchies without introducing a separate object type for each hierarchy level.

PropertyMeaning
name / lastnameMember identity
emailContact/login email
roleUser or administrator role
passwordStored password hash managed by authentication infrastructure
activeApplication membership state
subscriptionuntilMembership validity date
parent_idOptional parent Member relationship

7.2 Composite design principle

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

Domain Parent Child Relationship
Activity Activity Activity Parent Activity → Child Activities
Member Member Member Parent Member → Child Members

In both cases the parent is not a different domain type. It is a domain object containing references to objects of the same type.

Implementation principle: client code should not assume that an Activity or Member is always a leaf object. Business logic that processes these objects should take possible parent-child relationships into account.

8. Costitem Model

Costitem represents a subscribable item belonging to an activity.

public ?CostitemTypeImplementation
    $costitemtypeimplementation = null;

During getInstance(), the framework:

  1. creates the concrete client Costitem class;
  2. initializes database properties;
  3. creates the classification-specific type implementation;
  4. loads the related Activity when activity_id is present.
Database propertyMeaning
idIdentifier
descriptionHuman-readable item description
classificationType implementation selector
priceUnit price
typeApplication-defined cost item type
activity_idOwning activity

9. Subscription Model

Subscription represents a registration by a member for a cost item.

public ?SubscriptionTypeImplementation
    $subscriptiontypeimplementation = null;

When instantiated, the framework resolves:

subscription.member
subscription.costitem
subscription.payment
subscriptiontypeimplementation

The payment relation is optional because a subscription can exist before payment has been associated with it.

9.1 Ticket relationship

In version 1.1.0 a subscription can have multiple persistent Ticket objects.

Subscription │ ├── member ├── costitem ├── payment │ └── tickets ├── Ticket ├── Ticket └── Ticket

The historical quantity concept should therefore no longer be interpreted as the complete ticket identity model. Individual Ticket records provide the persistent identity of each ticket.

10. Payment Model

Payment represents a financial transaction and extends the Controller Framework's DomainObject.

public ?PaymentTypeImplementation
    $paymenttypeimplementation = null;
MethodPurpose
getInstance()Creates the concrete client payment type and loads the member.
delete()Deletes associated subscriptions and then the payment inside a database transaction.
isPaid()Returns true when status is exactly paid.
statusReceived()Creates the payment status processing flow and delegates status-specific behaviour to the payment type implementation.

The transaction in Payment::delete() ensures that deletion of the payment and its associated subscriptions is committed atomically; an exception causes a rollback.

Payment status: client applications should use the dedicated payment status mechanism rather than treating payment status as an ordinary editable field in a generic update operation.

11. Ticket Model

membersactivities\model\subscriptions\Ticket is a persistent domain object representing an individual ticket.

11.1 Responsibilities

11.2 Ticket properties

PropertyPurpose
idPersistent ticket identifier.
descriptionTicket description.
classificationTicket type implementation selector.
subscription_idOwning subscription.
tokenUnique externally usable ticket token.
statusTicket state, such as valid or cancelled.
usedIndicates whether the ticket has already been claimed.
used_atTimestamp of successful ticket claim.
created_atCreation timestamp.

11.3 Ticket lookup

Tickets are identified by a unique token when used by external clients such as a scanner.

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

The token should be treated as untrusted input and must always be validated on the server.

11.4 Atomic ticket claim

The Ticket model provides an atomic claim operation:

$success = $ticket->claim();

The operation is successful only when the ticket is still valid and unused.

Scanner A ───────┐ │ ▼ claimTicket() │ ├── used = 0 ──► SUCCESS │ └── used = 1 ──► FAILURE ▲ │ Scanner B ───────┘

This protects the ticketing system against simultaneous scans from different devices.

11.5 Ticket type implementation

TicketTypeImplementation

The ticket classification determines the concrete ticket behaviour. The default classification is RGLR.

12. TicketScan Model

TicketScan represents an individual attempt to scan or validate a Ticket.

12.1 Responsibilities

PropertyPurpose
idPersistent scan identifier.
descriptionScan description.
classificationScan type implementation selector.
ticket_idTicket being scanned.
scannerIdentifier of the scanning device/application.
scanned_atTimestamp of the scan.
resultValidation result.

12.2 Scan result values

valid
already_used
cancelled
invalid
wrong_activity

The exact presentation of these values is a client application concern, but the server remains authoritative for determining the result.

12.3 TicketScan type implementation

TicketScanTypeImplementation

The default classification is RGLR. Client applications may provide specialized scan behaviour through their own type implementation.

13. Google Wallet Model

GoogleWalletTicket encapsulates Google Wallet integration.

MethodPurpose
auth()Initializes Google API authentication.
createClass()Creates a Wallet class.
updateClass()Updates a Wallet class.
createObject()Creates a Wallet ticket object.
updateObject()Updates a Wallet ticket object.
createJwt(int $id)Creates the JWT used to add the ticket to Google Wallet.

13.1 Ticket-based Wallet objects

Version 1.1.0 creates Wallet objects from persistent Ticket records.

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

This replaces the previous model where ticket instances were derived directly from subscription quantity.

14. Mapper Layer

Version 1.1.0 contains the following domain mappers:

ActivityMapper
CostitemMapper
SubscriptionMapper
PaymentMapper
TicketMapper
TicketScanMapper
Mapper Table Framework-specific fields
ActivityMapper activity date, duedate, longdescription, start, end, location, parent_id
CostitemMapper costitem price, type, activity_id
SubscriptionMapper subscription member_id, costitem_id, payment_id, quantity, remark
PaymentMapper payment member_id, date, amount, status, type, source
TicketMapper ticket subscription_id, token, status, used, used_at
TicketScanMapper ticket_scan ticket_id, scanner, scanned_at, result

Each mapper extends the Controller Framework Mapper and defines an allowed-field whitelist. This is important when performing update/insert operations through the persistence layer.

14.1 TicketMapper

The TicketMapper provides ticket-specific persistence operations including token lookup and atomic ticket claiming.

findByToken(string $token): ?Ticket

claimTicket(int $ticketId): bool

The atomic claim operation must be implemented at database level so that concurrent scanners cannot both successfully change the same ticket from unused to used.

14.2 TicketScanMapper

The TicketScanMapper persists scan records associated with tickets.

TicketScan
    │
    └── ticket_id ───► Ticket
Important: Mapper::findAll() accepts a free SQL select clause. Client code must never concatenate untrusted request data into such a clause.

15. Command Layer

The framework commands are grouped by responsibility.

PackageCommands
adminCreate, edit, delete and composite-management operations for activities, cost items, members and payments; administration and member search.
downloadsMember and participant XLS exports.
mollieGeneric Mollie payment creation and webhook processing.
userActivity registration, payment creation, payment confirmation and public/user activity flows.

Ticket scanning may be exposed through client-specific commands or through framework commands depending on the client application's routing and scanner architecture.

Commands are intended to be connected to routes through the Controller Framework configuration, normally controls.xml.

16. Command Decorators

Client applications should use Controller Framework CommandDecorator whenever framework command logic can be reused.

Client route │ ▼ Client CommandDecorator │ ├── client-specific validation/configuration ├── authentication level └── Request parameters/objects │ ▼ MembersActivities framework command │ ▼ Domain model / mapper

This is especially important for validation strategies, payment descriptions and client-specific ticket or scanner behaviour.

17. Strategy and Type Implementations

17.1 Type implementations

ActivityTypeImplementation
CostitemTypeImplementation
PaymentTypeImplementation
SubscriptionTypeImplementation
TicketTypeImplementation
TicketScanTypeImplementation

The classification field determines the concrete implementation.

For example:

class Activity_RGLR
    extends \membersactivities\model\activities\ActivityTypeImplementation
{
    // default activity behaviour
}

class Activity_STMP
    extends \membersactivities\model\activities\ActivityTypeImplementation
{
    public function seatmap(): bool {
        return true;
    }
}

17.2 Ticket type

class Ticket_RGLR
    extends \membersactivities\model\subscriptions\TicketTypeImplementation
{
    // regular ticket behaviour
}

17.3 TicketScan type

class TicketScan_RGLR
    extends \membersactivities\model\subscriptions\TicketScanTypeImplementation
{
    // regular scanner behaviour
}

The client application should use type implementations when behaviour genuinely depends on classification rather than adding classification-specific branching throughout the generic framework.

18. Subscription Validation

SubscriptionValidationStrategy is the extension point for business rules determining whether a member may subscribe to a cost item.

public function subscribe(
    \controllerframework\members\Member $member,
    \membersactivities\model\activities\Costitem $subscribableitem,
    array $properties
): array

Client applications can implement different policies, for example:

SubscriptionValidationPublic
SubscriptionValidationUser
SubscriptionValidationAdmin

The concrete strategy should be supplied through controlled application code, normally a CommandDecorator. A request parameter must never be treated as a class name and instantiated dynamically.

19. Request / Response Flow

HTTP Request │ ▼ CommandResolver │ ▼ Command / Decorator │ ├── Request input validation ├── CSRF validation where required ├── business operation └── response preparation │ ▼ Command status │ ├── CMD_OK / CMD_DEFAULT ├── CMD_ERROR └── other configured statuses │ ▼ View or Forward

The Request object is also used as a controlled communication channel between decorators and wrapped commands. This mechanism is used, among other things, for application-specific validation strategies and Mollie order descriptions.

20. Security Architecture

20.1 Authentication

Authentication and login levels are supplied by the Controller Framework. Commands should explicitly define whether they require no login, a user login or administrator login.

20.2 CSRF

State-changing operations should use POST and validate the CSRF token before modifying data.

20.3 AccessToken

Controller Framework 1.0.31 supplies AccessToken. MembersActivities uses it for protected payment operations and version 1.1.0 also uses a dedicated token purpose for ticket scanning.

AccessToken::generate(
    'mollie-order',
    (string) $paymentId
);

AccessToken::generate(
    'ticket_scan',
    (string) $activityId
);

The _SALTRAND secret used by the AccessToken mechanism must remain confidential and must not be stored in public source code.

20.4 Ticket scanner security

The ticket scanning endpoint should validate the activity-specific ticket_scan token before processing ticket tokens.

Scanner │ ├── activity ID ├── ticket_scan AccessToken └── ticket token │ ▼ Server │ ├── validate AccessToken ├── find Ticket ├── verify activity ├── verify status └── claim atomically

20.5 Input validation

Request parameters are untrusted. Validate IDs, email addresses, file names, redirects, query parameters, ticket tokens and all other externally supplied values according to their intended type and context.

21. Payment and Mollie Flow

CreatePaymentCommand │ ▼ Payment │ │ protected order/payment identifier ▼ PaymentToMollieCommand │ ▼ OrderToMollieCommand │ ├── validate protected identifier/token ├── load Payment from server-side database ├── obtain Payment::amount ├── obtain application-specific orderDescription └── create Mollie payment │ ▼ Mollie │ │ │ payment │ webhook ▼ ▼ customer WebhookFromMollieCommand │ ▼ Payment::statusReceived() │ ▼ PaymentTypeImplementation

The critical trust boundary is the payment amount: the amount must be read from the server-side Payment object and not accepted from a browser-submitted amount.

The order description is intentionally different: the client-specific CommandDecorator can set orderDescription on the Request so that different order types can supply different descriptions without modifying the generic payment command.

22. Ticket Scanning Flow

Ticket scanner │ │ ticket token │ activity token ▼ Ticket scan endpoint │ ├── validate ticket_scan AccessToken │ ├── find Ticket by token │ ├── verify Ticket exists │ ├── verify Ticket belongs to requested activity │ ├── verify Ticket status │ └── atomic claim │ ┌──────┴─────────┐ │ │ ▼ ▼ accepted rejected │ │ └───────┬────────┘ ▼ TicketScan │ ▼ scan result

22.1 Correctness requirements

The scanner must not consider a ticket valid merely because the token exists.

The server should verify:

  1. the scanning AccessToken is valid;
  2. the Ticket exists;
  3. the Ticket belongs to the relevant activity;
  4. the Ticket has a valid status;
  5. the Ticket has not already been used;
  6. the atomic claim operation succeeds.

The final atomic claim is the authoritative concurrency protection.

23. Seat Reservation Consistency

Applications using seat maps must consider the difference between displayed availability and actual database state.

Seat map displayed │ ▼ User selects seat │ ▼ Other user may reserve same seat │ ▼ Current subscription submitted │ ▼ Server checks current reservations │ ├── available ───► continue │ └── conflict ────► reject / retry

The seat conflict check therefore belongs at the server-side subscription creation boundary rather than only in the JavaScript seat map.

Client-side seat maps improve usability, but they are not authoritative for reservation consistency.

24. Database Model

Table Primary purpose Important foreign keys
activity Activities and activity hierarchy parent_id → activity.id
member Members and member hierarchy parent_id → member.id
costitem Subscribable items/prices activity_id → activity.id
payment Financial transactions member_id → member.id
subscription Member registration for a cost item member_id, costitem_id, payment_id
ticket Individual persistent tickets subscription_id → subscription.id
ticket_scan Ticket scanning history ticket_id → ticket.id
remember_tokens Remember-me authentication tokens member-related authentication data
mail_queue Asynchronous email processing application process data

24.1 Relationship detail

activity 1 ─────────── * costitem activity 1 ─────────── * child activity member 1 ───────────── * subscription member 1 ───────────── * payment member 1 ───────────── * child member costitem 1 ─────────── * subscription payment 1 ──────────── * subscription subscription 1 ─────── * ticket ticket 1 ───────────── * ticket_scan

24.2 Ticket schema

ticket ├── id ├── description ├── classification ├── subscription_id ├── token ├── status ├── used ├── used_at └── created_at

24.3 Ticket scan schema

ticket_scan ├── id ├── description ├── classification ├── ticket_id ├── scanner ├── scanned_at └── result

24.4 Other 1.1.0 database changes

remember_tokens.expires_at is nullable.

The following mail_queue fields are nullable:

subject
body
recipient

The reference schema uses InnoDB, utf8mb4 and foreign key constraints. It is intended as a starting schema for a new client application rather than a production migration script.

25. Transactions and Consistency

Payment deletion is implemented transactionally: related subscriptions are deleted first, then the payment itself is deleted; an exception rolls the operation back.

25.1 Ticket claiming

Ticket claiming is a different form of consistency requirement. The important property is not a multi-statement business transaction but an atomic conditional state transition.

Conceptually:

UPDATE ticket
SET
    used = 1,
    used_at = CURRENT_TIMESTAMP
WHERE
    id = :ticket_id
    AND status = 'valid'
    AND used = 0;

The caller can determine whether the claim succeeded from the affected-row result.

This prevents two concurrent scanners from both receiving a successful claim for the same ticket.

25.2 Seat reservation consistency

Seat reservation validation must similarly be performed against current server-side database state immediately before the subscription is persisted.

For client-specific multi-step business operations, transactions should be considered whenever several related database changes must succeed or fail as one logical operation.

26. Mail Queue

The mail_queue table supports asynchronous mail processing.

A typical lifecycle is:

pending │ ▼ sending │ ├────────► sent │ └────────► failed

The queue records processing information such as attempts, creation/start/send timestamps and an error field.

Version 1.1.0 allows subject, body and recipient to be nullable, which permits the queue to represent records whose mail content is completed at a later processing stage.

A cron command can process pending messages and clean up old sent records.

27. Extension Guide

27.1 Add a new activity type

  1. Create a client Activity subclass if not already present.
  2. Create Activity_XXXX extending ActivityTypeImplementation.
  3. Store XXXX in the activity classification field.
  4. Add client logic only where the type needs special behaviour.

27.2 Add a new payment type

  1. Create a client Payment subclass.
  2. Create Payment_XXXX extending PaymentTypeImplementation.
  3. Implement statusReceived() when payment status changes require client-specific processing.

27.3 Add a new ticket type

  1. Create a client Ticket subclass if required.
  2. Create Ticket_XXXX extending TicketTypeImplementation.
  3. Store XXXX in the ticket classification field.
  4. Implement only ticket-specific behaviour.

27.4 Add a new TicketScan type

  1. Create a client TicketScan subclass if required.
  2. Create TicketScan_XXXX extending TicketScanTypeImplementation.
  3. Store XXXX in the scan classification field.
  4. Implement scanner-specific behaviour.

27.5 Implement a Member hierarchy

Member hierarchy support is based on the existing parent_id self-reference.

Parent Member
     │
     ├── Child Member
     └── Child Member

Client-specific code should treat the relationship as a domain hierarchy rather than introducing a separate "MemberGroup" object unless the application's business requirements genuinely require one.

27.6 Add client-specific subscription validation

  1. Create a class extending SubscriptionValidationStrategy.
  2. Implement the application-specific validation rules.
  3. Inject the strategy through a controlled CommandDecorator.

27.7 Add a new command around an existing command

  1. Create a client CommandDecorator.
  2. Set the wrapped framework command in initCommand().
  3. Use doExecuteDecorator() for client-specific preparation.
  4. Define the required authentication level.
  5. Register the route in controls.xml.

28. Migration from 1.0.31

Version 1.1.0 introduces new persistent domain entities and therefore requires more than a Composer version change for applications using the new functionality.

28.1 Composer

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

The Controller Framework remains at version 1.0.31.

28.2 Database migration

Applications should apply controlled database changes for:

28.3 Existing subscriptions

In version 1.0.31, multiple tickets could be represented by a subscription's quantity. Version 1.1.0 introduces individual Ticket records.

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

Applications using the ticket functionality should migrate existing relevant subscriptions to individual Ticket records.

The migration must generate unique ticket tokens and preserve any client-specific ticket information.

28.4 Google Wallet migration

Applications using Google Wallet should ensure that the required Ticket records exist before using the 1.1.0 Wallet implementation.

28.5 Ticket scanner migration

Applications introducing ticket scanning should implement:

Migration principle: framework upgrades and client data migrations should be treated as separate controlled steps. Do not replace an existing production database with the reference database schema.

29. API / Class Reference

Class Type Key API
membersactivities\model\activities\Activity abstract model getInstance, getParticipants, subscriptionPeriodOver, getTotalAmountReceived
membersactivities\model\activities\ActivityComposite composite model getChildren, isComposite, subscriptionPeriodOver
controllerframework\members\Member domain model / composite root parent-child member relationship
membersactivities\model\activities\Costitem abstract model getInstance
membersactivities\model\subscriptions\Subscription abstract model getInstance
membersactivities\model\subscriptions\Payment abstract model getInstance, delete, isPaid, statusReceived
membersactivities\model\subscriptions\Ticket domain model getInstance, findByToken, claim
membersactivities\model\subscriptions\TicketScan domain model getInstance, scan persistence
membersactivities\model\subscriptions\SubscriptionValidationStrategy strategy subscribe, errorcode
membersactivities\model\activities\ActivityMapper mapper getChildren, getParticipants
membersactivities\model\activities\CostitemMapper mapper persistence / allowed fields
membersactivities\model\subscriptions\SubscriptionMapper mapper persistence / allowed fields
membersactivities\model\subscriptions\PaymentMapper mapper persistence / allowed fields
membersactivities\model\subscriptions\TicketMapper mapper findByToken, claimTicket, persistence / allowed fields
membersactivities\model\subscriptions\TicketScanMapper mapper scan persistence / allowed fields
membersactivities\model\activities\ActivityTypeImplementation type implementation seatmap and activity-specific extension points
membersactivities\model\activities\CostitemTypeImplementation type implementation extension point
membersactivities\model\subscriptions\SubscriptionTypeImplementation type implementation extension point
membersactivities\model\subscriptions\PaymentTypeImplementation type implementation statusReceived, getSubscription, Wallet support
membersactivities\model\subscriptions\TicketTypeImplementation type implementation ticket-specific extension point
membersactivities\model\subscriptions\TicketScanTypeImplementation type implementation ticket-scan-specific extension point
membersactivities\model\wallet\GoogleWalletTicket integration service auth, create/update class/object, createJwt

30. Technical Review Checklist