Skip to content

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)

  1. Code calls MembershipService::isMember(Contact $contact, Contact $target).
  2. 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).
  3. 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.
  4. If still not a member, the service dispatches the IS_MEMBER event; subscribers can call markAsMember() to mark the contact as a member.
  5. 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 MembershipService and events in code.