Update 2022-06-13:
Be warned! It turns out that using Twig or any other depencies with composer in WordPress is in fact a bad idea! The problem is that other plugins or themes may also use the same components but in different version – and then this may end up in all kind of conflicts! For the time being I do NOT recommend to follow this article!
The basic structure of WordPress plugins is kept rather simple:
The file readme.txt
file with a description in the header and a PHP file for the plugin initialization are required as a minimum requirement in the plugin directory. It is not absolutely necessary to implement your own classes, since the code is executed directly by WordPress – however, organizing it into classes makes readability and maintainability much easier.
Object-oriented structure of a WordPress plugin
Due to the way WordPress works, the usual approach for an object-oriented plugin is to implement a main class that is instantiated at runtime. All necessary initializations are carried out in the constructor of the class. It makes sense to define your own namespace to ensure that your the class does not conflict with existing global classes.
For using register_activation_hook()
and register_deactivation_hook()
it is neccessary to provide the name of the file which is used to load the plugin. So it’s a good idea to pass this as a parameter to the main class so that this information is available there as well.
It also makes sense to define the plugin version as constant (MyPlugin::VERSION
). This will be needed when using wp_enqueue_script()
and wp_enqueue_style()
and should be adjusted for every update of the plugin.
As an example, a simple plugin that provides a shortcode.
In wp-content/plugins/my-plugin/readme.txt
:
=== My Plugin === Contributors: myusername Tags: images Requires at least: 5.0 Tested up to: 6.0 Stable tag: 1.0 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html My Plugin for useful stuff. == Description == This is the detailed plugin description.
In wp-content/plugins/my-plugin/my-plugin.php
:
<?php namespace MyPlugin; defined('ABSPATH') or die(); class MyPlugin { const VERSION = '1.0'; protected string $pluginFile; public function __construct(string $pluginFile) { $this->pluginFile = $pluginFile; add_shortcode('myshortcode', array($this, 'shortCode')); } public function shortCode($atts, $content = null): string { $output = ''; // ... return $output; } } $myPlugin = new MyPlugin(__FILE__);
The process here is as following:
- WordPress will check if the plugin is already activated and will run the code in
wp-content/plugins/my-plugin/my-plugin.php
. - By
$myPlugin = new MyPlugin();
a new instance of the classMyPlugin
is created and the constructor of the class is executed, in which the shortcode is added. - Whenever the shortcode
[myshortcode]
is used, the methodMyPlugin::shortCode()
will be executed.
With the statement defined('ABSPATH') or die();
at the beginning it is ensured that the code is only executed in the context of WordPress – which is recognizable by the fact that ABSPATH
is defined.
Integration of Twig
The intended way to use Twig is to install it with Composer. The installation of Composer itself is described in the documentation, see Download Composer.
To install Twig, change the directory of the plugin in a console and execute the following command to install Twig 3.0:
composer require "twig/twig:^3.0"
This will download Twig itself and the required packages symfony/polyfilly-mbstring
and symfony/polyfill-ctype
from https://repo.packagist.org. The result is an additional vendor
directory for the packages and the two files composer.json
and composer.lock
.
In addition, you manually create the directory templates
, in which you later store the templates for Twig. This results in the following structure:
wp-content/ plugins/ my-plugin/ templates/ vendor/ composer/ symfony/ twig/ autoload.php composer.json composer.lock my-plugin.php readme.txt
Integrating Twig via Composer’s autoloader
The necessary Twig classes are loaded via the autoloader as soon as you request them with use
. For this to work, the autoloader that Composer created in the vendor
directory must be loaded.
An instance of Twig is created in the constructor of the plugin class, which is then used to output templates.
The file my-plugin.php
is extended as follows:
<?php namespace MyPlugin; use Twig\Loader\FilesystemLoader; use Twig\Environment; defined('ABSPATH') or die(); require(__DIR__ . '/vendor/autoload.php'); class MyPlugin { const VERSION = '1.0'; const SLUG = 'my-plugin; protected string $pluginFile; protected Environment $twig; public function __construct(string $pluginFile) { $this->pluginFile = $pluginFile; $this->twig = new Environment(new FilesystemLoader(__DIR__.'/templates')); add_shortcode('myshortcode', array($this, 'shortCode')); } } public function shortCode($atts, $content = null): string { $output = ''; // ... return $output; } } $myPlugin = new MyPlugin(__FILE__);
Using Twig-Templates
The structure of Twig templates is well described in the Twig documentation: Twig for Template Designers
As an example, the shortcode should output an image using the specified ID as follows:
[myshortcode id="7"]
The output of the shortcode would be possible with a Twig template templates/shortcode.html.twig
as follows:
<figure> <img src="{{ imageUrl }}" alt="{{ imageAlt }}" /> {% if imageCaption %} <figcaption>{{ imageCaption }}</figcaption> {% endif %} </figure>
This can then be used at runtime in the shortcode handler:
public function shortCode($atts, $content = null): string { if (!isset($atts['id'])) { return ''; } $id = $atts['id']; return $this->twig->render('shortcode.html.twig', [ 'imageUrl' => wp_get_attachment_image_url($id, 'full'), 'imageAlt' => get_post_meta($id, '_wp_attachment_image_alt', TRUE); 'imageCaption' => wp_get_attachment_caption($id) ]); }
With $this->twig->render('shortcode.html.twig', ...
Twig is asked to process the template in the file templates/shortcode.html.twig
. The variables required there are passed as array and are then available within the template accordingly.
Outlook
Just for a simple shortcode, using Twig might seem to be too much. However, there are situations where significantly more HTML has to be generated than just a few lines – for example for administrative pages in the backend. This is where Twig is very helpful for separating code and UI. Templates can also be broken down into smaller units, which simplifies troubleshooting if a change doesn’t produce what you expected.
Caching
If Twig templates are used in the frontend, caching can help a lot to improve performance. Twig compiles every template and supports storing the result to a cache, so next time the template does not have to be compiled again. However this requires creating a cache folder which also should be removed when the plugin is deactivated. This will be covered in a future article.
Extensions
It is also possible to integrate your own Twig extensions, see Extending Twig. This is necessary, for example, if you want to use the WordPress functions __()
or _n()
to output texts with translations in a template. This will be covered in a future article.