Skip to content

[3.0] Avoid the deprecated by-reference aliases in saveBatch() - #9314

Open
albertlast wants to merge 2 commits into
SimpleMachines:release-3.0from
albertlast:fix/savebatch-deprecated-alias-props
Open

[3.0] Avoid the deprecated by-reference aliases in saveBatch()#9314
albertlast wants to merge 2 commits into
SimpleMachines:release-3.0from
albertlast:fix/savebatch-deprecated-alias-props

Conversation

@albertlast

Copy link
Copy Markdown
Collaborator

Note

This change was produced by an LLM. The code, the commit message and this
description were all written by Claude (Anthropic), driven by @albertlast. It has
not yet had human code review.

Everything stated below was verified by actually running it against a fresh
PostgreSQL install of this branch, rather than only reasoned about. Even so,
please review it as untrusted work: the diagnosis may be right while the fix is
not what SMF would prefer stylistically or architecturally.

Description

User::saveBatch() maps table columns onto property names, but had no entries for
member_name, real_name, email_address or usertitle, so all four fell through
to default => $column and addressed the deprecated backward compatibility aliases
rather than the canonical properties.

Those aliases are by-reference property hooks:

public string $member_name {
    &get => $this->username;
}

isset() on an uninitialized typed property normally returns false, but a
by-reference hook has to materialise a reference first, so it throws instead:

Error: Cannot access uninitialized non-nullable property SMF\User::$username
by reference in Sources/User.php:808

Two call paths reach it:

  • UpdateSpoofDetectorNames loads members with UserDataset::None and sets only
    $name, so $username is never initialised — the task threw on every run.
  • More seriously, an ordinary page load for a logged in member, via
    User::loadMe()setLastVisit()save()saveBatch().

Combined with the error handler re-entering on the same object (see the separate PR
for that), the second path produced an uncaught fatal on page load.

Of the 24 aliases declared with a &get hook, these four are the only ones that are
members columns and were not already handled — either by their own case in the
switch (id_member, id_post_group, id_group, lngfile, website_url,
website_title) or by an existing entry in this match. The mapping should now be
exhaustive for the columns saveBatch() can see.

Why not give the properties default values

Defaulting $username/$email would make isset() return true and cause
saveBatch() to write empty strings over every member's username and email on a
batch update. Skipping uninitialized properties is the correct behaviour.

How this was verified

A regression test driving the exact failing condition — a member loaded with
UserDataset::None, handed to saveBatch() — run against both branches:

release-3.0 with this PR
saveBatch() throws OK
Existing data intact yes yes

The comparison matters: it shows the test genuinely detects the bug rather than
passing vacuously. The second row confirms the concern above — the admin's stored
email survived untouched.

Also confirmed UpdateSpoofDetectorNames now completes and writes a correct skeleton
(adminadrnin), with zero errors logged.

Issues References (Fixes|Related|Closes)

  1. No existing issue found for this.

albertlast and others added 2 commits July 28, 2026 23:22
User::saveBatch() maps table columns onto property names, but had no
entry for member_name or real_name, so both fell through to the default
and addressed the deprecated backward compatibility aliases instead of
the canonical $username and $name.

Those aliases are by-reference property hooks:

    public string $member_name {
        &get => $this->username;
    }

isset() on an uninitialized typed property normally returns false, but
a by-reference hook has to materialise a reference first, so it throws
instead:

  Error: Cannot access uninitialized non-nullable property
  SMF\User::$username by reference in Sources/User.php:808

UpdateSpoofDetectorNames loads members with UserDataset::None and sets
only $name, so $username is never initialised. The task therefore threw
on every run, which for a logged in member meant a fatal error on page
load.

Maps both columns to the canonical properties, where isset() behaves as
the surrounding code expects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
email_address and usertitle are members table columns that share their
names with by-reference backward compatibility aliases, so they hit the
same problem the previous commit fixed for member_name and real_name:

  Cannot access uninitialized non-nullable property SMF\User::$email
  by reference in Sources/User.php:850

Reached on an ordinary page load, via
User::loadMe() -> setLastVisit() -> save() -> saveBatch().

Of the 24 aliases declared with a &get hook, these are the only two
that are member columns and are not already handled, either by their
own case in the switch (id_member, id_post_group, id_group, lngfile,
website_url, website_title) or by an existing entry in this match. The
mapping is now exhaustive for the columns saveBatch() can see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
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