The current site¶
Read the site your code runs as, run code as another site, and give Drush, batch and queue work the site it belongs to.
Which site?¶
| Question | Ask |
|---|---|
| Which site does this code run as? | current_site |
| Which site did the request arrive on? | current_site->negotiated() |
Which site does the route's {site} or the ?site_id query name? |
sites.pointed_site |
| Which sites hold an entity, and which is its canonical site? | sites.membership->sitesOf(), canonicalSiteOf() |
The request negotiates its site once, before routing; see Architecture and, for the matching rules, How a request finds its site. Sub-requests keep the site of the main request.
Read the current site¶
Inject current_site as Drupal\sites\SiteProxyInterface. It extends
SiteInterface and answers every method (id(), label(), isHub(),
getSetting(), getBaseUrl(), getCacheTags(), ...) as the active
site:
use Drupal\sites\SiteProxyInterface;
final class RegionBadge {
public function __construct(
private readonly SiteProxyInterface $currentSite,
) {}
public function build(): array {
return [
'#plain_text' => (string) $this->currentSite->getSetting('region_key'),
'#cache' => ['tags' => $this->currentSite->getCacheTags()],
];
}
}
Read through the proxy at the point of use. A site object you keep from
getSite() stays the same site after a switch.
Negotiated and active site¶
negotiated()is the site the request arrived on: the site its host and path prefix name, or the hub. Nothing changes it for the rest of the request. "Did this request arrive on the hub?" isnegotiated()->isHub().getSite(), and everySiteInterfacemethod on the proxy, is the active site: whose configuration, content and URLs apply. It is the negotiated site, except inside a switch. A preview switches for the whole request: it arrives on the hub and renders the previewed site.isReady()isFALSEuntil a site was negotiated: early bootstrap, a Drush command before its pre-command hook, a kernel test that sets no site. The proxy then answers as the hub. Guard code that must not act on a site nobody chose.
Never call setInitialSite() or setSite(): they are the negotiation's and
the switcher's. Tests set the site with SiteContextTrait; see
Testing and debugging.
Run code as another site¶
SiteSwitcherInterface::call() (service site_switcher) makes a site active,
runs the callable, switches back even when the callable throws, and returns
the callable's result. It takes a SiteInterface or a site ID; an unknown ID
is an error.
use Drupal\Core\Url;
$front = $this->siteSwitcher->call('acme', static fn (): string => Url::fromRoute('<front>')->setAbsolute()->toString());
// https://acme.example.com/
Inside the callable, the active site's configuration overrides, site cache
context, query scoping, entity access and URL host apply. negotiated(), the
request's theme and its languages stay as they were: the theme is negotiated
once per request, and the switcher negotiates every language type before it
leaves the request's site, so no language is first negotiated under the other
site. Switches nest, and
isSwitched() tells whether one is open. To link to another site without
switching, pass the site_id URL option:
Url::fromRoute('<front>', [], ['site_id' => 'acme']).
Drush, batch and queue¶
- Drush.
--urinegotiates like a request:drush --uri=https://acme.example.com php:eval "echo \Drupal::service('current_site')->id();"printsacme; without--urithe command runs on the hub. A path that is exactly a sub-site's prefix on that host runs the command in the sub-site (--uri=https://acme.example.com/sub). Any other path stays the base path of a Drupal installed in a subdirectory. A command that bootstraps below the full bootstrap negotiates no site. - Batch. Each batch request negotiates its site from the host the batch
was started on. An operation that works on another site takes the site as
an argument and switches with
call(), assites_clone'sSiteCloneBatch::batchProcess()does. - Queue and cron. Cron runs at the end of whatever request triggers it,
on the hub for
drush cronwithout--uri. Put the site ID into the queue item and switch in the worker:
public function processItem($data): void {
$this->siteSwitcher->call($data['site_id'], function () use ($data): void {
// The site's email address, from its configuration overrides.
$site_mail = $this->configFactory->get('system.site')->get('mail');
$this->mailManager->mail('my_module', 'digest', $site_mail, 'en', $data);
});
}
sites_simple_sitemap's QueueWorker::generateResultsFromElement() does
this for each sitemap. To work on every site, loop over
plugin.manager.site->getSites() and switch to each. Cron tasks and batch
operations load the canonical version of content, and a site's version inside
a switch; see Content versions.
The site cache context¶
The site cache context is the active site's ID and follows a switch. Sites
adds it to the renderer's required cache contexts, so every render cache
entry varies by site without declaring it. Declare it where you cache outside
the render system, for example on a cacheable JSON response, and key a cache
entry of your own by current_site->id(). A decision about the negotiated
site varies by sites_negotiated_site instead.
Tag output with the site's cache tags (site:{id}) to clear it when the site
changes. How to cache site-aware output: Caching; every context
and tag: Cache contexts and tags.