Architecture¶
Short URL is built on interface-based services that can be individually overridden via Drupal's service container.
Services¶
ShortUrlManager¶
Implements ShortUrlManagerInterface. Orchestrates the redirect
lifecycle for short URL nodes.
Key methods:
buildFullUrl(NodeInterface $node)— builds the full short URL through the outbound path processor pipelinesyncRedirects(NodeInterface $node)— deletes and recreates all redirects for the node (handles slug, destination, language, and publish status changes)deleteRedirects(NodeInterface $node)— removes all redirects associated with the noderesolveNodeBySlug(string $slug)— loads a short URL node by its slug valuegetUrlOptions(NodeInterface $node)— returns options array for URL generation (extensibility point for decorators)
SlugGenerator¶
Implements SlugGeneratorInterface. Generates slugs for the base36
and auto-increment modes.
Key methods:
generateBase36(int $length)— generates a random base36 slug usingrandom_bytes()generateAutoIncrement()— returns the number after the last one given to an auto-increment slug, which the slug auto-increment counter keeps in State. Only when State has lost the counter does it look up the highest numeric slug instead. The counter stays locked until the node save commits, so two short URLs saved at once never get the same number
VisitTracker¶
Implements VisitTrackerInterface. Computes statistics from the
shorturl_visits table. Visits are recorded by the middleware
below, through a ShortUrlVisitEvent that VisitDatabaseSubscriber
writes to the table.
Tagged as backend_overridable, which means it can be swapped
out for an external analytics backend (Matomo, Google Analytics,
custom database) via service override.
Key methods:
trackVisit(Redirect $redirect, Request $request)— dispatches aShortUrlVisitEventfor a matched redirectgetStats(int $nid)— returns the full statistics arraysupportsStats()— indicates whether inline statistics are available (returnsFALSEin overrides that delegate to external systems)
QrCodeGenerator¶
Implements QrCodeGeneratorInterface. Generates QR code images
using endroid/qr-code.
Key methods:
generateSvgDataUri(string $url)— returns a base64-encoded SVG data URI for inline embeddinggeneratePngResponse(string $url)— returns a Symfony Response with PNG contentgenerateSvgResponse(string $url)— returns a Symfony Response with SVG content
ShortUrlVisitMiddleware¶
HTTP middleware at priority 210 (above PageCache at 200). Intercepts
redirect responses bearing the X-Shorturl-Nid header and, once the
response is sent, dispatches a ShortUrlVisitEvent, which
VisitDatabaseSubscriber writes as a visit row (a failed write is
logged, never breaking the redirect). Resolves country codes via
Smart IP when available.
The visit is recorded from the middleware's destruct(): Drupal
destructs the services a request used after sending the response
(after fastcgi_finish_request() on PHP-FPM), for page cache hits
too, where kernel.terminate is not dispatched. A subscriber to
ShortUrlVisitEvent therefore runs after the response is sent; one
that queues work should send it on destruct too (the middleware's
needs_destruction tag has priority 100, so it runs first).
ShortUrlQrPathProcessor¶
Inbound path processor at priority 200. Rewrites /{slug}.qr
paths to /api/shorturl/qr/{slug} so that appending .qr to any
short URL serves its QR code.
URL generation¶
Short URLs are built through Drupal's outbound path processor
pipeline via ShortUrlBuilderTrait. This trait:
- Calls
PathProcessorManager::processOutbound()with the slug as the path (e.g./promo). - Applies any prefix from
$options['prefix'](e.g. a language prefix like/fr/promo). - Uses
$options['base_url']if set by a path processor, otherwise falls back to the current request's scheme and host.
This approach ensures short URLs include the correct language prefix and hostname, and allows decorator modules to inject additional context into the options array.
Hook implementations¶
Hook implementations are organized into dedicated classes. A class is built the first time any of its hooks runs, so the hooks that run for every node or every entity sit in classes that build no Short URL service:
| Class | Hooks |
|---|---|
ShortUrlEntityHooks |
node_presave, node_insert, node_update, node_delete (its services are closures, built on the first short URL) |
ShortUrlFieldHooks |
entity_base_field_info, entity_bundle_field_info_alter |
ShortUrlRedirectHooks |
redirect_response_alter |
ShortUrlOperationHooks |
entity_operation |
ShortUrlCronHooks |
cron |
ShortUrlFormHooks |
form_node_shorturl_form_alter, form_node_shorturl_edit_form_alter |
ShortUrlThemeHooks |
theme |
ShortUrlViewsHooks |
views_data, views_data_alter |
Database schema¶
shorturl_visits table¶
| Column | Type | Description |
|---|---|---|
id |
serial | Primary key |
nid |
int | Short URL node ID |
langcode |
varchar(12) | Translation language code |
timestamp |
int | Unix timestamp |
referrer |
varchar(2048) | HTTP Referer header |
country_code |
varchar(2) | ISO 3166-1 alpha-2 country code |
ip_hash |
varchar(64) | HMAC-SHA256 of visitor IP, keyed with the hash salt |
user_agent |
varchar(512) | Visitor User-Agent header |
visit_date |
varchar(10) | Day of the visit (Y-m-d) in the site time zone when it was recorded |
Indexes: nid, nid + langcode, nid + visit_date, timestamp
Redirect entity additions¶
A shorturl_nid base field is added to the redirect entity type
to link redirects back to their source short URL node. This enables
efficient cleanup on node delete and status synchronization on
publish/unpublish.
Extending the module¶
Replacing the visit tracker¶
To delegate visit tracking to an external system:
# my_module.services.yml
services:
Drupal\shorturl\VisitTrackerInterface:
class: Drupal\my_module\ExternalVisitTracker
Your implementation should return FALSE from supportsStats()
if statistics are handled externally. The Statistics tab will be
hidden automatically.
Customizing slug generation¶
Override SlugGeneratorInterface to add custom slug algorithms:
services:
Drupal\shorturl\SlugGeneratorInterface:
class: Drupal\my_module\MySlugGenerator
Customizing QR codes¶
Override QrCodeGeneratorInterface to change QR code appearance,
size, error correction, or use a different library:
services:
Drupal\shorturl\QrCodeGeneratorInterface:
class: Drupal\my_module\MyQrCodeGenerator