Cron and queue
Membership expiration is handled in two steps: hook_cron finds memberships that need to be expired and enqueues them; a queue worker processes each item by calling the membership’s term plugin’s expire() method.
hook_cron
Implementation: Drupal\crm_membership\Hook\CronHooks::cron()
File: src/Hook/CronHooks.php
On each cron run, the hook runs two separate entity queries and merges the results:
Query 1 — Active memberships with no current periods
Finds memberships where:
status = active(futurememberships are excluded )current_periodsis empty (IS NULL)
These are treated as needing expiration (e.g. they were active but something left them without a valid period).
Query 2 — Non-expired memberships whose current periods have all ended
Uses a two-step diff:
- Load all memberships with
status <> expired(includes bothactiveandfuture) that have at least one current period withdate_range.end_value >= now. - Load all memberships with
status <> expiredthat have current periods withdate_range.end_value < now. - Take
array_diffof (2) minus (1) — memberships whose only current periods are in the past.
If at least one current period is still active (end date in the future), the membership is skipped.
Enqueue
For each membership ID from queries 1 and 2, creates one item on the queue crm_membership_expiration with payload ['crm_membership_id' => $membership_id].
The hook does not change any membership or period itself; it only adds items to the queue. Date comparison uses the storage timezone and datetime format from DateTimeItemInterface.
Known limitations
- Lifetime / open-ended periods may have no
date_range.end_value. Cron comparesend_valueagainst the current time; behavior for null end dates is undefined. isExpiredFor()on term plugins is not implemented (returnsFALSEin the base class). Cron does not call it; it relies on period end dates only.- No automated tests cover cron expiration logic yet. See Roadmap.
Queue worker
Queue ID: crm_membership_expiration
Class: Drupal\crm_membership\Plugin\QueueWorker\MembershipExpiration
File: src/Plugin/QueueWorker/MembershipExpiration.php
Cron time: 60 seconds (so cron will process this queue for up to 60 seconds per run).
For each queue item:
- Reads
crm_membership_idfrom the item data. - Loads the
crm_membershipentity. - Gets the membership’s Membership Term plugin via
$membership->getMembershipTerm(). - Calls
$membershipTerm->expire().
The term plugin’s expire() is responsible for updating the membership (e.g. set status to expired, remove or adjust current_periods, and save). The queue worker does not perform any other logic.
Summary
| Step | Component | Action |
|---|---|---|
| 1 | CronHooks::cron() |
Find active memberships without periods, or non-expired memberships with only expired periods; enqueue each by ID. |
| 2 | MembershipExpiration::processItem() |
Load membership, get term plugin, call expire(). |
Ensure cron is running (e.g. via system cron or Drupal’s cron) so that expirations are processed regularly. The 60-second cron time limit means only a subset of queued items may be processed per run if there are many expirations.
Related
- Status and periods — How
statusandcurrent_periodsrelate to expiration - Roadmap — Cron tests, automatic renewal, lifetime period behavior
- Testing — Test coverage gaps