[3.0] Build a Time before the user is loaded - #9635
Conversation
|
Instead of all these changes, it would be better and cleaner to simply replace this: ... with this: The secret sauce here is the introduction of the nullsafe operator (i.e. |
QueryString::cleanRequest() redirects '?msg=1' to the topic that message sits in, and with queryless URLs turned on the redirect target is rewritten through Topic::buildRoute(), which loads the topic to get its slug. All of that happens before User::load(), so the Time that Topic::loadTopicInfo() builds for the topic's start date had nothing to read a time zone from. User::$me is a typed static, so reading it that early is a fatal error rather than an empty value. The nullsafe operator lets that read fall through to the forum's default time zone, and then to PHP's, instead of throwing. cron.php never loads a user at all, so Tasks\PaidSubs was reaching the same fatal by a different road. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
7f5d86e to
b9a8666
Compare
|
Taken — that's much better, thanks. Pushed as a one-line change; the helper method is gone. Two notes from checking it, neither of them an argument against the suggestion.
class U { public string $timezone = 'Asia/Tokyo'; }
class A { public static U $me; }
var_dump(A::$me?->timezone ?? 'fallback'); // string(8) "fallback"
var_dump(A::$me->timezone ?? 'fallback'); // string(8) "fallback"Only a bare read throws — which is why
The one behavioural difference from what I first pushed is that the fallback is now cached in |
Description
A direct link to a post (
?msg=6000, ormsgs/6000) is a 500 when queryless URLs are turned on:The chain is:
cleanRequest()callsredirectFromMsg(), which looks up the topic the message is in and redirects to it.Utils::redirectexit()then runs the target throughQueryString::rewriteAsQueryless(), which asksTopic::buildRoute()for the route, and on a slug cache miss that loads the topic. All of this happens inForum::__construct(), well beforeUser::loadMe().Topic::loadTopicInfo()is already written to survive being called without a user — it guardsUser::$mewithisset()in four places. The one thing it does unguarded is build aTimefor the topic's start date, andTime::__construct()readsUser::$me->timezone.User::$meis a typed static with no default, so reading it before it is assigned is a fatal error rather than an empty value.The read now falls through to the forum's default time zone, and then to PHP's, rather than throwing.
Fixing it in
Timerather than inTopic::loadTopicInfo()covers the general case, and there is at least one other case:cron.phpnever loads a user at all —TaskRunnersetsUser::$scand nothing else — soTasks\PaidSubs.php:104, which builds aTimefor the reminder email'sEND_DATE, was reaching the same fatal by a different road.Verified on the Docker environment with
queryless_urlsenabled:?msg=6000andmsgs/6000both 302 totopics/<slug>-1187/msg6000#msg6000and render, andsmf_log_errorsstays empty.tests/Unit/TimeTest.phpis new. Its regression test errors with the reported fatal before the change and passes after it. Note thatTimecaches the zone it works out inTime::$user_tz, and a typed static cannot be put back into its uninitialised state, so only the firstTimebuilt in a process exercises this path — the test asserts that precondition explicitly, so that if something later builds aTimeearlier it fails rather than quietly passing while testing nothing.Issues References (Fixes|Related|Closes)
🤖 Generated with Claude Code