Architecture overview
The CRM Membership module is built on a plugin-based architecture for membership lifecycle management. The main building blocks are entities, the Membership Term plugin system, the MembershipService, events, and background processing via cron and a queue worker.
High-level diagram
flowchart TB
subgraph entities [Entities]
Membership[Membership]
MembershipType[MembershipType]
MembershipPeriod[MembershipPeriod]
end
subgraph plugins [Plugin system]
TermManager[MembershipTermManager]
TermPlugin[MembershipTerm plugins]
end
subgraph api [Services and events]
MembershipService[MembershipService]
Events[IS_MEMBER event]
end
subgraph background [Background processing]
Cron[hook_cron]
Queue[MembershipExpiration queue]
end
MembershipType --> Membership
Membership --> MembershipPeriod
Membership --> TermManager
TermManager --> TermPlugin
MembershipService --> Membership
MembershipService --> Events
Cron --> Queue
Queue --> Membership
Components
| Component | Description | See |
|---|---|---|
| Entities | Membership (content), MembershipType (config bundle), MembershipPeriod (content). Define the data model and CRUD. | Entities |
| Plugin system | Membership Term plugins determine how membership periods are calculated (fixed, rolling, lifetime) and how activation, renewal, and expiration behave. | Plugin system |
| MembershipService | Central API: check if a contact is a member of a target, and load memberships for a contact or target. Uses term plugins and dispatches the IS_MEMBER event. | MembershipService |
| Events | MembershipEvents::IS_MEMBER allows other modules to override or extend “is this contact a member of this target?” logic. |
Events |
| Cron and queue | Cron finds memberships that need expiration (no current periods or only expired periods) and enqueues them; the queue worker applies the term plugin’s expire logic. | Cron and queue |
Data flow (membership check)
- Code calls
MembershipService::isMember(Contact $contact, Contact $target). - The service queries active memberships linking the contact to the target and, for each, delegates to the membership’s term plugin to see if it is active for that contact (including grace periods).
- If no direct membership is found, the service also checks indirect memberships for the target (e.g. household memberships) and again uses the term plugin to see if the contact is active in any current period.
- If still not a member, the service dispatches the IS_MEMBER event; subscribers can call
markAsMember()to mark the contact as a member. - The method returns whether the contact is considered a member by any of the above.
Next steps
- Entities — Membership, MembershipType, MembershipPeriod fields and links.
- Plugin system — Built-in term plugins and how to implement custom ones.
- Services & API — Using
MembershipServiceand events in code.