Skip to content

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 pipeline
  • syncRedirects(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 node
  • resolveNodeBySlug(string $slug) — loads a short URL node by its slug value
  • getUrlOptions(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 using random_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 a ShortUrlVisitEvent for a matched redirect
  • getStats(int $nid) — returns the full statistics array
  • supportsStats() — indicates whether inline statistics are available (returns FALSE in 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 embedding
  • generatePngResponse(string $url) — returns a Symfony Response with PNG content
  • generateSvgResponse(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:

  1. Calls PathProcessorManager::processOutbound() with the slug as the path (e.g. /promo).
  2. Applies any prefix from $options['prefix'] (e.g. a language prefix like /fr/promo).
  3. 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