Skip to content

Architecture

How a request becomes a site, which part of Sites answers which question, and which service to inject for it.

The terms hub, site, site type, settings and canonical site are defined on Concepts.

How a request becomes a site

flowchart TD
  defs["plugin.manager.site<br/>#[Site] plugins + site entities"] --> index["Resolution index<br/>hostnames, aliases, path prefixes"]
  req(["Request<br/>acme.example.com/jobs/about"]) --> event
  index --> event["InitialSiteEvent<br/>path prefix (10), hostname (0)"]
  event --> proxy["current_site<br/>negotiated site = active site"]
  proxy --> settings["Settings build<br/>SiteSettingsEvent::BUILD"]
  proxy --> gate["Site gate<br/>access(): 404 when not served"]
  proxy --> config["SiteConfigOverrider<br/>configuration per site"]
  proxy --> policy["SitesAccessPolicy<br/>per-site roles"]
  proxy --> content["sites.entity_access, query alters<br/>read sites.membership"]
  1. Definitions. plugin.manager.site discovers #[Site] plugins and adds one definition per site entity in hook_site_info_alter(). It indexes each site's path prefix, and its hostname and aliases in every environment. See Site plugins and site types.
  2. Negotiation. On the main request, SitesKernelRequestSubscriber dispatches InitialSiteEvent (sites.initialize) at kernel.request priority 266, before routing. The Sites path prefix subscriber (priority 10) claims a sub-site, InitialSiteSubscriberByHostname (priority 0) a main site; the first site set wins. When no site claims the host, the request runs on the hub (site ID default). Drush runs the same negotiation for --uri in a pre-command hook.
  3. Current site. The subscriber seals the result on current_site. Every reader of current_site gets that site, or the site a SiteSwitcherInterface::call() switched to. See The current site.
  4. Settings build. A site resolves its settings once, on first read: its stored values over type and plugin defaults, the current translation and environment, its configuration-backed values, then SiteSettingsEvent::BUILD, where main-site inheritance (priority 120) and settings.php values (priority 0) apply. See Setting plugins.
  5. Site gate. At priority 31, after routing, the active site's access() decides whether it serves the route. A refusal answers 404.
  6. Config overrides. SiteConfigOverrider (a config.factory.override at priority -150) layers the active site's configuration: what its setting plugins derive, the override rows the site stored, and settings.php values on top. See Config overrides.
  7. Access policy. SitesAccessPolicy adds the roles an account holds on the active site to its permissions. On the hub it adds the roles on the site the request points at. See Access.
  8. Membership. sites.membership answers which sites an entity belongs to and which one is canonical, from the storages tagged sites.membership_storage. Entity access and the query alters read it: on a site's host, content of other sites answers 404 and drops out of lists. See Site-aware entity types and Queries and Views.

site, the active site's ID, is a required render cache context, and the config factory caches overridden configuration per site. See Caching.

Service map

Type-hint the interface: each one is aliased to its service. A class marked @internal is no API, even where its service is.

To Service Interface
read the active or the negotiated site current_site Drupal\sites\SiteProxyInterface
run code as another site site_switcher Drupal\sites\SiteSwitcherInterface
load or list sites, find sub-sites plugin.manager.site Drupal\sites\Plugin\SitePluginManagerInterface
read the site a route's {site} or ?site_id names sites.pointed_site Drupal\sites\Service\PointedSiteInterface
read or write the sites of an entity sites.membership Drupal\sites\Membership\SiteMembershipInterface
put content on a site on behalf of an account sites.content_assignment Drupal\sites\Membership\SiteContentAssignmentInterface
ask whether a type or bundle is site content sites.content_support Drupal\sites\Service\SiteContentSupportInterface
ask whether an account holds a permission on a site sites.capabilities Drupal\sites\Section\SiteCapabilitiesInterface
get the sites' verdict on an entity operation sites.entity_access Drupal\sites\Access\SiteEntityAccessInterface
read or change settings outside a section form sites.site_settings Drupal\sites\SiteSetting\SiteSettingsInterface
create, enable, arrange or delete a site sites.lifecycle Drupal\sites\Lifecycle\SiteLifecycleInterface
list a site's people, hand out its roles sites.members Drupal\sites\Membership\SiteMembersInterface
list the sites an account may work on sites.user_access Drupal\sites\Service\UserSiteAccessInterface

Every service, plugin type, hook and event is listed in Services, plugins, hooks, events.

Rules for your code

  • One owner per decision. Each service above holds the rules of its concept; the forms, Drush commands and agent tools of the suite call it. A surface you add calls the same methods: the …Access() method, the …Errors() method, then the action.
  • Read pointers through sites.pointed_site. Never read the route's {site} or the ?site_id query yourself; fromRoute(), fromQuery() and forAdministration() resolve them, the hub and unknown IDs to NULL.
  • A pointer is no context. sites.pointed_site names a site; configuration, isolation and theming follow current_site. To act as the named site, switch with call().