Skip to content

fix: Prevent infinite recursion in CommentsEntry::record() closures - #118

Merged
luisdalmolin merged 1 commit into
mainfrom
fix/comments-entry-record-closure-recursion
Sep 4, 2026
Merged

luisdalmolin merged 1 commit into
mainfrom
fix/comments-entry-record-closure-recursion

Conversation

@luisdalmolin

Copy link
Copy Markdown
Member

Problem

Reported in #116: using CommentsEntry::record() with a closure blows the memory limit.

CommentsEntry::make('comments_section')
    ->record(fn (Prerequisite $record) => PrerequisiteSite::where(...)->first())
Allowed memory size of 134217728 bytes exhausted (tried to allocate 48250880 bytes)

Root cause

It's infinite recursion, not a leak.

record() proxied to Filament's model(), which stores the value on the component's $model. BelongsToModel::getRecord() then evaluates $model, and Filament resolves a closure's parameters via Component::resolveDefaultClosureDependencyForEvaluationByName(), where 'record' => [$this->getRecord()].

So getRecord() → evaluate closure → needs $record → getRecord() → … Reproduced locally with 136,000+ stack frames and the exact error from the report.

The trigger is the closure's signature, not its body. It recurses if the parameter is named $record (by-name match) or type-hinted with any Model subclass (by-type match). The reported snippet hits both.

This went unnoticed because the documented form is parameterless (fn () => $pivot) and the existing test passes a plain model instance — both of which work fine.

Fix

Store the override on its own property instead of $this->model, and resolve it in an overridden getRecord() guarded against re-entry. While the override is resolving, a nested getRecord() returns the container record rather than recursing.

The guard covers every injection path rather than just the two known ones, so closures type-hinting an intermediate parent class are handled too. As a side effect, $record inside the closure now resolves to the container (page) record, which is what the reported snippet already assumed.

Tests

New tests/Filament/CommentsEntryRecordTest.php covers:

  • closure type-hinting the record (the reported crash)
  • closure with a $record parameter
  • $record resolving to the container record
  • parameterless closure (documented form)
  • plain model instance
  • no override → container record
  • end-to-end render through the blade view asserting comments belong to the overridden record

The first four fail on main with the reported memory exhaustion; all pass with the fix.

Full suite: 169 passed. Pint clean. PHPStan unchanged — the 7 pre-existing errors in tests/Livewire/CommentReactionTest.php are also present on main.

Not addressed

The same comment reports a second, unrelated issue: no way to remove attachments when editing an existing comment. That's a separate gap — the attachment UI is only included in the new-comment form, and Livewire\Comment::updateComment() has no attachment handling at all, so attachments can be neither added nor removed while editing. Worth its own issue.

https://claude.ai/code/session_01Qy9u3fiaQQJsTyE6PxRdK6

`record()` proxied to Filament's `model()`, which stores the value on the
component's `$model`. `BelongsToModel::getRecord()` evaluates `$model`, and
Filament resolves a closure's `$record` parameter (by name, or by any Model
type-hint) by calling `getRecord()` again — so any closure referencing the
record recursed until memory was exhausted:

    CommentsEntry::make('comments')
        ->record(fn (Prerequisite $record) => $pivot)

    Allowed memory size of 134217728 bytes exhausted

Only the documented parameterless form (`fn () => $pivot`) and plain model
instances worked, which is why this went unnoticed.

The override is now stored on its own property and resolved in an overridden
`getRecord()` guarded against re-entry, so a nested lookup returns the
container record instead of recursing. Closures may now type-hint or name a
`$record` parameter and receive the page record.

Fixes #116

Claude-Session: https://claude.ai/code/session_01Qy9u3fiaQQJsTyE6PxRdK6
@luisdalmolin
luisdalmolin merged commit 250d4a0 into main Sep 4, 2026
20 checks passed
@luisdalmolin
luisdalmolin deleted the fix/comments-entry-record-closure-recursion branch September 4, 2026 19:29
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.

1 participant