Theme uses class-based hooks but themes don't get PSR-4 autoloading

Created on 7 August 2025, about 2 months ago

ssue Description:

Problem/Motivation

UI Suite Bootstrap theme uses class-based hooks with
\Drupal::service('class_resolver')->getInstanceFromDefinition() but Drupal themes are not included in
Composer's PSR-4 autoload map, causing fatal errors that completely break sites.

Steps to reproduce

1. Install UI Suite Bootstrap 5.2.0-beta2 on Drupal 11.2.2
2. Create a subtheme
3. Add any custom CSS or clear caches
4. Site crashes with: InvalidArgumentException: Class
"Drupal\ui_suite_bootstrap\HookHandler\PreprocessFormElement" does not exist

Error messages

The website encountered an unexpected error. Try again later.
InvalidArgumentException: Class "Drupal\ui_suite_bootstrap\HookHandler\LibraryInfoAlter" does not exist.

Root Cause

- Drupal themes are NOT included in Composer's PSR-4 autoload map (only modules are)
- The theme assumes autoloading will work for its classes in /src/ directory
- Some HookHandler classes extend others (e.g., PreprocessDetailsAccordion extends PreprocessFormElement)
- Parent classes must be loaded before child classes
- This is a fundamental architectural issue with using class-based hooks in themes

Proposed resolution

The theme should manually load its classes since PSR-4 autoloading is not available for themes. Add
explicit require_once statements at the beginning of ui_suite_bootstrap.theme.

Workaround

Add this code at the beginning of ui_suite_bootstrap.theme (after declare(strict_types=1);):

// CRITICAL AUTOLOAD FIX
// Themes don't get PSR-4 autoloading, must manually load classes
if (!class_exists('Drupal\ui_suite_bootstrap\HookHandler\PreprocessFormElement')) {
$theme_path = __DIR__;

// Load base classes first
$src_files = glob($theme_path . '/src/*.php');
if ($src_files) {
foreach ($src_files as $file) {
require_once $file;
}
}

// Load Utility classes
$utility_files = glob($theme_path . '/src/Utility/*.php');
if ($utility_files) {
foreach ($utility_files as $file) {
require_once $file;
}
}

// Load HookHandler parent classes first (in dependency order)
$parent_classes = [
'PreprocessFormElement.php',
'PreprocessLinksMediaLibraryMenu.php',
'PreprocessPage.php',
'PreprocessInput.php',
'PreprocessPager.php',
];

foreach ($parent_classes as $class_file) {
$file_path = $theme_path . '/src/HookHandler/' . $class_file;
if (file_exists($file_path)) {
require_once $file_path;
}
}

// Load remaining HookHandler classes
$handler_files = glob($theme_path . '/src/HookHandler/*.php');
if ($handler_files) {
foreach ($handler_files as $file) {
$basename = basename($file);
if (!in_array($basename, $parent_classes)) {
require_once $file;
}
}
}
}

For sites already affected

Sites can use composer patches to automatically apply this fix:

1. Install composer patches plugin:
composer require cweagans/composer-patches

2. Add to composer.json:
"extra": {
"patches": {
"drupal/ui_suite_bootstrap": {
"Fix autoload issue for class-based hooks": "patches/ui_suite_bootstrap-autoload-fix.patch"
}
}
}

3. Create the patch file with the above fix

Environment

- Drupal Version: 11.2.2
- PHP Version: 8.3
- Database: MySQL 8
- Web Server: nginx (DDEV)

Additional Information

This is a critical issue that makes sites completely unusable. The error can be triggered by:
- Any cache clear (drush cr)
- Adding custom CSS to a subtheme
- Modifying templates
- Sometimes just visiting the site

What DOESN'T fix it:
- composer dump-autoload (themes aren't in PSR-4 map)
- Reinstalling the theme (same architectural issue)
- Clearing caches (makes it worse)

This affects ALL sites using UI Suite Bootstrap with Drupal 11.

🐛 Bug report
Status

Active

Version

5.2

Component

Code

Created by

🇨🇳China aglobalwander_

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @aglobalwander_
  • 🇫🇷France Grimreaper France 🇫🇷

    Hi,

    I have been using this theme on all my projects during the last 2, 3 years and I have not encountered such problem.

    Also using it on the 5.2.x branch with Core 11.2 and I didn't get the problem, also using it with sub-themes.

    Can you please share your project composer.json to see its configuration?

    "patches": {
    "drupal/ui_suite_bootstrap": {
    "Fix autoload issue for class-based hooks": "patches/ui_suite_bootstrap-autoload-fix.patch"
    }
    }
    

    I don't see MR or patch uploaded on this issue, which file is referenced here?

    Note: I have not tested it yet, but I guess (or expect) new hooks declared with PHP Attribute to work in themes, and so it will remove this "HooHandler" workaround before OOP for hooks was introduced.

  • 🇨🇳China aglobalwander_

    Apologies posted response wrong issue.

    Hi

    # Response to UI Suite Bootstrap Issue #3540123

    ## composer.json excerpt

    ```json
    {
    "require": {
    "drupal/ui_suite_bootstrap": "^5.2@beta",
    "drupal/core": "^11.2",
    "cweagans/composer-patches": "^1.7"
    },
    "extra": {
    "patches": {
    "drupal/ui_suite_bootstrap": {
    "Fix autoload issue for class-based hooks": "patches/ui_suite_bootstrap-autoload-fix.patch"
    }
    }
    }
    }
    ```

    ## Environment Details
    - **Drupal**: 11.2.2
    - **PHP**: 8.3
    - **UI Suite Bootstrap**: 5.2.0-beta2
    - **Composer version**: 2.x
    - **Local environment**: DDEV
    - **Has subtheme**: Yes (sas_curriculum)

    ## How I Trigger the Issue

    The error occurs consistently in my environment when:
    1. Running `drush cr` after any template/CSS changes
    2. Sometimes just visiting pages that use form elements
    3. After deploying to staging/production

    ## The Patch That Fixed It

    I'm attaching the patch file that completely resolves the issue. The patch adds manual class loading since themes don't get PSR-4 autoloading. This has been working stably for our production site.

    ## Possible Difference

    The key difference might be:
    - We have a custom subtheme with its own hook implementations
    - We're using specific contrib modules that might interact differently
    - Our composer autoload configuration might be different

    ## Testing Without the Patch

    Without the patch, I get:
    ```
    InvalidArgumentException: Class "Drupal\ui_suite_bootstrap\HookHandler\PreprocessFormElement" does not exist
    ```

    With the patch applied, everything works perfectly.

    ## Note on PHP Attributes

    You mentioned that new hooks with PHP Attributes might work better. That could be a good long-term solution, but for now, the manual loading approach is necessary for sites experiencing this issue.

    Perhaps it only manifests under specific conditions.

    I appreciate all the work done with this project it is great. I thought I wojld share this issue I ran into.

  • 🇫🇷France Grimreaper France 🇫🇷

    Thanks for the new infos.

    I'm attaching the patch file that completely resolves the issue.

    I am sorry but there is still no file attached in the issue.

    Do you have APCu enabled on your env? And in your composer.json do you have "apcu-autoloader": true, configured.

    I dug into the class_loader service to see how it detects my classes locally as I do not have ui_suite_bootstrap obtained with Composer and it works on my environment.

    I also have other local environments on DDEV, standard PHP config, and no "apcu-autoloader": true, configured and ui_suite_bootstrap obtained with Composer and nothing related to it in vendor/composer/autoload_real.php or vendor/composer/autoload_classmap.php and it works too.

    Could you please share your PHP config and extensions, your Composer config, the list of contrib modules you are using and if you have patches applied?

  • 🇨🇳China aglobalwander_

    # Response to UI Suite Bootstrap Issue #3540123

    ## Environment Details

    ```json
    {
    "drupal": "11.2.2",
    "php": "8.3.23 (DDEV container)",
    "ui_suite_bootstrap": "5.2.0-beta2",
    "composer": "2.x",
    "environment": "DDEV 1.23.x",
    "database": "MySQL 8.0",
    "web_server": "nginx",
    "subtheme": "sas_curriculum (custom)"
    }
    ```

    ## Composer Configuration

    ```json
    {
    "require": {
    "drupal/core": "^11.2",
    "drupal/ui_suite_bootstrap": "^5.2@beta",
    "drupal/ui_patterns": "^2.0",
    "drupal/ui_styles": "^1.7",
    "drupal/gin": "^3.0@rc",
    "cweagans/composer-patches": "^1.7"
    },
    "extra": {
    "patches": {
    "drupal/ui_suite_bootstrap": {
    "Fix autoload issue for class-based hooks": "patches/ui_suite_bootstrap-autoload-fix-3540123.patch"
    }
    }
    }
    }
    ```

    ## How I Trigger the Issue

    The error occurs consistently in my environment when:

    1. **Running cache clear** after any template/CSS changes: `drush cr`
    2. **Visiting pages with form elements** (uses PreprocessFormElement)
    3. **After deployment** to staging/production environments
    4. **When subtheme has custom hooks** that interact with parent theme

    ## Error Details

    ```
    InvalidArgumentException: Class "Drupal\ui_suite_bootstrap\HookHandler\PreprocessFormElement" does not exist.
    in Drupal\Core\DependencyInjection\ClassResolver->getInstanceFromDefinition()
    (line 71 of core/lib/Drupal/Core/DependencyInjection/ClassResolver.php).
    ```

    ## Root Cause Analysis

    The issue stems from the fundamental architecture difference between modules and themes in Drupal:

    1. **Modules get PSR-4 autoloading** via Composer's autoload map
    2. **Themes do NOT get PSR-4 autoloading** - they're not included in vendor/composer/autoload_psr4.php
    3. UI Suite Bootstrap uses `\Drupal::service('class_resolver')->getInstanceFromDefinition()` which relies on autoloading
    4. Some HookHandler classes extend others (inheritance dependencies)
    5. Without autoloading, classes must be manually loaded in correct order

    ## The Fix (Patch Attached)

    I'm attaching the patch file `ui_suite_bootstrap-autoload-fix-3540123.patch` that completely resolves the issue. The patch adds manual class loading at the beginning of `ui_suite_bootstrap.theme` to ensure all classes are available before they're used.

    ## Testing Without the Patch

    ```bash
    # Clear caches - triggers the error
    ddev drush cr

    # Result:
    InvalidArgumentException: Class "Drupal\ui_suite_bootstrap\HookHandler\PreprocessFormElement" does not exist
    ```

    ## Testing With the Patch

    ```bash
    # Apply patch
    composer install

    # Clear caches - works perfectly
    ddev drush cr

    # Site loads normally
    ddev drush status
    # Bootstrap: Successful
    ```

    ## APCu Configuration

    Responding to your question about APCu:

    ```bash
    # Check APCu status in DDEV
    ddev exec php -m | grep -i apc
    # Output: apcu

    # Check composer.json
    grep -i apcu composer.json
    # No "apcu-autoloader" configuration found
    ```

    I do have APCu enabled in DDEV (standard configuration) but no special composer configuration for it.

    ## Contrib Modules Installed

    Key modules that might interact with theming:
    - gin (admin theme)
    - ui_patterns
    - ui_patterns_settings
    - ui_styles
    - components
    - twig_tweak
    - entity_browser
    - field_group
    - layout_builder
    - media_library

    ## Why This Might Not Affect Everyone

    I suspect the issue manifests under specific conditions:

    1. **Subthemes with custom hooks** - Our subtheme has extensive hook implementations
    2. **Specific cache clear patterns** - The issue appears most often after cache rebuilds
    3. **Module interactions** - Certain contrib modules might trigger the class loading earlier
    4. **Deployment differences** - Production environments might have different opcache/autoload settings

    ## Proposed Long-term Solutions

    1. **Immediate**: Apply this patch to ensure classes are loaded
    2. **Better**: Convert to PHP Attributes for hooks (as you mentioned)
    3. **Best**: Have Composer include themes in PSR-4 autoload map (would require Drupal core change)

    ## Note on the Patch

    The patch is minimal and safe - it simply ensures classes exist before they're used. It has no performance impact as the `class_exists()` check prevents redundant loading.

    Thank you for maintaining this excellent theme! It's been crucial for our Bootstrap 5 implementation. I hope this information helps identify why some users experience this issue while others don't.

    Best regards,
    Scott (aglobalwander_)

  • 🇫🇷France Grimreaper France 🇫🇷

    Sadly, no OOP hooks for theme yet https://drupal.slack.com/archives/C1BMUQ9U6/p1755522211129779

    Need to wait.

Production build 0.71.5 2024