Skip to content

Defer innerHTML serialization in the "Grabbed" debug log#46

Merged
fivefilters merged 1 commit into
claude/readability-php-modernize-ii46mkfrom
claude/github-fork-php-logging-20xm0e
Jul 15, 2026
Merged

Defer innerHTML serialization in the "Grabbed" debug log#46
fivefilters merged 1 commit into
claude/readability-php-modernize-ii46mkfrom
claude/github-fork-php-logging-20xm0e

Conversation

@fivefilters

Copy link
Copy Markdown
Owner

What

grabArticle() logged the grabbed article with:

$this->log('Grabbed: ' . $articleContent->innerHTML);

PHP evaluates function arguments eagerly, so innerHTML serialized the entire article subtree into a string on every parse — even when no logger was configured and debug was off. log()'s early return then discarded it. The serialization work (and a large transient allocation) happened before log() was ever entered.

This changes the call site to pass a closure, and teaches log()'s formatter to resolve any \Closure argument — but only after the enabled check that guards the method:

$this->log('Grabbed:', fn (): string => $articleContent->innerHTML);

When logging is off, the closure is never invoked and innerHTML is never built. When logging is on, the emitted message is byte-for-byte identical to before.

Why a closure, not a change to the element formatter

The formatter's \Dom\Element branch is shared by ~12 other call sites (Candidate:, Looking at sibling node:, setNodeTag, Cleaning Conditionally, …), many inside hot per-node loops. They rely on the compact <div class="..."> representation. Emitting innerHTML for all elements would flood those logs with unreadable full-subtree dumps. Deferring via a closure scopes the change to this one line and leaves every other element log untouched. The formatter now also resolves closures generically, so other sites can defer expensive values the same way.

Testing

Verified with a standalone script (composer/phpunit unavailable in this environment):

  • Behavior unchanged: a real parse() with a PSR-3 logger emits exactly one Grabbed: message containing the serialized innerHTML.
  • Deferral works: reproducing log()'s structure with a counting closure, the closure is invoked 0 times when logging is off and once when a logger is present.

php -l passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BXHSUavFZ8SkC5kX6j59BV


Generated by Claude Code

The log() call in grabArticle() concatenated $articleContent->innerHTML
into the message unconditionally. Because PHP evaluates arguments eagerly,
the full article subtree was serialized on every parse even when no logger
was configured and debug was off, then discarded.

Pass a closure instead and resolve it inside log()'s formatter, which runs
only after the enabled check. When logging is off the innerHTML is never
built; when it is on the output is unchanged. The formatter now resolves
any Closure argument first, so other call sites can defer expensive values
the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXHSUavFZ8SkC5kX6j59BV
@fivefilters fivefilters changed the base branch from master to claude/readability-php-modernize-ii46mk July 15, 2026 13:12
@fivefilters fivefilters marked this pull request as ready for review July 15, 2026 13:13
@fivefilters fivefilters merged commit 0d6ee28 into claude/readability-php-modernize-ii46mk Jul 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants