Problem/Motivation
`TrashStorageTrait::buildQuery()` is already workspace-aware — it joins
`workspace_association`, repoints the `revision` join at the workspace revision,
and filters `deleted IS NULL`. On a cold cache, loading an entity trashed inside
the active workspace correctly returns nothing.
With a warm `cache.entity` it does not, because the load path never reaches
`buildQuery()`:
```
getFromStaticCache() entity.memory_cache
getFromStorage()
→ preLoad() hook_entity_preload()
→ getFromPersistentCache() cache.entity ← answers here
→ doLoadMultiple() → buildQuery() ← trash's workspace filter
```
`WorkspaceProviderBase::entityPreload()` normally claims every tracked ID and
`preLoad()` removes it from `$ids`, so the workspace-agnostic cache is never
consulted. Trash breaks that: the tracked revision is soft-deleted, so
`buildQuery()` filters it out of the `loadMultipleRevisions()` call preload makes,
the ID goes unclaimed, and `cache.entity` returns the **Live default revision** —
not deleted, still published, so it renders while the workspace is active.
`TrashCacheBackendTrait` can't catch this: it filters on write via
`Trash::entityIsDeleted($data)`, but in a workspace the deletion lives in a
pending revision while the cached Live default is not deleted.
Affects every entity type that is trash-enabled and workspace-supported. Entity
queries are fine (they use Trash's decorated query factory), so it surfaces on
reference-based loads — a referenced media item, node or block_content that keeps
rendering after being trashed in the workspace.
Steps to reproduce
With Trash and Workspaces enabled, and Trash enabled for `media` (any
trash-enabled, workspace-supported entity type reproduces it):
1. In Live, create an Image media item and a page that renders it through an
entity reference field (not a view).
2. View the page in Live so the media entity lands in `cache.entity`.
3. Switch to workspace A. Delete the media item.
4. View the page again in workspace A.
**Expected:** the media does not resolve, so nothing is rendered for it — the
behaviour you get on a cold cache.
**Actual:** the media still resolves and still renders. Flushing caches produces
the expected result until the entity is loaded in Live again.
Same reproduction with an entity reference to a trashed node or block_content.
Proposed resolution
Override `getFromPersistentCache()` in `TrashStorageTrait`, beside the
`buildQuery()` it protects: skip the cache for IDs tracked in the active
workspace, then return them to `$ids` so they resolve through
`doLoadMultiple()`/`buildQuery()`.
The lookup is exact rather than heuristic — `preLoad()` has already removed every
ID whose workspace revision *was* returned, so any surviving tracked ID is one
Trash filtered out. Guards keep it off the common paths: non-empty `$ids`, trash
context `active`, Workspaces installed, entity type supported, non-default
workspace active. `loadUnchanged()` is fixed by the same override.
Cost is one indexed `workspace_association` lookup per multi-load inside a
workspace — the same query Workspaces already runs for the same IDs in
`entityPreload()`. If maintainers prefer, Trash could instead record the IDs it
filtered during preload and consult that in-request list, avoiding the query
entirely; that's harder to wire up (only revision IDs are known at the filtering
point), so the simpler version is proposed first.
Issue fork trash-3616230
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
Comment #4
paranojik commentedComment #5
alecsmrekar commentedComment #6
amateescu commentedReviewed, and the first point requires a new approach for this fix.
Comment #9
amateescu commentedMerged into 3.1.x and cherry-picked to 3.x, thanks!