Skip to content

[2.x] fix: TypeError in Revised event when post content is NULL#4648

Merged
imorland merged 1 commit into
2.xfrom
im/revised-event-null-content
May 9, 2026
Merged

[2.x] fix: TypeError in Revised event when post content is NULL#4648
imorland merged 1 commit into
2.xfrom
im/revised-event-null-content

Conversation

@imorland

@imorland imorland commented May 9, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #4606. `CommentPost::revise()` raises a `Flarum\Post\Event\Revised` whose constructor types `$oldContent` as non-nullable `string`. But the `posts.content` column is nullable — legacy/imported posts and extension-created posts can have `NULL` content. Editing such a post throws a `TypeError` on every save and pollutes the log.

Root cause

```php
// CommentPost::revise()
$oldContent = $this->content; // ← can be null
$this->raise(new Revised($this, $actor, $oldContent));
```

```php
// Revised::__construct
public string $oldContent // ← rejects null
```

```
TypeError: Flarum\Post\Event\Revised::__construct(): Argument #3 ($oldContent) must be of type string, null given
```

Fix

Coalesce at the call site:

```php
$oldContent = $this->content ?? '';
```

Chosen over widening `Revised::$oldContent` to `?string` because:

  1. RC-friendly — no public-API change. Any extension that does `strlen($event->oldContent)` or similar without a null check keeps working.
  2. Semantic loss is negligible — distinguishing "was empty string" from "was null" is meaningful in principle, but no listener in core or any bundled extension actually uses that distinction; `grep oldContent framework/ extensions/` finds zero consumers.
  3. Future-friendly — if a real need emerges to widen the type later, that's an additive change. Going `?string` now would commit us to that signature.

Tests

New `framework/core/tests/integration/api/posts/UpdateTest.php` (the file didn't exist — there was no integration test for post updates):

  1. `revising_a_post_with_null_content_does_not_throw` — calls `revise()` on a post whose `content` is null and asserts no exception, content updated correctly.
  2. `revising_a_post_with_null_content_raises_revised_event` — asserts the event is still raised and `oldContent` is `''` (not skipped).

Both tests fail against pre-fix code with the exact `TypeError` from the bug report; both pass after the fix.

CommentPost::revise() forwards $this->content as $oldContent to the
Revised event, but the column is nullable while Revised's constructor
types $oldContent as non-nullable string. Editing a post that had
NULL content (legacy imports, extension-created posts) throws a
TypeError on every save and pollutes the log.

Coalesce at the call site rather than widening Revised::$oldContent
to ?string. Listeners that read the property still get a string —
fully backward-compatible for any extension that does string ops on
it without a null check.

Add a regression test in framework/core/tests/integration/api/posts/UpdateTest.php
(file did not exist before — there was no UpdateTest for posts).

Fixes #4606.
@imorland imorland requested a review from a team as a code owner May 9, 2026 06:08
@imorland imorland added this to the 2.0.0-rc.2 milestone May 9, 2026
@imorland imorland merged commit 9b9ad46 into 2.x May 9, 2026
25 checks passed
@imorland imorland deleted the im/revised-event-null-content branch May 9, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError in Revised event when post content is NULL: $oldContent must be of type string, null given

1 participant