
Our themes include the ability to install Dripyard custom Drupal recipes from within the theme settings page. To our knowledge, this capability is a Dripyard first and deserves its own blog post (sign up to be notified). To make this work, we utilize the Drupal Batch API, but subsequent batch requests don’t invoke classes defined in a theme since it’s not in context for general Drupal requests. To resolve this, we found a creative way to ensure additional requests invoke our batch process class every time. Using a combination of our class loader and the Batch API’s setFile()
method, we’re able to maintain complete control over the batch pipeline to install the recipes.When we started building Dripyard themes, we made the decision early on to not have any dependencies outside of Drupal core. We wanted to avoid depending on contributed modules, npm
build processes, and external libraries so that our premium Drupal themes could adapt to any development workflow. Additionally, we wanted to prevent having our own companion module required for Dripyard themes so we can offer a complete package you can download, drop in, and enable.
Drupal’s autoloader implementation for themes is limited
With the fixes for class loading and object-oriented inheritance, we can establish patterns in our themes—like our RecipeInstaller
, which handles the recipe batch processes explained above and allows sub-themes like neonbyte
to extend or override select parts of the class. In the neonbyte
example, the base class handles all batch processing and the logic of locating and installing items, while the extended neonbyte
class defines the recipes that are available and their relationships to other recipes. Additionally, if you use neonbyte
as a base theme of your custom theme, the recipes from neonbyte
are still discovered and available to be installed form your custom theme settings page. During development of our layout classes, we realized that using our theme namespace Drupaldripyard_base
would sometimes cause errors. For example when Layout Builder was making AJAX requests for the user interface. By ensuring our classes are registered with a custom class loader, we were able to reliably bundle custom Layouts
classes in the theme. Thanks to these customizations, we’re able to ship the Dripyard Dynamic Layout with our base theme—a flexible Layout Builder section that lets you define columns, rows, and a wide range of spacing options.
Shipping custom layouts via the Drupal Layout API
Interested in more? Join our webinar where we’ll be launching our themes for sale and you can see the power of Dripyard and Drupal together. Our premium Drupal themes deliver modern PHP, clean architecture, and Layout Builder support—all while depending only on Drupal core!We plan to offer first-class support for Drupal Canvas (formerly Experience Builder) once it has an official release. In the meantime, we’ve standardized on Layout Builder for all of our recipes and demo content, since it’s already part of Drupal core and provides a solid page-building experience.
Batch processing and autoloading
public static function queueInstall($recipe_key, $theme) {
try {
$recipe_path = static::resolveRecipePath($recipe_key, $theme);
$recipe = Recipe::createFromDirectory($recipe_path);
static::$recipeBatch = static::$recipeBatch ?? new BatchBuilder();
// Ensure this file is loaded on every operation since themes
// do not have automatic class loading.
static::$recipeBatch->setFile(Drupal::service('extension.list.theme')->getPath('dripyard_base') . '/src/Recipes/RecipeBatchProcessor.php');
class RecipeInstaller extends RecipeInstallerBase {
/**
* {@inheritdoc}
*/
protected function getAvailableRecipes() {
return [
'dripyard_neonbyte_blocks' => [
'machine_name' => 'dripyard_neonbyte_blocks',
'title' => t('Neonbyte Blocks'),
'description' => t('This recipe provides a set of block types based on the single directory components of this theme. These work well with layout builder, but can be used with other page layout modules.'),
'extended_by' => ['dripyard_neonbyte_demo_content', 'dripyard_neonbyte_landing_pages'],
],
....
A cleaner .theme
file in our themes
/**
* Implements hook_form_FORM_ID_alter().
*/
function dripyard_base_form_system_theme_settings_alter(&$form, FormStateInterface $form_state) {
$theme = $form_state->getBuildInfo()['args'][0];
$theme_settings_classes = ClassDiscovery::getAvailableClasses($theme, 'ThemeSettings');
foreach ($theme_settings_classes as $class_name) {
$class = ClassDiscovery::loadClass($theme, 'ThemeSettings', $class_name);
if ($class !== NULL) {
$settingsClass = new $class();
$settingsClass->setTheme($theme);
$settingsClass->themeSettingsFormAlter($form, $form_state);
}
}
}
Inheritable and default theme settings
In this article, we explore the hurdles we faced along the way and how we dealt with them.For Dripyard themes, using Composer is an option, not a requirement. However, we wanted to make use of modern PHP and PHP autoloading to avoid a “spaghetti mess” of hooks and if
statements in large .theme
files. Drupal core has limited support for PSR-4 autoloading in theme directories, and classes aren’t always loaded like you’d expect, especially when just dropping folders in a ./themes
directory. To solve this, we implemented a custom class loader to help with autoloading and to make our DripyardBase
namespace consistently available. We did this by creating a dripyard-classloader.php
that invokes spl_autoload_register
when necessary. There we define our namespaces and perform additional class discovery for other Dripyard themes and sub-themes. We also have an internal discovery class that resolves the inheritance tree of themes that reference ours as a base class
.
See Dripyard’s core-only approach in action
Even after handling these edge cases, it should be noted that our classes are still only available when the theme is in context. For example, these classes aren’t loaded in the admin theme—unless you use Dripyard as your admin theme, which we ensure does work if needed.