Sometimes you need to adjust the DOM in a way you can’t do just via overwriting Twig templates. Thats where preprocessors come in handy.
Lets assume we already have a custom theme called “mytheme” and created a mytheme.theme file in web/themes/custom/mytheme
Even though this file has a .theme extension you can write normal PHP code in there.
Preprocessor functions are usually in the following structure: THEMENAME_preprocess_HOOK()
Our THEMENAME here is mytheme and you can see the available hooks via enabling the Twig Debugging Mode in the frontend HTML comments.
So therefore you can create custom preprocessor function like
mytheme_preprocess_node()
All nodes
mytheme_preprocess_node__article()
All article nodes
mytheme_preprocess_menu()
All menus
What needs to be inside these functions?
Lets say we want to add a class to a specific menu.
/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
if ($variables['menu_name'] == 'main') {
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
$variables['attributes']['class'] = array_merge(
$variables['attributes']['class'],
['my-main-menu']
);
}
}
First of all: $variables is passed through by reference, therefore all the changes we do inside $variables gets passed onto the template side.
Therefore, as you can see above, we check on which menu we currently are and if we are on the ‘main’ menu we add the class ‘my-main-menu’ to the attributes class array.
This results in the following DOM:
The hook “menu__main” here is only a more specific hook as we used above.
We could have also got the same result with the following function:
function mytheme_preprocess_menu__main(&$variables) {
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
$variables['attributes']['class'] = array_merge(
$variables['attributes']['class'],
['my-main-menu']
);
}
As you can see the function name has an appended __main at the end but now we don’t need the outer most if query to check which menu we currently try to preprocess.
One important notice here: The preprocessor mytheme_preprocess_menu only adjusts values for the menu.html.twig, not any other Twig files like page.html.twig or block.html.twig
If you want to adjust values inside the page.html.twig or any other Twig file you should check its hook first and create a separate preprocessor function for this hook.
So therefore you can only adjust values inside a preprocessor for its assigned element connected via the hook. If you want to adjust an element because of another element thats more complicated.
Requirement: Working Drupal site which has already been installed via a local instance or on a server.
Add a settings.local.php for your environment specific settings
It’s always a good idea to have a config file specific to your currently active environment (e.g. stagging, dev and live server). Drupal already preps that in your web/sites/default/settings.php with the following lines:
You only have to uncomment these 3 lines of code and create a web/sites/default/settings.local.php
In there you can add your environment specific settings like the database array already present in your settings.php or the ones I will explain further in this post:
Exclude settings.local.php from GIT
Please check your .gitignore so the settings.local.php doesn’t get commited into GIT.
I have experienced some weird caching issues if your not logged in. Even though I was sure i had correctly regenerated my CSS & JS and force refreshed my browser cache I still got old styling.
The solution: Log in
Twig Debugging
Twig has its own “config file” in which you can tweak how Twig is handling your template files. This file is located in web/sites/default/services.yml
services.yml not present?
If the services.yml is not present just copy the default.services.yml and rename it into services.yml
After that you should perform a drush cr!
In this services.yml you should adjust the following values to have a better Twig experience:
parameters:
# --- Some comments ---
twig.config:
# --- Some comments ---
debug: true
# --- Some comments ---
auto_reload: true
# --- Some comments ---
cache: false
With that you should see HTML comments in the frontend showing you several information about which template Drupal used to generate this DOM, how you can overwrite these templates and which hook you can use to adjust parts if the DOM in preprocessors. More on that in another chapter!
Twig Developer Tools
The easiest way to know your current Twig environment is via outputting all your available variables:
{{ dump() }}
I have tried using kint but only ran into PHP memory size issues regularly due to the fact, that Drupal likes to have circular references and therefore create an infinite loop.
So my best experiences were just using the default Twig debugging tool dump()
Drupal is another very popular CMS which has a pretty large community and many “modules” which extend the CMS the way you like.
In my opinion Drupal does the following aspects of being a CMS very good:
Creation of Content Types, Taxonomies and fields via backend
Built in caching system
Built in multilingual support
Built in backend error log viewer (Watchdog)
Very mighty permission system
You recieve e-mail notifications if the core or a module has security issues (if you subscribe to the security newsletter)
Built on Symfony and therefore you have TWIG as a template engine
The “Webform” module is the most advanced form module I have ever seen
Neutral aspects:
Requires PHP composer to install/update core and modules therefore you need SSH but you have dependency management. If you want to learn more about PHP composer see HERE
Backend content structure not as user-friendly as WordPress
Negative aspects:
If you want to do more than the provided functionality in the backend you will have a pretty steep learning curve on how to e.g. adjust the DOM the way you want it, learn what preprocessors are and how to adjust one element due to the configuration of another.
If a update goes wrong (either composer updates or database updates) the whole site will be down till you fixed it. It is very common that you have to add a patch to a module or the core so a specific problem doesn’t occur on your website till this patch has been merged into its own release version by the core team or the module developer.
Drupal starts of with 87 database tables, unlike WordPress with just 12 tables. There are many reasons why that is the fact. One of them is the fact, that each field gets its own table and the whole caching system requires its own tables. See database diagramm here: https://www.drupal.org/files/Drupal8_UPsitesWeb_Schema_10-19-2013.png
So what I want to say is
Drupal is a CMS which is easy to use if you need exactly what it offers in the backend. If you need more customization of specific elements which are not already provided or are extendable by modules you will have a pretty hard time.
I love the fact, that multilingual support is built in and there is also no problem adding multilingual support after you have created the website because the whole system has that covered.
If you want to learn more about Drupal the site drupalize.me has very good descriptions and trainings.
WordPress is definitely the most well-known CMS in the general public and therefore the first choice for most people to start with.
And in my opinion thats totally fine because WordPress has in my opinion:
the best backend to manage content in a user-friendly way,
the easiest setup to start with,
has a huge repository of plugins (which can be a downside as well, see below),
huge community + tons of documentation,
is lightweight and can run on pretty much anything,
easy to update with a 1 click button in the backend
But there are some downsides to WordPress which can lead to many headaches and frustration:
No multi-lingual support per default WordPress, only with (mostly) paid plugins
There are so many free plugins that its hard to decide which is the one you really need and most of the time (after you decide on a plugin) it comes with so much functionality you will not need which creates overhead
Interoperability between plugins regularly creates problems (for me)
And here some neither positive nor negative aspects:
No dependency management makes it easy to update but creates dependency problems if plugin versions don’t work together
No PHP template engine like TWIG produces no consistent way to connect PHP logic (aka Controller) with the template (aka View) and makes it harder to reuse template parts but removes the overhead and need of configuration for TWIG
WordPress starts of with just 12 database tables, unlike Drupals 87 tables. This makes the database structure of WordPress very approachable and easy to understand if you need to dig deeper.
WordPress is a CMS which is fairly easy to understand, can be setup and used pretty quickly and has tons of plugins and support available. Because of these reasons it became the leading CMS worldwide.
BUT
WordPress also has its limitations and drawbacks. For me the main reasons to not use WordPress are:
Multilingual Website
Huge amount of data and/or rapidly increasing amount of data (> 200k entities)
Custom Search Implementation like SOLR
About the “Huge amount of data (> 200k entities)”: This is just a number I wrote down by gut feeling. The fact that every field of every entity in WordPress is saved in the same wp_postmeta table just increases the size of this table so that it has to get slow after “some amount” of entities. In Drupal each field gets its own table and therefore is much more performant handling large amounts of data.
With that alone you should see the module appear in the backend under “Extend”
Adding the views display style plugin code
Now lets create the rest of the files with the following file system structure:
The file “SunlimeCustomViewDisplay.php” contains:
<?php
/**
* @file
* Definition of Drupal\sunlime_custom_view_display\Plugin\views\style\SunlimeCustomViewDisplay.
*/
namespace Drupal\sunlime_custom_view_display\Plugin\views\style;
use Drupal\core\form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;
/**
* Style plugin to render listing.
*
* @ingroup views_style_plugins
*
* @ViewsStyle(
* id = "sunlime_custom_view_display",
* title = @Translation("Sunlime New"),
* help = @Translation("Render a listing of view data."),
* theme = "views_view_sunlime_custom_view_display",
* display_types = { "normal" }
* )
*
*/
class SunlimeCustomViewDisplay extends StylePluginBase {
/**
* Set default options
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['height'] = array('default' => '354');
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['height'] = array(
'#type' => 'textfield',
'#title' => $this->t('Height'),
'#description' => $this->t('Default height of header.'),
'#size' => '6',
'#default_value' => $this->options['height'],
'#required' => TRUE,
'#field_suffix' => 'px',
);
}
}
With this PHP class you will now see the following option popup in the view formatter selection:
The buildOptionsForm(&$form, FormStateInterface $form_state) function defines which fields should be present in the “Settings” modal of the view formatter.
The defineOptions() function defines the default values for the given fields in the buildOptionsForm(&$form, FormStateInterface $form_state) function.
One very important part here is the theme definition in the @ViewsStyle comments of the class. This text defines how the template file has to be named.
Basically as you can see its 1:1 the same name but with the _ replaced with –
I don’t know why that has to be like that but I guess it has some Drupal historical reason.
Outputting the view
So now lets open views-view-sunlime-custom-view-display.html.twig and add the following code:
<h1>Start of view-view-sunlime-custom-view-display.html.twig</h1>
<h2>Settings of View</h2>
{# Options from View Settings #}
{{ dump(view.style_plugin.options) }}
<h2>Rows</h2>
{# View Result #}
{{ dump(rows) }}
<h2>Logged in user</h2>
{# Logged in user #}
{{ dump(user) }}
With that code (and a place where your view is outputted in Drupal like a block or a page) you should see something like this:
Preproccessing the view
Just like you can preprocess many other data in Drupal you can of course preprocess your views data as well.
For that first of all open the sunlime_custom_view_display.module file and add the following content:
<?php
/**
* @file
* Sunlime Custom Views module help and theme functions.
*/
// Theme functions in a separate .inc file.
\Drupal::moduleHandler()->loadInclude('sunlime_custom_view_display', 'inc', 'sunlime_custom_view_display.theme');
/**
* Implements hook_theme().
*/
function sunlime_custom_view_display_theme($existing, $type, $theme, $path) {
return array(
'sunlime_view_display_module' => array(
'file' => 'sunlime_custom_view_display.theme.inc',
),
);
}
Basically the first line dictates that the file sunlime_custom_view_display.theme.inc should also be included. Lets add the following code inside the sunlime_custom_view_display.theme.inc
<?php
/**
* Prepares variables for view template.
*
* Default template: views-view-sunlime-custom-view-display.html.twig.
*
* @param array $variables
* An associative array containing:
* - view: A ViewExecutable object.
* - rows: The raw row data.
*/
function template_preprocess_views_view_sunlime_custom_view_display(&$variables) {
// View options set by user.
$options = $variables['view']->style_plugin->options;
$rows = $variables['rows'];
echo '<h2> $options in sunlime_custom_view_display.theme.inc</h2>';
var_dump($options);
echo '<h2> $rows in sunlime_custom_view_display.theme.inc</h2>';
var_dump($rows);
// Apply required logic and add variables here. We can define variable in
// our hook_theme and use it here.
}
With that in place you should now see the following additional output:
Depending on your Drupal cache settings as well as your TWIG debug settings you will probably have to do a drush cr more often or not.
I want to use the fields defined in the view backend. How do I do that?
The above example just shows you how to create a basic views formatter and requires you to iterate over the rows object in TWIG to access the nodes data.
But this “version” will not work with the defined fields in the Drupal backend.
The following template file shows a basic view formatter twig output for a grid output:
At the start of your custom PHP class you have to add the following 2 variables so you “somehow” tell Drupal to “use the row plugin” which changes “something”.
But not only that, you will also have to add the following preprocessor to your <module>.theme.inc
/**
* Prepares variables for the view grid display template.
*
* Template: views-view-grid-display.html.twig
*
* @param array $variables
* An associative array containing:
* - view: The view object.
* - rows: An array of row items. Each row is an array of content.
*/
function template_preprocess_views_view_grid_display(&$variables) {
template_preprocess_views_view_unformatted($variables);
}
The name of the function has to be adapted to your view display name, but I hope this is clear since it is basically a preprocessor.
I have no idea why this is needed and we just accidentally happened to stumble upon that but as you can see, I have no idea whats happening here. With that, it works, and thats all you will need.
If someone reads this who has a deeper understanding of this and can explain to me why this is needed I would be very happy to adjust this post and cite you.
Multilingual adjustments
Using custom view style plugins in a multilingual page need a little bit of customization to work.
If you read out the fields value hard coded via {{ row._entity.fields.title.value }} you will need to adjust your code to something like that:
{% set current_language = row.node_field_data_langcode %}
{% if row._entity.hastranslation(current_language) %}
{% set entity = row._entity.translation(current_language) %}
{% else %}
{# No translation available for this node #}
{% endif %}
{{ entity.fields.title.value }}
A content management system (short CMS) allows you to structure your content into bits and pieces which can be displayed, searched, filtered or used in any way you need it.
How to structure content
The basic concept behind a CMS is always the same:
Content Types
Content Types (or sometimes called Post Types or Collections, in short CT) are ways to differentiate single entries of your content into separate “areas” so you can handle these types of content better.
Examples for Content Types are:
Blog-Posts
Team-Members
Projects
Each Content Type usually has its own set of fields, in which data is saved per entity.
Lets take “Team-Members” as an example. Therefore we could have the following fields inside the Team-Members Content Type:
First name
Last name
Birthdate
Image
Job Title
So therefore we can create 2 “entities” inside the Team-Members Content-Type:
Kevin Pfeifer
First name => Kevin
Last name => Pfeifer
Birthdate => 08-05-1993
Image => <Path-to-image>
Job Title => Full Stack Developer
Jamil Bates
First name => Jamil
Last name => Bates
Birthdate => 23-03-1991
Image => <Path-to-image>
Job Title => Frontend Developer
Taxonomies
Taxonomies (short tax) are ways to categorise your entities inside your content type into more separate areas.
If we stick with the “Team-Member” Content Type an easy example for a Taxonomy is “Project-Team“.
Inside our Taxonomy we now create terms called “Website Devguide” and “Website DASVok” which we now can connect to our Team-Members.
Therefore we can assign Team-Member “Kevin Peifer” to the Project “Website DASVok” and the Team-Member “Jamil Bates” to the Project “Website Devguide”.
Common CMS systems and their default Content Types and Taxonomies
Lets take 2 of the most common CMS and explain their default Content Types and Taxonomies:
WordPress
Content Types in WordPress are called “Post Types” because WordPress originated as a blogging system and therefore pretty much every “entity” is a “post” inside WordPress.
As per WordPress 5.4.2 (June 2020) you get the following backend structure per default configured:
“Posts” (CT) with 2 Taxonomies: “Categories” and “Tags“ “Pages” (CT) with no Taxonomies
Technically Media is a CT as well, because each uploaded media gets its own “attachment” entity but thats too far in the WordPress structure.
Of course, dependent on your used Plugins, you will get more CT and/or Taxonomies or even install Plugins where you can create your own CT and Taxonomies in the backend.
Drupal 8/9
In Drupal Taxonomies are called “Vocabularies“. Currently I don’t know why but I guess it has some historical reason, same like Content Types in WordPress are called Post Types.
As per Drupal 9 (June 2020) you get the following backend structure per default configured:
“Article” (CT) with 1 Taxonomy “Tags“ “Basic page” (CT) with no Taxonomies
You can also enable the “Media” module which allows you to basically do the same with your uploaded files (images, documents etc.)
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPT
Privacy & Cookies Policy
Privacy Overview
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.