Hooks and events¶
Find the hook or event your module implements to react to sites, their content and their settings.
Services, plugins, hooks, events lists every hook and event with its module.
Hooks¶
Each module documents its hooks in its *.api.php file: sites.api.php,
modules/sites_content/sites_content.api.php and
modules/sites_ui/sites_ui.api.php in the Sites project, and the files of
Sites clone, Sites content overrides, Sites Metatag and Sites Pathauto.
Implement them as OOP hooks, a #[Hook] method on a class in src/Hook.
| To | Implement | Module |
|---|---|---|
| Let sites hold an entity type your module defines | hook_sites_content_entity_types_alter() |
sites |
| Show its entities that no site holds on every site | hook_sites_shared_pool_content_entity_types_alter() |
sites |
| Serve a route on a site, or answer 404 there | hook_site_access_alter(), hook_site_SITE_ID_access_alter() |
sites |
| Keep a setting from passing to sub-sites | hook_sites_inheritance_ignore_alter() |
sites |
| Delete your data of a site while the site is deleted | hook_sites_site_delete_operations_alter() |
sites |
| Mark sites in the list of an entity's sites | hook_sites_entity_site_badges_alter() |
sites |
| Scope a view built at runtime as a content overview | hook_sites_overview_views_alter() |
sites |
| Change discovered plugins | hook_site_info_alter(), hook_site_setting_info_alter(), hook_sites_launch_step_info_alter(), hook_site_admin_section_info_alter() |
sites |
| Report a problem on the Sites status report | hook_sites_status(), hook_sites_status_alter() |
sites_ui |
| Add to a site's dashboard what is no section card | hook_site_dashboard_alter() |
sites_ui |
| Keep entities your module holds back, like trashed ones, out of a site's content counts and reports | hook_sites_content_inventory_query_alter() |
sites_content |
The hooks of Sites content overrides are on Satellite APIs, the content entity type hooks on Site-aware entity types, and section cards on Extension points.
hook_site_info_alter() and the two content entity type hooks run while
Sites builds the site definitions and settings: do not load sites in them.
The site access hooks pass the result by reference. orIf() and andIf()
return a new object, so assign it:
#[Hook('site_access_alter')]
public function siteAccessAlter(AccessResultInterface &$access, AccountInterface $account, array &$context): void {
if ($context['route_match']->getRouteName() === 'my_module.status') {
$access = $access->orIf(AccessResult::allowed());
}
}
Delete data your module keeps per site in the delete batch, one request per
operation. The site entity is deleted after your operations run.
Keep hook_ENTITY_TYPE_predelete() for site as the fallback for a plain
$site->delete(); SitesContentOverridesSiteCleanupHooks in Sites content
overrides does both.
#[Hook('sites_site_delete_operations_alter')]
public function siteDeleteOperationsAlter(array &$operations, SiteInterface $site): void {
$operations[] = [[MySiteDataBatch::class, 'purge'], [$site->id()]];
}
Events¶
Subscribe with a service tagged event_subscriber.
| Event | Constant | Dispatched |
|---|---|---|
sites.initialize |
InitialSiteEvent::EVENT_NAME |
Once per main request and before each Drush command, to pick the site. |
sites.settings_build |
SiteSettingsEvent::BUILD |
Once per site instance, when its settings are resolved. |
sites.content.add, sites.content.remove |
ContentEvent::ADD_EVENT_NAME, ContentEvent::REMOVE_EVENT_NAME |
After an entity joins or leaves a site. |
sites.content.canonical |
ContentEvent::CANONICAL_EVENT_NAME |
After an entity gets its first or a new canonical site. |
InitialSiteEvent¶
The first subscriber that calls setInitialSite() wins; a request nobody
claims runs on the hub. Sites claims sub-sites by path prefix at priority 10
and sites by hostname at 0. Subscribe above 0 to take precedence over the
hostname, and return when a site is set. The event runs at kernel request
priority 266, before routing: there is no route match yet.
Pick the site from the hostname and the path only. The anonymous page cache answers before this event, and keys its entries by URL.
final class HostMapSubscriber implements EventSubscriberInterface {
public function __construct(
private readonly RequestStack $requestStack,
private readonly SitePluginManagerInterface $siteManager,
) {}
public static function getSubscribedEvents(): array {
return [InitialSiteEvent::EVENT_NAME => ['onInitialSite', 20]];
}
public function onInitialSite(InitialSiteEvent $event): void {
$host = $this->requestStack->getCurrentRequest()?->getHost();
$site_id = Settings::get('my_module_hosts', [])[$host] ?? NULL;
if ($event->getInitialSite() === NULL && $site_id !== NULL && ($site = $this->siteManager->getSite($site_id))) {
$event->setInitialSite($site);
}
}
}
For further hostnames of one site, its hostname aliases need no code; see Hostnames and sub-sites.
SiteSettingsEvent¶
The event runs after the stored, translated, environment and config-target
values are merged. Sites subscribes twice: main-site inheritance at priority 120 and
the settings.php values at 0. The last subscriber wins, so subscribe above
0 to leave settings.php the last word.
The event carries no cache metadata. Compute values from the site and from what changes only with a deployment: output that reads a setting carries the cache metadata of the site alone.
public static function getSubscribedEvents(): array {
return [SiteSettingsEvent::BUILD => ['onSettingsBuild', 10]];
}
public function onSettingsBuild(SiteSettingsEvent $event): void {
if (empty($event->getSetting('my_module_contact'))) {
$event->setSetting('my_module_contact', 'info@' . $event->getSetting('hostname'));
}
}
For fixed defaults, a setting plugin or the site type is the place.
ContentEvent¶
The membership service (sites.membership) dispatches it with the site and
the entity. sites.content.add follows a new membership, sites.content.remove
a deleted one. For sites.content.canonical, getSite() is the new
canonical site and getPreviousSite() the old one, NULL for the first.
Only memberships stored as relationship rows dispatch. Entity types that
carry their site on the entity, like a site_id field, dispatch nothing:
their save is the event. A site plugin whose dispatchContentEvents()
returns FALSE dispatches nothing either.
final class SitemapQueueSubscriber implements EventSubscriberInterface {
public function __construct(
private readonly QueueFactory $queueFactory,
) {}
public static function getSubscribedEvents(): array {
return [
ContentEvent::ADD_EVENT_NAME => 'onMembershipChange',
ContentEvent::REMOVE_EVENT_NAME => 'onMembershipChange',
];
}
public function onMembershipChange(ContentEvent $event): void {
$entity = $event->getEntity();
$this->queueFactory->get('my_module_sitemap')->createItem([
'site_id' => $event->getSite()->id(),
'entity_type_id' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
]);
}
}
services:
my_module.sitemap_queue_subscriber:
class: Drupal\my_module\EventSubscriber\SitemapQueueSubscriber
autowire: true
tags:
- { name: event_subscriber }
AliasCleanupOnRemovalSubscriber in Sites path alias and
AliasGenerationOnAddSubscriber in Sites Pathauto are real subscribers.