Skip to content

Fatal Error: Call to a member function getAttributes() on null when aggregating on orphaned relationship/mock models #4667

Description

@ekumanov

Problem

If a database contains an orphaned reference to a User (e.g. last_posted_user_id points to a deleted user) and a RelationAggregator (like countRelation) is configured for the UserResource, navigating to a page that serializes this orphaned relationship crashes the entire forum with a 500 error:
Error: Call to a member function getAttributes() on null in Illuminate\Database\Eloquent\Collection::loadAggregate.

This is a critical robustness issue in Flarum 2.0's EloquentBuffer.

Root cause

This happens due to a combination of Flarum's JSON:API serializer and the EloquentBuffer:

  1. When JSON:API encounters an un-included relation pointing to an orphaned ID (e.g. a deleted user), it uses a linkage optimization: $related->newInstance()->forceFill(['id' => $spammerId]). This creates a "mock" model where $model->exists = false.
  2. This mock model is passed through the resource serialization pipeline, which triggers UserResource::fields().
  3. If an extension (like fof/upload) adds a countRelation aggregator field to UserResource, the mock model is added to EloquentBuffer::$buffer.
  4. At the end of the request, EloquentBuffer::load() processes the buffer, calling Laravel's $collection->loadAggregate().
  5. loadAggregate() runs a database query using whereKey([orphanedId]). Because the record doesn't exist, it returns empty.
  6. Laravel attempts to map the results back to the original models in the collection. When it asks for the attributes of the orphaned ID, it calls getAttributes() on null, causing a fatal crash.

Reproducer

(This reproduces the issue via fof/anti-spam which leaves orphaned references, but the root cause is in EloquentBuffer)

  1. Enable fof/anti-spam and fof/upload.
  2. As a normal user, create a discussion with a post.
  3. As an admin, go to that user's profile and use "Mark as Spammer". Select Delete posts and Delete user (but leave Delete discussions unchecked).
    (Note: fof/anti-spam bulk-deletes posts, leaving last_posted_user_id on the discussion pointing to the deleted user).
  4. Navigate to the forum homepage (or any route that lists that discussion) as a Guest.
  5. The request immediately crashes with Call to a member function getAttributes() on null.

Suggested fix

Patch Flarum\Api\Resource\EloquentBuffer::load() to filter out models that do not actually exist in the database before passing them to relations or aggregators.

// vendor/flarum/core/src/Api/Resource/EloquentBuffer.php ~ line 117
-        $collection = $model->newCollection($models);
+        $collection = $model->newCollection($models)->filter(function ($m) {
+            return $m->exists && $m->getKey() !== null;
+        });
+
+        if ($collection->isEmpty()) {
+            return;
+        }

This ensures loadAggregate only attempts to query and map valid models, making Flarum's API resilient to orphaned database records.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions