✨ Show a reading time next to each post date - #227
Merged
Conversation
Posts advertised only a publication date, so readers had no signal about length before committing to one. The reading time is derived from the rendered body rather than declared in front matter, so there is nothing to keep in sync. BlogPostRepository::parse() already rendered each post in full to read its front matter and threw the HTML away; it now passes that HTML to the factory, so the word count is free and rides the existing index cache. The index cache key gains a version segment: the pool is Redis backed and survives deploys, and the key was derived only from content mtimes, so a deploy that left content/blog/ untouched would have returned BlogPost objects unserialized into the new shape with $readingTime uninitialized.
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.
Blog posts advertised only a publication date, so readers had no signal about how long a post is before committing to it. This adds a "6 min read" indicator next to the date, on the single-post header and on every post card (
/blog, the category and tag archives, and the six-latest section on the homepage — they all sharetemplates/component/Blog/PostCard.html.twig).Where the number comes from
BlogPostRepository::parse()already rendered each post's markdown in full when building the index — it readParsedMarkdown->frontmatterand threw->htmlaway. It now hands that HTML to the factory, so the word count costs nothing extra and rides the existing index cache. No second pass over the filesystem, no new cache entry, noreading_timefront matter field to keep in sync.ReadingTimeCalculator(src/Blog/Domain/Service/) strips tags, decodes entities, splits on/\s+/u, then 200 wpm rounded up with a floor of one minute.preg_splitrather thanstr_word_count— the latter is locale dependent and mangles the accented French catalogue. is decoded before splitting, otherwise it glues its neighbours into a single word.Code blocks are counted like prose. Reading a listing is not faster than reading a paragraph, and special casing
<pre>adds a branch for no clear win — but it is a one-line change in the calculator if you disagree.The cache key bump
index()gains a version segment:blog.index.v2.{locale}.{fingerprint}.blog.cacheis Redis backed and survives deploys, and the key was derived only from content mtimes. Without the bump, a deploy that leftcontent/blog/untouched would have returned entries serialized beforeBlogPostgained$readingTime, unserialized into the new shape with the typed property uninitialized — a fatal on every read of/blog. The constant carries a comment saying it needs bumping wheneverBlogPostchanges shape.Also in the payload
timeRequiredjoins theBlogPostingJSON-LD as an ISO 8601 duration (PT2M), next todatePublished.Verification
51 unit / 90 integration / 26 functional tests green, PHPStan level 8 clean, PHP CS Fixer and Twig CS Fixer clean, Twig/YAML/Doctrine linters clean.
Checked against the one real post in
content/blog/:/blog/why-this-blog-has-no-databaserenders2 min read,/fr/blog/pourquoi-ce-blog-na-pas-de-base-de-donneesrenders2 min de lecture, both with"timeRequired":"PT2M"in the JSON-LD.The test fixtures are one or two sentences each, so they all land on the one-minute floor — the arithmetic itself is pinned in
ReadingTimeCalculatorTest, where the input is controlled.