Short description of the issue
$pages->save() can throw Error: [] operator not supported for strings at wire/core/Pages/PagesEditor.php:876 (and the same line pattern at :1004). It occurs with the default options (getVerbose is false), so normal saving is affected, not just callers requesting verbose data.
Introduced by commit 941fd8b2 ("Add new $pages->save() options: saveAll, getVerbose, throw"). Still present in current dev — git log 941fd8b2..dev -- wire/core/Pages/PagesEditor.php is empty, so that commit is the last change to the file and upgrading dev does not resolve it.
Expected behavior
Saving a page completes normally.
Actual behavior
Error: [] operator not supported for strings
in wire/core/Pages/PagesEditor.php:876
$logHooks[] = "saved";
Optional: Suggestion for a possible fix
The new verbose-logging code aliases $verboseData array elements by reference. In ___save() this is done only when verbose (lines 511–513, inside if($getVerbose)):
$logMessages = &$verboseData['messages'];
$logErrors = &$verboseData['errors'];
$logHooks = &$verboseData['hooks'];
But the two helper methods create the same aliases unconditionally, regardless of getVerbose:
savePageQuery() — lines 642–644
savePageFinish() — lines 849–851
Those aliases stay live across the hookable calls in the save path (statusChangeReady, saveReady, savePageOrFieldReady, addReady, saved, savedPageOrField, …). PHP's reference-on-copy behaviour then bites: when a live reference element exists and any structure sharing it is copied by value and that element reassigned, the original element is silently clobbered to a non-array. Minimal demonstration of the mechanism (PHP 8.5):
$vd = [];
$ref = &$vd['hooks']; // live reference element
$ref[] = 'x'; $ref[] = 'y';
$copy = $vd; // copied while $ref is live -> copy shares the reference
$copy['hooks'] = "STRING"; // also clobbers $vd['hooks']
var_dump($vd['hooks']); // string(6) "STRING" -- no longer an array
Once $verboseData['hooks'] holds a string, $logHooks[] = … (which is &$verboseData['hooks']) throws. Two crash sites exist, both in savePageFinish() (both reached via the alias at line 851): line 876 (the "page unchanged" early-return branch) and line 1004 (the normal changed-page path).
Suggested fix: make the two helpers mirror what ___save() already does at 511–513 — alias only when verbose, otherwise append to a throwaway local array. Apply to savePageQuery() (642–644) and savePageFinish() (849–851):
$getVerbose = !empty($options['getVerbose']);
$logMessages = [];
$logErrors = [];
$logHooks = [];
if($getVerbose) {
$logMessages = &$verboseData['messages'];
$logErrors = &$verboseData['errors'];
$logHooks = &$verboseData['hooks'];
}
With this, non-verbose saves (the default) send all $logMessages[] / $logErrors[] / $logHooks[] appends to discarded local arrays that never touch $verboseData, so both crash sites become impossible. Verbose behaviour is unchanged, and the live-reference exposure is reduced to the opt-in getVerbose case only.
Steps to reproduce the issue
The crash is environment-dependent: the fragile live references run for every save, but the clobbering copy-and-reassign is triggered by a save-hook in the stack, so a bare install may not surface it. Observed reliably when editing/saving a User page on a site with the usual set of save hooks (e.g. VersionControl snapshots, SeoMaestro, PrivacyWire).
- On dev (3.0.265+), have one or more modules hooking
Pages::saveReady/Pages::saved (VersionControl, etc.).
- Edit and save any page (reproduced via
ProcessUser editing a user).
- Save throws
[] operator not supported for strings at PagesEditor.php:876.
Setup/Environment
- ProcessWire version: dev 3.0.265–3.0.266 (latest dev; confirmed unfixed in 3.0.266)
- (Optional) PHP version: 8.5.7
- (Optional) Any 3rd party modules that are installed and could be related to the issue: modules that hook
Pages::saveReady / Pages::saved (e.g. VersionControl, SeoMaestro, PrivacyWire) — anything adding save-path hooks can trigger it
Short description of the issue
$pages->save()can throwError: [] operator not supported for stringsatwire/core/Pages/PagesEditor.php:876(and the same line pattern at:1004). It occurs with the default options (getVerboseisfalse), so normal saving is affected, not just callers requesting verbose data.Introduced by commit
941fd8b2("Add new$pages->save()options:saveAll,getVerbose,throw"). Still present in current dev —git log 941fd8b2..dev -- wire/core/Pages/PagesEditor.phpis empty, so that commit is the last change to the file and upgrading dev does not resolve it.Expected behavior
Saving a page completes normally.
Actual behavior
Optional: Suggestion for a possible fix
The new verbose-logging code aliases
$verboseDataarray elements by reference. In___save()this is done only when verbose (lines 511–513, insideif($getVerbose)):But the two helper methods create the same aliases unconditionally, regardless of
getVerbose:savePageQuery()— lines 642–644savePageFinish()— lines 849–851Those aliases stay live across the hookable calls in the save path (
statusChangeReady,saveReady,savePageOrFieldReady,addReady,saved,savedPageOrField, …). PHP's reference-on-copy behaviour then bites: when a live reference element exists and any structure sharing it is copied by value and that element reassigned, the original element is silently clobbered to a non-array. Minimal demonstration of the mechanism (PHP 8.5):Once
$verboseData['hooks']holds a string,$logHooks[] = …(which is&$verboseData['hooks']) throws. Two crash sites exist, both insavePageFinish()(both reached via the alias at line 851): line 876 (the "page unchanged" early-return branch) and line 1004 (the normal changed-page path).Suggested fix: make the two helpers mirror what
___save()already does at 511–513 — alias only when verbose, otherwise append to a throwaway local array. Apply tosavePageQuery()(642–644) andsavePageFinish()(849–851):With this, non-verbose saves (the default) send all
$logMessages[]/$logErrors[]/$logHooks[]appends to discarded local arrays that never touch$verboseData, so both crash sites become impossible. Verbose behaviour is unchanged, and the live-reference exposure is reduced to the opt-ingetVerbosecase only.Steps to reproduce the issue
The crash is environment-dependent: the fragile live references run for every save, but the clobbering copy-and-reassign is triggered by a save-hook in the stack, so a bare install may not surface it. Observed reliably when editing/saving a User page on a site with the usual set of save hooks (e.g. VersionControl snapshots, SeoMaestro, PrivacyWire).
Pages::saveReady/Pages::saved(VersionControl, etc.).ProcessUserediting a user).[] operator not supported for stringsatPagesEditor.php:876.Setup/Environment
Pages::saveReady/Pages::saved(e.g. VersionControl, SeoMaestro, PrivacyWire) — anything adding save-path hooks can trigger it