Skip to content

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 ( future memberships are excluded )
  • current_periods is 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:

  1. Load all memberships with status <> expired (includes both active and future) that have at least one current period with date_range.end_value >= now.
  2. Load all memberships with status <> expired that have current periods with date_range.end_value < now.
  3. Take array_diff of (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 compares end_value against the current time; behavior for null end dates is undefined.
  • isExpiredFor() on term plugins is not implemented (returns FALSE in 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:

  1. Reads crm_membership_id from the item data.
  2. Loads the crm_membership entity.
  3. Gets the membership’s Membership Term plugin via $membership->getMembershipTerm().
  4. 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.

  • Status and periods — How status and current_periods relate to expiration
  • Roadmap — Cron tests, automatic renewal, lifetime period behavior
  • Testing — Test coverage gaps