Repository navigation
[6.x] Work out an entry's original state only when it's asked for - #15842
Open
DanielDarrenJones wants to merge 1 commit into
Open
DanielDarrenJones wants to merge 1 commit into
DanielDarrenJones wants to merge 1 commit into
Conversation
DanielDarrenJones
marked this pull request as ready for review
November 10, 2026 21:11
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BasicStore::getItem()callssyncOriginal()on every entry it reads from the Stache, so thatisDirty()andgetOriginal()can answer later. For an entry that builds an array of its collection, locale, origin, slug, date, published state, path and all its data, on every read. Almost no entry read on a request is ever asked.Entry::syncOriginal()now keeps a copy of the entry as it is at that moment (clone $this, which already copies its data and supplements, plus its own copy of the date), and the attributes are taken from that copy the first timegetOriginal(),isDirty()or__sleep()needs them. What they report is unchanged.This is
Entryonly.Collection,Term,Tree,UserandAssetkeep working their state out straight away.Details
origin()is remembered in Blink by the entry's id, so the copy would be handed the entry's current origin. While the snapshot is worked out, the copy is shown the origin it was synced with, and what was remembered is put back afterwards.the_descendants_cache_is_cleared_for_the_previous_origin_when_the_origin_changescovers this path and fails without it.origin(whether it has one) anddate(inherited when the entry has none of its own). They used to be read from the origin when the entry was synced, and are now read when the snapshot is first asked for. So if, in the same request, an entry is read, then its origin's date is changed or the origin is deleted, and only after that is the entry asked forgetOriginal()orisDirty(), those two attributes show the origin as it is by then. I haven't found anything in core that does things in that order, and the test suite passes, but I haven't proved nothing does. Capturing them at sync time would mean looking the origin up for every localised entry that's read, which is most of what this saves on a multisite.__sleep()works the snapshot out first and leaves the copy out, so a serialized entry is what it was before.getCurrentDirtyStateAttributes()is still what produces the snapshot; it's called on the copy. The Eloquent driver'sEntry::fromModel()ends withsyncOriginal(), so it gets the same saving.Benchmark
Reading entries through
Entry::query()->where('collection', ...)->get(), with and without this change, run alternately (Apple M1 Pro, PHP 8.5).A small site (single site, 100 to 319 entries per collection, Statamic 6.40; median of 5 processes):
That's 10 to 15 µs per entry: 10 to 25% of the time spent reading entries, but under 1% of a page that takes 50 to 100 ms. Whole page loads on this site are within noise.
A large multisite (9,000 entries, 3,000 of them translations, nested page builder, Statamic
6.x; median of 6 alternating runs):The ranges don't overlap (151 to 160 µs against 79 to 96 µs). The saving per entry is larger here because the snapshot costs more for translated, dated, larger entries: it looks up the origin, formats the date and copies more data. A cold
stache:warmon this site showed no clear change (29.4 s against 28.1 s, with overlapping ranges).So this is a steady saving on every entry read. It doesn't show on a small site, and it adds up where a request reads hundreds or thousands of entries (large listings and navs, sitemaps, feeds, exports), most of all on multisites.
Tests
Four new tests in
EntryTest:syncOriginal(), andgetOriginal()takes it from the copy;set(),data()->put(),slug(),published()and a date modified in place;The full suite passes on PHP 8.4 (11,611 passed, 12 skipped).