Change record status: 
Introduced in branch: 
1.0.x
Introduced in version: 
1.0.0-beta7
Description: 

The pre-1.0 cleanup (#3616937: Cleanup omnibus: dead config keys, missing token dependency, per-role user saves, page_attachments overhead, drush alias, field length, translatability, misc) touches configuration, two entity fields, the module's Drupal 10 compatibility layer and the Drush commands. Run drush updb after updating: two update hooks ship.

Settings removed

site_id, api_key and log_webhooks are removed from subscription_manager.settings (update 10026 clears them). Nothing in Subscription Manager or in any connector read them; each connector has its own webhook-logging setting. custom_subscribe_url is kept: Stripe Subscription and Shopify Subscription read it from here to redirect would-be subscribers to a site-provided page. Its description on the settings form now says so. The token browser next to it appears only when the Token module is installed; token replacement itself does not need that module.

Name fields widened

The name base field on both the subscription and the subscription plan entity grows from 50 to 255 characters (update 10027 alters the columns in place; existing data is unaffected). Plan names come from the remote service and could exceed 50 characters before.

The plan's show boolean field now defaults to FALSE rather than an empty string, so a freshly created plan passes validation without the field being set explicitly. No storage change.

Drupal 10 compatibility layer removed

Following the ^11.3 requirement from #3616936: SubscribeMenuLink declares the user.roles cache context but varies per user, leaking Join/Upgrade labels across users, the #[LegacyHook] procedural stubs are removed from subscription_manager.module, and composer.json now requires drupal/core ^11.3. Hooks are the #[Hook] classes under src/Hook only. The two form submit callbacks (subscription_manager_user_login_submit(), subscription_manager_user_register_form_submit()) and the two reconcilers used by update hooks remain procedural.

The module's classes are autowired: the queue workers, the constraint validator, the controller, the settings form and the hooks service no longer define create() factories. subscription_manager.services.yml declares class-name aliases for the module's services so they can be autowired by type from any module:

Drupal\subscription_manager\SubscriptionManagerService
Drupal\subscription_manager\SubscriptionManagerConnectorManager
Drupal\subscription_manager\CycleEngine
Drupal\subscription_manager\DunningManager
Drupal\subscription_manager\Service\RemoteCancellation
Drupal\subscription_manager\Service\PostPurchaseTokenService
Drupal\subscription_manager\Service\SubscriptionRefreshThrottle
Drupal\subscription_manager\Service\DuplicateSubscriptionResolver
Drupal\subscription_manager\Service\SubscriptionReconciler

Connector plugins can do the same: declare a constructor and let PluginBase::create() wire it. Two things to know: a service without an interface alias (such as the module's logger channel) is named with #[Autowire(service: '…')], and autowired plugin properties must not be readonly, because DependencySerializationTrait cannot restore them.

Attributes everywhere

The SubscriptionEntity and SubscriptionPlanEntity entity types and the two Views field plugins are now declared with PHP attributes. Connector plugins may still use the @SubscriptionManagerConnector annotation (the module tests that it works), but the attribute is recommended.

Drush command logic as services

The logic behind subscription_manager:cleanup-duplicates, subscription_manager:find-duplicates and subscription_manager:reconcile lives in two new services the commands only format:

  • subscription_manager.duplicate_resolver (DuplicateSubscriptionResolver): cleanupInactiveDuplicates(?int $uid, bool $dry_run), findDuplicates(?int $uid, bool $include_same_connector), resolveDuplicates(array $duplicates, ?string $priority, bool $include_same_connector, bool $dry_run). Results are arrays of counts and rows; actions are the class's ACTION_* constants. Cancelling goes through RemoteCancellation (#3616934: Deleting a user deletes local subscription entities but never cancels the remote contract, so billing continues) and deactivates the local subscription only on success.
  • subscription_manager.reconciler (SubscriptionReconciler): connectorIds(?string $connector_id) and reconcileConnector(string $connector_id, string $status_filter, bool $dry_run, ?callable $tick), returning per-connector counts and status changes.

The SubscriptionManagerCommands constructor changed accordingly (it takes the two services instead of the remote cancellation service); sites that subclass or re-register the command class must update. Command names, options and output are unchanged, except that sync-plans lost its leftover foo alias in favour of sm-sync-plans.

Smaller changes

  • Role changes from a subscription save the user account once, not once per role.
  • The Views field plugins Remote status and Will renew render translated labels with CSS classes (subscription-manager-status--good|bad|warning|muted, from the subscription_manager/status library) instead of inline colours; themes overriding the old inline styles should target the classes.
  • The routes subscription_manager.manage_subscription and subscription_manager.billing_history have leading slashes in their path definitions; the paths themselves are unchanged.
Impacts: 
Site builders, administrators, editors
Module developers