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"]
- Definitions.
plugin.manager.sitediscovers#[Site]plugins and adds one definition persiteentity inhook_site_info_alter(). It indexes each site's path prefix, and its hostname and aliases in every environment. See Site plugins and site types. - Negotiation. On the main request,
SitesKernelRequestSubscriberdispatchesInitialSiteEvent(sites.initialize) atkernel.requestpriority 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 IDdefault). Drush runs the same negotiation for--uriin a pre-command hook. - Current site. The subscriber seals the result on
current_site. Every reader ofcurrent_sitegets that site, or the site aSiteSwitcherInterface::call()switched to. See The current site. - 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) andsettings.phpvalues (priority 0) apply. See Setting plugins. - Site gate. At priority 31, after routing, the active site's
access()decides whether it serves the route. A refusal answers 404. - Config overrides.
SiteConfigOverrider(aconfig.factory.overrideat priority -150) layers the active site's configuration: what its setting plugins derive, the override rows the site stored, andsettings.phpvalues on top. See Config overrides. - Access policy.
SitesAccessPolicyadds 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. - Membership.
sites.membershipanswers which sites an entity belongs to and which one is canonical, from the storages taggedsites.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_idquery yourself;fromRoute(),fromQuery()andforAdministration()resolve them, the hub and unknown IDs toNULL. - A pointer is no context.
sites.pointed_sitenames a site; configuration, isolation and theming followcurrent_site. To act as the named site, switch withcall().