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.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

aglobalwander_ created an issue. See original summary.

grimreaper’s picture

Version: 5.2.0-beta2 » 5.2.x-dev
Category: Bug report » Support request
Priority: Critical » Normal
Status: Active » Postponed (maintainer needs more info)

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.

grimreaper’s picture

aglobalwander_’s picture

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.

grimreaper’s picture

StatusFileSize
new192.65 KB

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?

aglobalwander_’s picture

# 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

any insight on what to check here would be helpful too.

## 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

## 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_)

grimreaper’s picture

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

Need to wait.

herved’s picture

Hi, just to report that we're also facing this issue with DDEV. I think the issue is valid, but I haven’t yet investigated what’s going on exactly.

Indeed, themes don’t get PSR-4 autoloading, but these classes are invoked using ClassResolver::getInstanceFromDefinition, so should get loaded if not already?
Also, I wonder if this is specific to APCu — does disabling APCu resolve it? Just to confirm.

I don't know much about autoloading but maybe registering the namespace in composer autoload might be a better workaround for now?
I'll do some testing/investigation and update.

herved’s picture

Update: registering the namespace manually in composer.json does seem to work.
Still a workaround of course, but better than the proposed patch.

"autoload": {
    "psr-4": {
        "Drupal\\ui_suite_bootstrap\\": "web/themes/contrib/ui_suite_bootstrap/src",
        "Drupal\\my_subtheme\\": "web/themes/custom/my_subtheme/src"
    }
},

The one for my_subtheme is only needed if you implement hook classes similar to ui_suite_bootstrap obviously.
Make sure to composer dump-autoload after that.

grimreaper’s picture

Assigned: Unassigned » grimreaper
Status: Postponed (maintainer needs more info) » Active

Thanks @herved for the investigation.

Good idea the autoload in the composer.json! (this reminds me of a bug in a custom project (with normal layout plugins as PHP in a theme as it can be registered in theme))

I will try to propose an MR next week or you can if you want.

And it is strange because I have projects using DDEV and ui_suite_bootstrap and didn't get the bug.

herved’s picture

So far I was not able to reproduce on a clean ui_suite_bootstrap ddev setup, sadly.
But it looks like some race condition.
I could only reproduce it on my project and it happens intermittently. This seems to involve the main request + aggregated assets (CSS/JS) requests (needs aggregation enabled).

When that happens, \Composer\Autoload\ClassLoader::findFile gets called on $class Drupal\ui_suite_bootstrap\HookHandler\LibraryInfoAlter while somehow themes are not yet psr4-registered ($this->prefixDirsPsr4 doesn't contain any themes namespaces), so findFileWithExtension returns false and gets stored in apcu_add(). At this point, the only way to recover is to restart ddev or the web server which I assume clears the apcu cache.
Here is a stacktrace and method I used: https://gist.github.com/vever001/009e4163eb6d27cab26a2b5a0df41a62
That request is a CSS aggregated URI (/sites/default/files/css/...).

I wonder precisely why they are not psr4-registered yet in such cases, because IIUC this should happen earlier in the request, in ThemeHandler::addTheme called very early on KernelEvents::REQUEST.

Update: It appears ThemeHandler::addTheme isn't called in such cases, which explains why themes aren't psr4-registered. Why it's not called is yet to be determined...

herved’s picture

I managed to reproduce on a clean setup! though using xdebug to manipulate the execution order.

- Install clean ui_suite_bootstrap with ddev (enable modules + theme)
- Load 1 tab with homepage and another tab with the first aggregated CSS URL on the homepage (sites/default/files/css/...)
- ddev drush cr && ddev xdebug on (clears cache, starts xdebug and clears APCu cache)
- Place a breakpoint on first line in \Composer\Autoload\ClassLoader::findFile with condition: $class === 'Drupal\ui_suite_bootstrap\HookHandler\LibraryInfoAlter'
- Now run 2 concurrent requests:
-- First load the homepage, let it hit the breakpoint and pause.
-- Then load the CSS URL page, let it hit the breakpoint as well, step over a bit and notice that it cannot find the class and will store false in apcu_add() then let it resume.
- At this point the apcu cache is corrupted. All pages are throwing

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

The only way to recover is to clear APCu (e.g. via ddev restart or ddev xdebug toggle)

Maybe there are easier ways to trigger this, but this is so far one reliable way I found.
I didn't dig much further but I suggest to just register the namespace in composer as per #9.
Also, subthemes (and so starterkits) may have to do the same if they also use the same "class-based hooks".

grimreaper’s picture

Category: Support request » Bug report

Thanks @herved for your investigation.

Need also to document it.

grimreaper’s picture

Status: Active » Needs review

If you can give a quick review before merge that will be great! :)

herved’s picture

Left a small non-blocking comment.
Works perfectly on my side — Many thanks for the excellent support, and thanks to the entire team behind the UI initiative!

  • grimreaper committed 2fecfb2b on 5.2.x
    [#3540123] chore: Fix code quality after PHPStan update.
    
    By: grimreaper
    

  • grimreaper committed f448d7a8 on 5.2.x
    [#3540123] fix: Theme uses class-based hooks but themes don't get PSR-4...
grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Needs review » Fixed

Merged.

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

grimreaper’s picture

@aglobalwander_ , @herved

If no more autoload problem with the current dev version, I will do a release.

Can you both confirm that please?

herved’s picture

I'm currently on 5.2.0-beta3 with the fix from #9 in my main composer.json, no more issues since then.
But I tested the fix on current dev with procedure from #12 and can confirm it does solve the issue.
So +1 for me.

grimreaper’s picture

Thanks! Waiting for @aglobalwander_ feedback as well.

grimreaper’s picture

Fun fact....

While testing #3373929: CSS and JS returns 404 when enabling css and js aggregation on nginx on ddev to have a quick nginx setup, I reproduced the issue...

InvalidArgumentException: Class "Drupal\ui_suite_bootstrap\HookHandler\LibraryInfoAlter" does not exist. in Drupal\Core\DependencyInjection\ClassResolver->getInstanceFromDefinition() (line 32 of /var/www/html/app/core/lib/Drupal/Core/DependencyInjection/ClassResolver.php).

https://sobki-platform-demo-bootstrap.ddev.site/sites/default/files/css/...

And so confirm that also need to add autoload in Sobki Bootstrap and that I will do a release because I witness the fix too!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.