SlideShare a Scribd company logo
Drupalcon 2023 - How Drupal builds your pages.pdf
lussoluca
How Drupal builds your pages
D10 edition
Luca Lusso
Drupal / PHP / Go developer @ SparkFabrik
Drupal contributor (WebProfiler, Monolog,
Symfony Messenger, …) and speaker
Drupal.org: www.drupal.org/u/lussoluca
Twitter: www.twitter.com/lussoluca
LinkedIn: www.linkedin.com/in/lussoluca
drupal.slack.com: lussoluca
@lussoluca
We are a tech company of engineers,
developers and designers, capable of
accompanying customers step by step
in the CLOUD NATIVE era,
with an agile and pragmatic approach.
We are committed to OPEN SOURCE
and we embrace its philosophy
in our internal practices.
TECHNOLOGY,
STRATEGY AND METHOD
4
5
All Drupal pages are built in the same way:
A Request is received and a Response is
produced
In this session we’ll see how Drupal turns an
HTTP Request into an HTML Response
6
GET /en/forecast/lille HTTP/2 <!DOCTYPE html
>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<meta
name
="Generator"
content
="Drupal 10 (https://www.drupal.org)
" />
...
</html>
7
➔ Custom module (machine name: weather)
➔ Custom page
➔ Not build using the Node system (even if the flow is
the same, the Node system is too complex to be
analyzed in a short time)
➔ There is a custom Entity called “City” used to store a
description and some geographic coordinates
➔ On this page we retrieve the city, call an external
service to get weather forecasts and build the
output
➔ Demo module repository:
https://github.com/lussoluca/weather
/en/forecast/lille
8
9
In the context of computer programming, instrumentation
refers to the measure of a product's performance, in order
to diagnose errors and to write trace information.
➔ Profiling
➔ Tracing
➔ Logging
Instrumentation
10
➔ New settings page available from Drupal 10.1
➔ /admin/config/development/settings
➔ Useful during inspection and development
➔
Disable optimizations
Profiling: WebProfiler
11
WebProfiler adds a toolbar at the bottom of every page and
shows you all sorts of stats, such as the amount of
database queries loaded on the page, which services are
used, and much more.
Depends on:
➔ Devel
➔ Tracer
https://www.drupal.org/project/webprofiler/
12
index.php
13
➔ Require the Composer
autoload file
➔ Create a new Kernel
➔ Handle the request to build a
response
➔ Return the response
➔ Terminate the Kernel
➔ Front controller pattern
use DrupalCoreDrupalKernel;
use SymfonyComponentHttpFoundationRequest;
$autoloader = require_once 'autoload.php';
$kernel = new DrupalKernel('prod', $autoloader);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
➔ https://stackphp.com/
➔ DrupalKernel is decorated by a set of middlewares
Stack middleware
14
DrupalKernel
➔ Ordered by priority desc on request and by priority
asc on response
Stack middleware
15
16
After passing through all the middlewares, the Request is
handled by a router that finds the code responsible for
converting the Request into a Response.
In this case the Request is managed by a Controller:
ForecastController.
Request collector
17
Routing and controllers
➔ weather.routing.yml
➔ Route name: weather.forecast
➔ {city} placeholder
➔ _controller
➔ _title
➔ Requirements
➔ Param converters
Routing
weather.forecast:
path: '/forecast/{city}'
defaults:
_controller: 'DrupalweatherControllerForecastController::page'
_title_callback: 'DrupalweatherControllerForecastController::title'
requirements:
_permission: 'access content'
options:
parameters:
city:
type: 'weather:city'
18
Routes collector
19
Routes collector
Param converters
weather.forecast:
path: '/forecast/{city}'
defaults:
_controller: 'DrupalweatherControllerForecastController::page'
_title_callback: 'DrupalweatherControllerForecastController::title'
requirements:
_permission: 'access content'
options:
parameters:
city:
type: 'weather:city'
The Node module uses the same
technique to upcast the nid to full
Node objects.
20
Param converters
public function applies( $definition , $name, Route $route):bool {
if (!empty($definition ['type']) && $definition ['type'] === 'weather:city' ) {
return TRUE;
}
return FALSE;
}
public function convert( $value, $definition , $name, array $defaults ):?CityInterface
{
$cities = $this
-> entityTypeManager
->getStorage( 'city')
->loadByProperties([ 'label' => $value]);
if (count( $cities) == 0) {
return NULL;
}
return reset($cities);
}
➔ applies is used to check if this
param converter should be
used for this route
➔ convert will takes the value
from the url (lille) and convert
it to something more
structured (typically an
object). If convert return
NULL, Drupal will return a 404
error page.
21
Services collector
22
Services collector
Services collector
23
Database collector
Title callback
weather.forecast:
path: '/forecast/{city}'
defaults:
_controller: 'DrupalweatherControllerForecastController::page'
_title_callback: 'DrupalweatherControllerForecastController::title'
requirements:
_permission: 'access content'
options:
parameters:
city:
type: 'weather:city'
Method used by Drupal to retrieve
the title of a route
24
Title callback
public function title(CityInterface $city): string {
return $this->t('Weather forecast for @city', [
'@city' => $city->label(),
]);
}
➔ Returns a string
➔ Can use the parameters
(upcasted) from the URL
25
Controller callback
weather.forecast:
path: '/forecast/{city}'
defaults:
_controller: 'DrupalweatherControllerForecastController::page'
_title_callback: 'DrupalweatherControllerForecastController::title'
requirements:
_permission: 'access content'
options:
parameters:
city:
type: 'weather:city'
Method used by Drupal to retrieve
the main content of a route
26
Controller callback
public function page(CityInterface $city): array {
$forecast = $this->weatherClient ->getForecastData( $city->label());
$build['content' ] = [
'#theme' => 'weather_forecast' ,
'#forecast' => $forecast ,
'#units' => $this->config( 'weather.settings' )->get('units'),
'#description' => $city->getDescription(),
'#coordinates' => $city->getCoordinates(),
'#cache' => [
'max-age' => 10800, // 3 hours.
'tags' => [
'forecast:' . strtolower( $city->label()),
],
'contexts' => [
'url',
],
],
];
return $build;
}
27
Services
public function page(CityInterface $city): array {
$forecast = $this->weatherClient ->getForecastData( $city->label());
$build['content'] = [
'#theme' => 'weather_forecast',
'#forecast' => $forecast,
'#units' => $this->config('weather.settings')->get('units'),
'#description' => $city->getDescription(),
'#coordinates' => $city->getCoordinates(),
'#cache' => [
'max-age' => 10800, // 3 hours.
'tags' => [
'forecast:' . strtolower($city->label()),
],
'contexts' => [
'url',
],
],
];
return $build;
}
➔ PHP classes to perform some
useful task
◆ Sending email
◆ Authenticate users
◆ Call external services
◆ …
➔ Drupal core has more than 500
services
➔ We’ve seen a couple of them
already:
◆ Middlewares are
services
◆ Param converters are
services
28
Services collector
29
Services collector
30
Http collector
31
➔ Well done! We found that the ForecastController has
the piece of code that render this route!
➔ But wait…
➔ ForecastController returns the title of the page and a
simple array…
◆ Where all this markup comes from?
◆ Where are loaded and placed the blocks (like
the header and the footer)?
◆ How CSS and JavaScript assets are managed?
Entering the Drupal render pipeline
/en/forecast/lille
32
Render pipeline
Render arrays
public function page(CityInterface $city): array {
$forecast = $this->weatherClient->getForecastData($city->label());
$build['content' ] = [
'#theme' => 'weather_forecast' ,
'#forecast' => $forecast ,
'#units' => $this->config( 'weather.settings' )->get('units'),
'#description' => $city->getDescription(),
'#coordinates' => $city->getCoordinates(),
'#cache' => [
'max-age' => 10800, // 3 hours.
'tags' => [
'forecast:' . strtolower( $city->label()),
],
'contexts' => [
'url',
],
],
];
return $build;
}
➔ weather_forecast: theme hook
➔ Keys that starts with ‘#’ are the
properties
➔ Keys that doesn’t start with ‘#’
are nested render arrays
➔ Must declare ‘#cache’
information
33
➔ Where I can find the whole list of
available theme hooks?
➔ Which arguments are available?
Theme registry
"weather_details" => array:6 [ ▶]
"weather_forecast" => array:6 [▼
"variables" => array:4 [▼
"forecast" => []
"units" => "metric"
"description" => ""
"coordinates" => []
]
"type" => "module"
"theme path" => "modules/custom/weather"
"template" => "weather-forecast"
"path" => "modules/custom/weather/templates"
"preprocess functions" => array:2 [▼
0 => "template_preprocess"
1 => "contextual_preprocess"
]
]
"weather_forecast_single" => array:6 [ ▶]
➔ Provided by Devel module (on
/devel/theme/registry)
➔ For each theme hook
◆ List of variables (with
default values)
◆ Path on the filesystem
◆ Template
◆ Proprocess functions
34
Defining a theme hook
function weather_theme(): array {
return [
'weather_forecast' => [
'variables' => [
'forecast' => [],
'units' => 'metric',
'description' => '',
'coordinates' => [],
],
],
];
}
➔ weather.module
➔ List of theme hooks defined by
a module, with a machine
name and a list of accepted
variables (and their default
values)
35
weather-forecast.html.twig
{{ attach_library('
weather/map
') }}
{{ attach_library('
weather/base
') }}
<div class="forecast-list
">
{% for item in forecast.list %}
<div class="forecast-element
">
<h3 class="forecast-element__date
">{{ item.dt_txt|date("
d/m/Y H:i") }}</h3>
<p class="forecast-element__temp
">{{ item.main.temp }} °{{ units == '
metric' ? 'C' : 'F' }}</p>
<p class="forecast-element__description
">{{ item.weather[
0].description|title }}</
p>
<a href="{{ path('weather.details
', {city:forecast.city.name, date:item.dt_txt}) }}" class="use-ajax"
data-dialog-type
="dialog"
data-dialog-options
='{"width":700,"title":"{{ 'Details'|t }}"}'>More info</
a>
</div>
{% endfor %}
</div>
<div class="city-wrapper
">
<div class="city-description
">
{{ description }}
</div>
<div class="city-map">
<div id="map" data-lat="{{ coordinates
.0 }}" data-lng="{{ coordinates
.1 }}"></div>
</div>
</div>
36
Controller output
public function page(CityInterface $city): array {
$forecast = $this->weatherClient->getForecastData($city->label());
$build['content'] = [
'#theme' => 'weather_forecast',
'#forecast' => $forecast,
'#units' => $this->config('weather.settings')->get('units'),
'#description' => $city->getDescription(),
'#coordinates' => $city->getCoordinates(),
'#cache' => [
'max-age' => 10800, // 3 hours.
'tags' => [
'forecast:' . strtolower($city->label()),
],
'contexts' => [
'url',
],
],
];
return $build;
}
➔ Controllers return a render
array most of the time
➔ This is still not a Response
37
Drupal uses the render array returned from a controller to
fill the main page content.
We now need to understand how Drupal builds the render
array for the whole page and how it turns it into an HTML
response.
But at this point, even if you follow the source code, it’s
challenging to understand where the conversion
happens.
Controller output
A lot of systems in Drupal are loosely coupled and the
communications between them happens through the
event system.
When a controller returns something different from a
Response object, the Drupal Kernel takes the render
array and dispatches a kernel.view event.
38
How we discover that?
By tracing our page!
Tracing: o11y
39
➔ Observability suite
➔ Uses data collected by the Tracer module
➔ https://www.drupal.org/project/o11y/
➔ https://www.slideshare.net/sparkfabrik/do-you-kn
ow-what-your-drupal-is-doing-observe-it-drupalco
n-prague-2022
Drupal timeline
40
Events collector
41
$request = $event->getRequest();
$result = $event->getControllerResult();
// Render the controller result into a response if it's a render array.
if (
is_array($result) &&
($request->query->has(static::WRAPPER_FORMAT) || $request->getRequestFormat() == 'html')) {
$wrapper = $request->query->get(static::WRAPPER_FORMAT, 'html');
...
$renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]);
$response = $renderer->renderResponse($result, $request, $this->routeMatch);
...
$event->setResponse($response);
}
42
MainContentViewSubscriber
Events collector
43
foreach (
$this->blockRepository
->getVisibleBlocksPerRegion( $cacheable_metadata_list ) as $region => $blocks) {
/** @var DrupalblockBlockInterface [] $blocks */
foreach ($blocks as $key => $block) {
$block_plugin = $block->getPlugin();
if ($block_plugin instanceof MainContentBlockPluginInterface ) {
$block_plugin ->setMainContent( $this->mainContent );
$main_content_block_displayed = TRUE;
}
elseif ($block_plugin instanceof TitleBlockPluginInterface ) {
$block_plugin ->setTitle( $this->title);
}
elseif ($block_plugin instanceof MessagesBlockPluginInterface ) {
$messages_block_displayed = TRUE;
}
$build[$region][$key] = $this->blockViewBuilder ->view($block);
...
44
➔ Builds a render array with all
the visible blocks
BlockPageVariant
Blocks collector
45
index.php
46
Finally, the full HTML has been built.
The latest steps are to send the
response back to the browser and to
terminate the kernel.
use DrupalCoreDrupalKernel;
use SymfonyComponentHttpFoundationRequest;
$autoloader = require_once 'autoload.php';
$kernel = new DrupalKernel('prod', $autoloader);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
47
Theme collector - Rendering Call Graph
48
Theme collector - Twig filters
49
Asset collector - Libraries
50
➔ Theme collector lists SDC components
➔ Frontend collector shows Core Web Vitals information (on Chrome)
➔ Ajax collector lists ajax calls made from a page
➔ Some pages are forms or views, you can inspect them too
➔ WebProfiler collects redirects and forwards
➔ WebProfiler collects sub-requests made by BigPipe
https://tech.sparkfabrik.com/en/blog/drupal-sdc/
https://tech.sparkfabrik.com/en/blog/webprofiler_updates/
Discover more
51
Chapter 1 - Setting Up a Local Environment
…
Chapter 3 - How Drupal Renders an HTML Page
Chapter 4 - Mapping the Design to Drupal Components
…
Chapter 11 - Single Directory Components
…
Chapter 15 - Building a Decoupled Frontend
Learn more
Join us for
contribution opportunities
17-20 October, 2023
Room 4.1 & 4.2
Mentored
Contribution
First Time
Contributor Workshop
General
Contribution
#DrupalContributions
17 - 20 October: 9:00 - 18:00
Room 4.1
17 October: 17:15 - 18:00
Room 2.4
18 October : 10:30 - 11:15
Room 2.4
20 October : 09:00 - 12:30
Room 4.2
20 October : 09:00 – 18:00
Room 4.2
What did you think?
Please fill in this session survey directly from the Mobile App.
We appreciate your feedback!
Please take a moment to fill out:
the Individual
session surveys
(in the Mobile App or
QR code at the entrance of each room)
1 2
the general
conference survey
Flash the QR code
OR
It will be sent by email

More Related Content

Similar to Drupalcon 2023 - How Drupal builds your pages.pdf (20)

PDF
Drupal Recipe
hernanibf
 
PPTX
Large Scale Drupal - Behind the Scenes
Boyan Borisov
 
PDF
[drupalday2017] - Drupal come frontend che consuma servizi: HTTP Client Manager
DrupalDay
 
PPTX
One Drupal to rule them all - Drupalcamp London
hernanibf
 
PDF
Hong Kong Drupal User Group - Sep 13th
Wong Hoi Sing Edison
 
PPTX
DrupalCon Austin 2014 Review
Daniel Stout
 
PDF
Using VueJS in front of Drupal 8
Brian Ward
 
ODP
Drupal Architecture and functionality
Ann Lam
 
PDF
Drupal Flyover, CMS Expo
Emma Jane Hogbin Westby
 
PDF
Drupal Development.pdf
Appdeveloper10
 
PDF
Drupal Is Not Your Web Site
Phase2
 
PPT
Freelancer Weapons of mass productivity
Gregg Coppen
 
PDF
Recipes for Drupal distributions
Lakshmi Narasimhan Parthasarathy
 
PDF
Drupal is not your Website
Phase2
 
PPTX
DrupalTour. Rivne — Drupal 8 (Ivan Tibezh, InternetDevels)
Drupaltour
 
KEY
DrupalCon 2011 Highlight
Supakit Kiatrungrit
 
PDF
Introduction to drupal
Pedro Cambra
 
PDF
Do you know what your drupal is doing? Observe it!
Luca Lusso
 
PPT
Easy Drupal Project Deployment With Features Module & Drush
QArea
 
PDF
Drupal sharing in HP7
jimyhuang
 
Drupal Recipe
hernanibf
 
Large Scale Drupal - Behind the Scenes
Boyan Borisov
 
[drupalday2017] - Drupal come frontend che consuma servizi: HTTP Client Manager
DrupalDay
 
One Drupal to rule them all - Drupalcamp London
hernanibf
 
Hong Kong Drupal User Group - Sep 13th
Wong Hoi Sing Edison
 
DrupalCon Austin 2014 Review
Daniel Stout
 
Using VueJS in front of Drupal 8
Brian Ward
 
Drupal Architecture and functionality
Ann Lam
 
Drupal Flyover, CMS Expo
Emma Jane Hogbin Westby
 
Drupal Development.pdf
Appdeveloper10
 
Drupal Is Not Your Web Site
Phase2
 
Freelancer Weapons of mass productivity
Gregg Coppen
 
Recipes for Drupal distributions
Lakshmi Narasimhan Parthasarathy
 
Drupal is not your Website
Phase2
 
DrupalTour. Rivne — Drupal 8 (Ivan Tibezh, InternetDevels)
Drupaltour
 
DrupalCon 2011 Highlight
Supakit Kiatrungrit
 
Introduction to drupal
Pedro Cambra
 
Do you know what your drupal is doing? Observe it!
Luca Lusso
 
Easy Drupal Project Deployment With Features Module & Drush
QArea
 
Drupal sharing in HP7
jimyhuang
 

More from Luca Lusso (6)

PDF
Searching on Drupal with search API and Typesense
Luca Lusso
 
PDF
Leveraging the Command Pattern: Enhancing Drupal with Symfony Messenger.pdf
Luca Lusso
 
PDF
Devel for Drupal 8
Luca Lusso
 
PDF
A new tool for measuring performance in Drupal 8 - Drupal Dev Days Montpellier
Luca Lusso
 
PDF
A new tool for measuring performance in Drupal 8 - DrupalCamp London
Luca Lusso
 
PDF
Come portare il profiler di symfony2 in drupal8
Luca Lusso
 
Searching on Drupal with search API and Typesense
Luca Lusso
 
Leveraging the Command Pattern: Enhancing Drupal with Symfony Messenger.pdf
Luca Lusso
 
Devel for Drupal 8
Luca Lusso
 
A new tool for measuring performance in Drupal 8 - Drupal Dev Days Montpellier
Luca Lusso
 
A new tool for measuring performance in Drupal 8 - DrupalCamp London
Luca Lusso
 
Come portare il profiler di symfony2 in drupal8
Luca Lusso
 
Ad

Recently uploaded (20)

PDF
Driver Easy Pro 6.1.1 Crack Licensce key 2025 FREE
utfefguu
 
PDF
The 5 Reasons for IT Maintenance - Arna Softech
Arna Softech
 
PDF
Generic or Specific? Making sensible software design decisions
Bert Jan Schrijver
 
PDF
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
PPTX
OpenChain @ OSS NA - In From the Cold: Open Source as Part of Mainstream Soft...
Shane Coughlan
 
PDF
Wondershare PDFelement Pro Crack for MacOS New Version Latest 2025
bashirkhan333g
 
PPTX
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
PPTX
Tally software_Introduction_Presentation
AditiBansal54083
 
PDF
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
PPTX
Finding Your License Details in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PPTX
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
PDF
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
PPTX
Empowering Asian Contributions: The Rise of Regional User Groups in Open Sour...
Shane Coughlan
 
PPTX
Transforming Mining & Engineering Operations with Odoo ERP | Streamline Proje...
SatishKumar2651
 
PDF
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
PPTX
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PDF
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
PPTX
Agentic Automation Journey Series Day 2 – Prompt Engineering for UiPath Agents
klpathrudu
 
PDF
4K Video Downloader Plus Pro Crack for MacOS New Download 2025
bashirkhan333g
 
PPTX
Home Care Tools: Benefits, features and more
Third Rock Techkno
 
Driver Easy Pro 6.1.1 Crack Licensce key 2025 FREE
utfefguu
 
The 5 Reasons for IT Maintenance - Arna Softech
Arna Softech
 
Generic or Specific? Making sensible software design decisions
Bert Jan Schrijver
 
Open Chain Q2 Steering Committee Meeting - 2025-06-25
Shane Coughlan
 
OpenChain @ OSS NA - In From the Cold: Open Source as Part of Mainstream Soft...
Shane Coughlan
 
Wondershare PDFelement Pro Crack for MacOS New Version Latest 2025
bashirkhan333g
 
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
Tally software_Introduction_Presentation
AditiBansal54083
 
HiHelloHR – Simplify HR Operations for Modern Workplaces
HiHelloHR
 
Finding Your License Details in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
[Solution] Why Choose the VeryPDF DRM Protector Custom-Built Solution for You...
Lingwen1998
 
Empowering Asian Contributions: The Rise of Regional User Groups in Open Sour...
Shane Coughlan
 
Transforming Mining & Engineering Operations with Odoo ERP | Streamline Proje...
SatishKumar2651
 
iTop VPN With Crack Lifetime Activation Key-CODE
utfefguu
 
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
Agentic Automation Journey Series Day 2 – Prompt Engineering for UiPath Agents
klpathrudu
 
4K Video Downloader Plus Pro Crack for MacOS New Download 2025
bashirkhan333g
 
Home Care Tools: Benefits, features and more
Third Rock Techkno
 
Ad

Drupalcon 2023 - How Drupal builds your pages.pdf

  • 2. lussoluca How Drupal builds your pages D10 edition
  • 3. Luca Lusso Drupal / PHP / Go developer @ SparkFabrik Drupal contributor (WebProfiler, Monolog, Symfony Messenger, …) and speaker Drupal.org: www.drupal.org/u/lussoluca Twitter: www.twitter.com/lussoluca LinkedIn: www.linkedin.com/in/lussoluca drupal.slack.com: lussoluca @lussoluca
  • 4. We are a tech company of engineers, developers and designers, capable of accompanying customers step by step in the CLOUD NATIVE era, with an agile and pragmatic approach. We are committed to OPEN SOURCE and we embrace its philosophy in our internal practices. TECHNOLOGY, STRATEGY AND METHOD 4
  • 5. 5 All Drupal pages are built in the same way: A Request is received and a Response is produced In this session we’ll see how Drupal turns an HTTP Request into an HTML Response
  • 6. 6 GET /en/forecast/lille HTTP/2 <!DOCTYPE html > <html lang="en" dir="ltr"> <head> <meta charset="utf-8" /> <meta name ="Generator" content ="Drupal 10 (https://www.drupal.org) " /> ... </html>
  • 7. 7 ➔ Custom module (machine name: weather) ➔ Custom page ➔ Not build using the Node system (even if the flow is the same, the Node system is too complex to be analyzed in a short time) ➔ There is a custom Entity called “City” used to store a description and some geographic coordinates ➔ On this page we retrieve the city, call an external service to get weather forecasts and build the output ➔ Demo module repository: https://github.com/lussoluca/weather /en/forecast/lille
  • 8. 8
  • 9. 9 In the context of computer programming, instrumentation refers to the measure of a product's performance, in order to diagnose errors and to write trace information. ➔ Profiling ➔ Tracing ➔ Logging Instrumentation
  • 10. 10 ➔ New settings page available from Drupal 10.1 ➔ /admin/config/development/settings ➔ Useful during inspection and development ➔ Disable optimizations
  • 11. Profiling: WebProfiler 11 WebProfiler adds a toolbar at the bottom of every page and shows you all sorts of stats, such as the amount of database queries loaded on the page, which services are used, and much more. Depends on: ➔ Devel ➔ Tracer https://www.drupal.org/project/webprofiler/
  • 12. 12
  • 13. index.php 13 ➔ Require the Composer autoload file ➔ Create a new Kernel ➔ Handle the request to build a response ➔ Return the response ➔ Terminate the Kernel ➔ Front controller pattern use DrupalCoreDrupalKernel; use SymfonyComponentHttpFoundationRequest; $autoloader = require_once 'autoload.php'; $kernel = new DrupalKernel('prod', $autoloader); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
  • 14. ➔ https://stackphp.com/ ➔ DrupalKernel is decorated by a set of middlewares Stack middleware 14 DrupalKernel
  • 15. ➔ Ordered by priority desc on request and by priority asc on response Stack middleware 15
  • 16. 16 After passing through all the middlewares, the Request is handled by a router that finds the code responsible for converting the Request into a Response. In this case the Request is managed by a Controller: ForecastController. Request collector
  • 18. ➔ weather.routing.yml ➔ Route name: weather.forecast ➔ {city} placeholder ➔ _controller ➔ _title ➔ Requirements ➔ Param converters Routing weather.forecast: path: '/forecast/{city}' defaults: _controller: 'DrupalweatherControllerForecastController::page' _title_callback: 'DrupalweatherControllerForecastController::title' requirements: _permission: 'access content' options: parameters: city: type: 'weather:city' 18
  • 20. Param converters weather.forecast: path: '/forecast/{city}' defaults: _controller: 'DrupalweatherControllerForecastController::page' _title_callback: 'DrupalweatherControllerForecastController::title' requirements: _permission: 'access content' options: parameters: city: type: 'weather:city' The Node module uses the same technique to upcast the nid to full Node objects. 20
  • 21. Param converters public function applies( $definition , $name, Route $route):bool { if (!empty($definition ['type']) && $definition ['type'] === 'weather:city' ) { return TRUE; } return FALSE; } public function convert( $value, $definition , $name, array $defaults ):?CityInterface { $cities = $this -> entityTypeManager ->getStorage( 'city') ->loadByProperties([ 'label' => $value]); if (count( $cities) == 0) { return NULL; } return reset($cities); } ➔ applies is used to check if this param converter should be used for this route ➔ convert will takes the value from the url (lille) and convert it to something more structured (typically an object). If convert return NULL, Drupal will return a 404 error page. 21
  • 24. Title callback weather.forecast: path: '/forecast/{city}' defaults: _controller: 'DrupalweatherControllerForecastController::page' _title_callback: 'DrupalweatherControllerForecastController::title' requirements: _permission: 'access content' options: parameters: city: type: 'weather:city' Method used by Drupal to retrieve the title of a route 24
  • 25. Title callback public function title(CityInterface $city): string { return $this->t('Weather forecast for @city', [ '@city' => $city->label(), ]); } ➔ Returns a string ➔ Can use the parameters (upcasted) from the URL 25
  • 26. Controller callback weather.forecast: path: '/forecast/{city}' defaults: _controller: 'DrupalweatherControllerForecastController::page' _title_callback: 'DrupalweatherControllerForecastController::title' requirements: _permission: 'access content' options: parameters: city: type: 'weather:city' Method used by Drupal to retrieve the main content of a route 26
  • 27. Controller callback public function page(CityInterface $city): array { $forecast = $this->weatherClient ->getForecastData( $city->label()); $build['content' ] = [ '#theme' => 'weather_forecast' , '#forecast' => $forecast , '#units' => $this->config( 'weather.settings' )->get('units'), '#description' => $city->getDescription(), '#coordinates' => $city->getCoordinates(), '#cache' => [ 'max-age' => 10800, // 3 hours. 'tags' => [ 'forecast:' . strtolower( $city->label()), ], 'contexts' => [ 'url', ], ], ]; return $build; } 27
  • 28. Services public function page(CityInterface $city): array { $forecast = $this->weatherClient ->getForecastData( $city->label()); $build['content'] = [ '#theme' => 'weather_forecast', '#forecast' => $forecast, '#units' => $this->config('weather.settings')->get('units'), '#description' => $city->getDescription(), '#coordinates' => $city->getCoordinates(), '#cache' => [ 'max-age' => 10800, // 3 hours. 'tags' => [ 'forecast:' . strtolower($city->label()), ], 'contexts' => [ 'url', ], ], ]; return $build; } ➔ PHP classes to perform some useful task ◆ Sending email ◆ Authenticate users ◆ Call external services ◆ … ➔ Drupal core has more than 500 services ➔ We’ve seen a couple of them already: ◆ Middlewares are services ◆ Param converters are services 28
  • 31. 31 ➔ Well done! We found that the ForecastController has the piece of code that render this route! ➔ But wait… ➔ ForecastController returns the title of the page and a simple array… ◆ Where all this markup comes from? ◆ Where are loaded and placed the blocks (like the header and the footer)? ◆ How CSS and JavaScript assets are managed? Entering the Drupal render pipeline /en/forecast/lille
  • 33. Render arrays public function page(CityInterface $city): array { $forecast = $this->weatherClient->getForecastData($city->label()); $build['content' ] = [ '#theme' => 'weather_forecast' , '#forecast' => $forecast , '#units' => $this->config( 'weather.settings' )->get('units'), '#description' => $city->getDescription(), '#coordinates' => $city->getCoordinates(), '#cache' => [ 'max-age' => 10800, // 3 hours. 'tags' => [ 'forecast:' . strtolower( $city->label()), ], 'contexts' => [ 'url', ], ], ]; return $build; } ➔ weather_forecast: theme hook ➔ Keys that starts with ‘#’ are the properties ➔ Keys that doesn’t start with ‘#’ are nested render arrays ➔ Must declare ‘#cache’ information 33 ➔ Where I can find the whole list of available theme hooks? ➔ Which arguments are available?
  • 34. Theme registry "weather_details" => array:6 [ ▶] "weather_forecast" => array:6 [▼ "variables" => array:4 [▼ "forecast" => [] "units" => "metric" "description" => "" "coordinates" => [] ] "type" => "module" "theme path" => "modules/custom/weather" "template" => "weather-forecast" "path" => "modules/custom/weather/templates" "preprocess functions" => array:2 [▼ 0 => "template_preprocess" 1 => "contextual_preprocess" ] ] "weather_forecast_single" => array:6 [ ▶] ➔ Provided by Devel module (on /devel/theme/registry) ➔ For each theme hook ◆ List of variables (with default values) ◆ Path on the filesystem ◆ Template ◆ Proprocess functions 34
  • 35. Defining a theme hook function weather_theme(): array { return [ 'weather_forecast' => [ 'variables' => [ 'forecast' => [], 'units' => 'metric', 'description' => '', 'coordinates' => [], ], ], ]; } ➔ weather.module ➔ List of theme hooks defined by a module, with a machine name and a list of accepted variables (and their default values) 35
  • 36. weather-forecast.html.twig {{ attach_library(' weather/map ') }} {{ attach_library(' weather/base ') }} <div class="forecast-list "> {% for item in forecast.list %} <div class="forecast-element "> <h3 class="forecast-element__date ">{{ item.dt_txt|date(" d/m/Y H:i") }}</h3> <p class="forecast-element__temp ">{{ item.main.temp }} °{{ units == ' metric' ? 'C' : 'F' }}</p> <p class="forecast-element__description ">{{ item.weather[ 0].description|title }}</ p> <a href="{{ path('weather.details ', {city:forecast.city.name, date:item.dt_txt}) }}" class="use-ajax" data-dialog-type ="dialog" data-dialog-options ='{"width":700,"title":"{{ 'Details'|t }}"}'>More info</ a> </div> {% endfor %} </div> <div class="city-wrapper "> <div class="city-description "> {{ description }} </div> <div class="city-map"> <div id="map" data-lat="{{ coordinates .0 }}" data-lng="{{ coordinates .1 }}"></div> </div> </div> 36
  • 37. Controller output public function page(CityInterface $city): array { $forecast = $this->weatherClient->getForecastData($city->label()); $build['content'] = [ '#theme' => 'weather_forecast', '#forecast' => $forecast, '#units' => $this->config('weather.settings')->get('units'), '#description' => $city->getDescription(), '#coordinates' => $city->getCoordinates(), '#cache' => [ 'max-age' => 10800, // 3 hours. 'tags' => [ 'forecast:' . strtolower($city->label()), ], 'contexts' => [ 'url', ], ], ]; return $build; } ➔ Controllers return a render array most of the time ➔ This is still not a Response 37
  • 38. Drupal uses the render array returned from a controller to fill the main page content. We now need to understand how Drupal builds the render array for the whole page and how it turns it into an HTML response. But at this point, even if you follow the source code, it’s challenging to understand where the conversion happens. Controller output A lot of systems in Drupal are loosely coupled and the communications between them happens through the event system. When a controller returns something different from a Response object, the Drupal Kernel takes the render array and dispatches a kernel.view event. 38 How we discover that? By tracing our page!
  • 39. Tracing: o11y 39 ➔ Observability suite ➔ Uses data collected by the Tracer module ➔ https://www.drupal.org/project/o11y/ ➔ https://www.slideshare.net/sparkfabrik/do-you-kn ow-what-your-drupal-is-doing-observe-it-drupalco n-prague-2022
  • 42. $request = $event->getRequest(); $result = $event->getControllerResult(); // Render the controller result into a response if it's a render array. if ( is_array($result) && ($request->query->has(static::WRAPPER_FORMAT) || $request->getRequestFormat() == 'html')) { $wrapper = $request->query->get(static::WRAPPER_FORMAT, 'html'); ... $renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]); $response = $renderer->renderResponse($result, $request, $this->routeMatch); ... $event->setResponse($response); } 42 MainContentViewSubscriber
  • 44. foreach ( $this->blockRepository ->getVisibleBlocksPerRegion( $cacheable_metadata_list ) as $region => $blocks) { /** @var DrupalblockBlockInterface [] $blocks */ foreach ($blocks as $key => $block) { $block_plugin = $block->getPlugin(); if ($block_plugin instanceof MainContentBlockPluginInterface ) { $block_plugin ->setMainContent( $this->mainContent ); $main_content_block_displayed = TRUE; } elseif ($block_plugin instanceof TitleBlockPluginInterface ) { $block_plugin ->setTitle( $this->title); } elseif ($block_plugin instanceof MessagesBlockPluginInterface ) { $messages_block_displayed = TRUE; } $build[$region][$key] = $this->blockViewBuilder ->view($block); ... 44 ➔ Builds a render array with all the visible blocks BlockPageVariant
  • 46. index.php 46 Finally, the full HTML has been built. The latest steps are to send the response back to the browser and to terminate the kernel. use DrupalCoreDrupalKernel; use SymfonyComponentHttpFoundationRequest; $autoloader = require_once 'autoload.php'; $kernel = new DrupalKernel('prod', $autoloader); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
  • 47. 47 Theme collector - Rendering Call Graph
  • 48. 48 Theme collector - Twig filters
  • 49. 49 Asset collector - Libraries
  • 50. 50 ➔ Theme collector lists SDC components ➔ Frontend collector shows Core Web Vitals information (on Chrome) ➔ Ajax collector lists ajax calls made from a page ➔ Some pages are forms or views, you can inspect them too ➔ WebProfiler collects redirects and forwards ➔ WebProfiler collects sub-requests made by BigPipe https://tech.sparkfabrik.com/en/blog/drupal-sdc/ https://tech.sparkfabrik.com/en/blog/webprofiler_updates/ Discover more
  • 51. 51 Chapter 1 - Setting Up a Local Environment … Chapter 3 - How Drupal Renders an HTML Page Chapter 4 - Mapping the Design to Drupal Components … Chapter 11 - Single Directory Components … Chapter 15 - Building a Decoupled Frontend Learn more
  • 52. Join us for contribution opportunities 17-20 October, 2023 Room 4.1 & 4.2 Mentored Contribution First Time Contributor Workshop General Contribution #DrupalContributions 17 - 20 October: 9:00 - 18:00 Room 4.1 17 October: 17:15 - 18:00 Room 2.4 18 October : 10:30 - 11:15 Room 2.4 20 October : 09:00 - 12:30 Room 4.2 20 October : 09:00 – 18:00 Room 4.2
  • 53. What did you think? Please fill in this session survey directly from the Mobile App.
  • 54. We appreciate your feedback! Please take a moment to fill out: the Individual session surveys (in the Mobile App or QR code at the entrance of each room) 1 2 the general conference survey Flash the QR code OR It will be sent by email