Repository navigation
Description
Bug description
Collection::save() doesn't clear the collection that CollectionStructure caches in Blink. After a collection is replaced by a new instance with different sites, its structure keeps answering from the old instance for the rest of the request, so structure()->in($site) returns null for a site the collection is now in.
CollectionStructure::collection() caches under this key:
return Blink::once('collection-structure-collection-'.$this->handle(), function () {
return Collection::findByHandle($this->handle());
});Collection::save() clears by prefix:
Blink::forget('collection-handles');
Blink::forget('mounted-collections');
Blink::flushStartingWith("collection-{$this->id()}");collection-structure-collection-foo doesn't start with collection-foo, so the cached copy survives the save. existsIn() reads that copy, and in() only makes a tree when existsIn() is true, so it returns null. Callers chain straight onto it. For example, Entry::page() does $this->structure()->in($this->locale())->find($id) and fails with Call to a member function find() on null.
Collection::delete() has the same gap. It only clears mounted-collections, so the cached copy also survives a delete. Re-creating the collection goes through save(), so fixing save() also fixes delete-then-recreate.
How to reproduce
This test fails on 6.x (440eaf2):
#[Test]
public function structure_resolves_a_tree_after_the_collection_is_resaved_on_another_site()
{
$this->setSites([
'en' => ['url' => 'http://domain.com/'],
'fr' => ['url' => 'http://domain.com/fr/'],
]);
Collection::make('foo')->sites(['en'])->structureContents(['root' => false])->save();
Collection::findByHandle('foo')->structure()->in('en'); // caches the sites:[en] instance
Collection::make('foo')->sites(['fr'])->structureContents(['root' => false])->save();
$this->assertNotNull(Collection::findByHandle('foo')->structure()->in('fr')); // null
}It only fails when the saved object is a different instance from the cached one. If the same object is changed and saved again, the cached copy is that object and shows the new sites. In practice a different instance comes from anything that builds the collection fresh, such as an import or a test fixture.
Adding Blink::forget("collection-structure-collection-{$this->id()}"); to the cleanup block in save() makes the test pass. A fix can follow as a PR.
This is related to #5046, but it's a different key: that one is Tree's cached data, this one is the structure's cached collection.
Environment
Reproduced on Statamic 6.x at 440eaf2, in Statamic's own test suite with PreventSavingStacheItemsToDisk. v6.26.0 has the same code in both methods.
Activity