Problem/Motivation

The following error sometimes occurs when someone visits the website during a deploy of new code, probably during a cache rebuild:

Compile Error: Cannot declare class Drupal__Core__Entity__Sql__SqlContentEntityStorageTrash665ed930f1d2d, because the name is already in use

Steps to reproduce

This one is hard to reproduce consistently, it seems to only happen when someone visits the site at a very specific point during cache rebuilds.

Proposed resolution

None yet.

Issue fork trash-3452414

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

DieterHolvoet created an issue. See original summary.

dieterholvoet’s picture

Maybe it could help to get rid of TrashEntityTypeManager::clearCachedDefinitions()? Is there any reason these files need to be regenerated on every cache rebuild? If at some point we update the template that's used to generate these files, we could regenerate existing classes once in an update hook.

I see now that the template uses original_class, which may change at some point.

dieterholvoet’s picture

You would think that regenerating the class suffix when deleting existing classes would prevent this issue. Maybe this only happens when someone visits the site between PhpStorageFactory::get('trash')->deleteAll(); and \Drupal::state()->delete('trash.class_suffix');? I'll switch these two lines and see if the issue still happens.

amateescu’s picture

StatusFileSize
new1012 bytes

FWIW, I added some `sleep()` calls in that code, executed drush cr and drush updb -y from the CLI, then browsed the site while the commands were still running, and I didn't bump into that error.

However, if you find that the MR makes a difference in your case, I've no problem with merging it :)

dieterholvoet’s picture

No it doesn't, the issue happened again since adding the patch.

socialnicheguru’s picture

Status: Active » Needs review
amateescu’s picture

Status: Needs review » Postponed (maintainer needs more info)

@socialnicheguru, there's nothing to review here, the patch in #5 is just meant for debugging, and the MR doesn't contain any commits (anymore).

Did you bump into this problem as well?

socialnicheguru’s picture

Sorry I was looking at this link and thought there was something to review: https://git.drupalcode.org/project/trash/-/merge_requests/25/diffs?commi...

amateescu’s picture

Status: Postponed (maintainer needs more info) » Closed (cannot reproduce)

I think that commit only reverts the initial commit from the MR.

I'm going to close this issue. @DieterHolvoet, please feel free to re-open it if you managed to get more info about this.

dieterholvoet’s picture

FYI this error is still appearing in logs every now and then:

Compile Error: Cannot redeclare class Drupal__Core__Entity__Sql__SqlContentEntityStorageTrash6936a8faddf9d (previously declared in /var/web/vd32004/deploy/shared/web/sites/default/files/php/trash/Drupal__Core__Entity__Sql__SqlContentEntityStorageTrash6936a8faddf9d/s4DjUG_7nC2Ip3vqkmzrN1DPbOOWBzHxqPUcTEx5QyE.php:8)

I also saw it happening once myself, when loading a page while config was being imported. I'm going to try to add this to settings.php, hopefully that fixes it:

$settings['php_storage']['trash']['class'] = \Drupal\Component\PhpStorage\FileStorage::class;

That should make sure that classes with the same class suffix are always stored in the same file. The MTimeProtectedFileStorage protects against "rogue" upload scripts that take filenames from $_GET, so it shouldn't be necessary in this case.

tylertech-lee-hazlett’s picture

drush updb can cause it too. I'm getting it on ~25 of 100 sites when running that command

2026-01-15T15:08:33.1728048Z   Unable to decode output into JSON: Syntax error                              
2026-01-15T15:08:33.1729094Z                                                                                
2026-01-15T15:08:33.1730175Z   Fatal error: Cannot redeclare class Drupal__node__NodeStorageTrash696902ec8  
2026-01-15T15:08:33.1731416Z   a9fe (previously declared in /files/php/trash/Drupal__node__NodeStorageTras  
2026-01-15T15:08:33.1732680Z   h696902ec8a9fe/SWGc4J0Q_JFEySFJ8kZuyJFBKDNDHy1jTGfvzzLX2Oc.php:8) in /files  
2026-01-15T15:08:33.1733954Z   /php/trash/Drupal__node__NodeStorageTrash696902ec8a9fe/NAgMAKMfBvCxcPrxUxdg  
2026-01-15T15:08:33.1735220Z   0qvzCj9E2Vdw5h_nNTE6jLw.php on line 8                                        
dieterholvoet’s picture

Status: Closed (cannot reproduce) » Active
amateescu’s picture

Version: 3.x-dev » 3.1.x-dev

amateescu’s picture

Status: Active » Needs review

Started a new MR with an approach I wanted to try for a long time. It's quite a big change, but it should be much more reliable than the current way we swap storage handlers.

the_g_bomb’s picture

I ran into this same issue during a updatedb as well.

Unable to decode output into JSON: Syntax error
Fatal error: Cannot declare class Drupal__node__NodeStorageTrash69453109e6dee, because the name is already in use in /files/php/trash/Drupal__node__NodeStorageTrash69453109e6dee/J6qrJa6NuNd6zClRjR1f2_YxhXXcdkgwH5AFRDiXjn8.php on line 8

It seemed to happen after a module ran a cache-clear or rebuilt the NodeStorage as a part of its update hook

I developed a workaround to add to the deployment steps, which was to run:

drush php:eval "\Drupal::service('file_system')->deleteRecursive('sites/default/files/php/trash');"')

I tried to debug locally and struggled, so I asked AI for help, this is how it described the problem:

Root Cause                                                                                                                                                                                                                 
The MTimeProtectedFileStorage generates file paths that include a hash based on the directory's mtime (line 135 in MTimeProtectedFastFileStorage.php):

return $directory . '/' . Crypt::hmacBase64($name, $this->secret . $directory_mtime) . '.php';                                                                                                                             

The Problem Flow:                                                                                                                                                                                                          

1. Bootstrap: File exists at path files/php/trash/ClassName/HASH_A.php → include_once loads class                                                                                                                          
2. ConfigEntityUpdater triggers entity rebuild                                                                                                                                                                             
3. useCaches(FALSE) or clearCachedDefinitions() deletes the PHP storage files                                                                                                                                              
4. Storage requested again → _trash_generate_storage_class() called                                                                                                                                                        
5. New suffix generated (or same suffix - doesn't matter)                                                                                                                                                                  
6. File doesn't exist → regenerated with new directory mtime → stored at files/php/trash/ClassName/HASH_B.php                                                                                                              
7. $php_storage->load() calls include_once on HASH_B.php                                                                                                                                                                   
8. PHP sees this as a different file (different path!) and includes it                                                                                                                                                     
9. Class declaration executes → FATAL: Class already in use                                                                                                                                                                

The include_once only prevents re-including the same file path, not the same class name.                                                                                                                                   

The Fix                                                                                                                                                                                                                    
In trash.module line 229, add a class_exists() check before calling load():                                                                                                                                                
// Current code (buggy):                                                                                                                                                                                                   
if ($php_storage->load($trash_class . '.php')) {                                                                                                                                                                           
  return $trash_class;                                                                                                                                                                                                     
}                                                                                                                                                                                                                          
// Fixed code:                                                                                                                                                                                                             
if (class_exists($trash_class) || $php_storage->load($trash_class . '.php')) {                                                                                                                                             
  return $trash_class;                                                                                                                                                                                                     
}                                                                                                                                                                                                                          

This prevents attempting to redeclare a class that's already loaded in PHP memory.                                                                                                                                         

My issue with testing the solution is that the issue seems intermittent, so I can't determine if I am lucky and the update hasn't broken it, or if the fix suggested is working.

amateescu’s picture

Title: Compile Error: Cannot declare class Drupal__Core__Entity__Sql__SqlContentEntityStorageTrash665ed930f1d2d, because the name is already in use » Decorate storage handlers instead of generating PHP code for them
Priority: Normal » Major

After splitting out #3569052: Move TrashStorageTrait::buildQuery to a hook implementation and #3569155: Refactor TrashStorageTrait::setPersistentCache to handle the protected methods we were overriding from SqlContentEntityStorage, the refactoring MR is green!

Even though I trust the test coverage that we currently have, I'd appreciate any real-world testing for it :)

twod made their first commit to this issue’s fork.

twod’s picture

Status: Needs review » Needs work

The comment on MemoryBackend::reset() says it's only called during tests, but this is false. If you go to /admin/reports/status it gets called via EntityDefinitionUpdateManager when it generates its changelist.
It just assumes the method is there. :(
(Related issue discussing not using MemoryBackend as it's designed for tests: #2973286: Clean up MemoryCache and MemoryBackend naming issues.)

After fixing that I stumbled upon a more fundamental problem with this approach. A general decorator is neat, but it can't easily implement all the expected interfaces for all storages. Just going to /node/1/revisions shows NodeController::getRevisionIds() expects NodeStorageInterface but got the TrashEntityStorageDecorator instead.

I'm not sure how we could get around that without generating adapter classes.

amateescu’s picture

Status: Needs work » Needs review

Woah.. it's kinda embarrassing that I missed that aspect :)

Ok, so a general decorator won't work, but friendsofphp/proxy-manager-lts seems to do the job quite well!

  • amateescu committed 3ff81a5c on 3.1.x
    task: #3452414 preparation: Add test coverage around storage class and...
terry_kolodiy’s picture

StatusFileSize
new113.03 KB

Patch from MR:

amateescu’s picture

@terry_kolodiy, that patch looks like an old revision of the MR, which had various problems. You should try to use the latest code from both 3.1.x and the MR.

amateescu’s picture

Title: Decorate storage handlers instead of generating PHP code for them » Refactor storage class generation

Even though I liked the "cleanliness" of the proxy manager approach, I equally dislike bringing in a big dependency like that.

So here's a new MR which just refactors the storage class generation to hopefully handle all possible cache clearing problems and edge cases around multiple web heads.

  • amateescu committed 44b0b17f on 3.1.x
    feat: #3452414 Refactor storage class generation
    
    By: amateescu
    By:...
amateescu’s picture

Status: Needs review » Fixed

Merged into 3.1.x!

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

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

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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

the_g_bomb’s picture

Just had another update hook do a cache clear during a deployment and got caught with this.
Does this need to be backported to 3.x? If I update to the latest stable (3.0.27), will this be solved?
For some reason, I am still on 3.0.18. I'll look into why violinist isn't doing its thing.

amateescu’s picture

@the_g_bomb, there were a few other changes related to this refactor that were done outside this issue, and it would be hard to track down each of them.

But you can safely update to 3.1.x-dev though if you're on Drupal 11.3. This work is the only major change between the 3.x and 3.1.x branches at the moment, so that branch is not in an "unstable" state by any means :) It will start getting official releases when #3376216: Translation support gets done.