Environment
ProcessWire 3.0.273 (dev), upgraded from 3.0.270 (FieldtypeFile module version 107 → 108)
PHP 8.4, reproduced on two independent environments (8.4.12 / macOS MAMP, and 8.4 / cPanel ea-php84 Linux)
Site with file/image fields present; no field-* / field-x-* templates
Error (first boot after the upgrade, CLI)
Error: Call to a member function findByType() on null in /wire/modules/Fieldtype/FieldtypeFile/FieldtypeFile.module:1898
#0 /wire/core/Modules/ModulesInfo.php(1276): ProcessWire\FieldtypeFile->upgrade(107, 108)
#1 /wire/core/Modules/Modules.php(2358): ProcessWire\ModulesInfo->moduleVersionChanged(Object(ProcessWire\FieldtypeFile), 107, 108) #2 /wire/core/Wire/Wire.php(414): ProcessWire\Modules->___moduleVersionChanged(...)
#3 /wire/core/WireHooks/WireHooks.php(1077): ProcessWire\Wire->_callMethod(...)
#4 /wire/core/Wire/Wire.php(476): ProcessWire\WireHooks->runHooks(...)
#5 /wire/core/Modules/ModulesInfo.php(1245): ProcessWire\Wire->__call('moduleVersionCh...', Array)
#6 /wire/core/Modules/ModulesLoader.php(158): ProcessWire\ModulesInfo->checkModuleVersion(Object(ProcessWire\FieldtypeFile)) #7 /wire/core/Modules/Modules.php(677): ProcessWire\ModulesLoader->initModule(Object(ProcessWire\FieldtypeFile), Array) #8 /wire/core/Modules/ModulesInfo.php(1127): ProcessWire\Modules->getModule('FieldtypeFile')
#9 /wire/core/Modules/Modules.php(2117): ProcessWire\ModulesInfo->clearModuleInfoCache(false)
#10 /wire/core/Wire/Wire.php(405): ProcessWire\Modules->___refresh()
#11 /wire/core/WireHooks/WireHooks.php(1077): ProcessWire\Wire->_callMethod('___refresh', Array)
#12 /wire/core/Modules/Modules.php(2135): ProcessWire\Wire->__call('refresh', Array)
#13 /wire/modules/System/SystemUpdater/SystemUpdater.module(112): ProcessWire\Modules->resetCache()
#14 /wire/core/Modules/ModulesLoader.php(168): ProcessWire\SystemUpdater->init()
#15 /wire/core/Modules/Modules.php(677): ProcessWire\ModulesLoader->initModule(Object(ProcessWire\SystemUpdater), Array) #16 /wire/core/Modules/Modules.php(503): ProcessWire\Modules->getModule('SystemUpdater')
#17 /wire/core/ProcessWire.php(599): ProcessWire\Modules->get('SystemUpdater')
#18 /wire/core/ProcessWire.php(346): ProcessWire\ProcessWire->load(Object(ProcessWire\Config))
#19 /index.php(57): ProcessWire\ProcessWire->boot(Object(ProcessWire\Config))
Steps to reproduce
- On a ProcessWire 3.0.270 installation (FieldtypeFile version 107), replace
wire/ with 3.0.273.
- Trigger any boot, CLI is easiest:
php index.php <anything>.
- The first boot fatals with the error above. The second and later boots are clean.
Root cause
Boot order in ProcessWire::load():
- Line 599:
$this->updater = $modules->get('SystemUpdater');, SystemUpdater's init() runs here. When a system version bump is pending it calls $this->modules->resetCache() (SystemUpdater.module line 112).
- That reset cascades:
resetCache() → clearModuleInfoCache() → getModule('FieldtypeFile') → checkModuleVersion() → version changed 107 → 108 → FieldtypeFile::upgrade(107, 108).
- But the API variables it needs are wired only AFTER line 599,
fields at line 606,
templates at line 608. So inside upgrade():
// wire/modules/Fieldtype/FieldtypeFile/FieldtypeFile.module, lines 1894-1898
public function upgrade($fromVersion, $toVersion) {
// FieldtypeImage has its own module version; this upgrade finds inherited types below if($this->className() !== 'FieldtypeFile' || $fromVersion >= 108) return;
$templates = $this->wire()->templates; // null here too, but assigning null doesn't throw foreach($this->wire()->fields->findByType('FieldtypeFile') as $field) { // line 1898: fatal
Consequences
- The first request/boot after upgrading to 3.0.273 fatals (CLI dies with the error; an HTTP request would be a 500 for that one request).
- Because SystemUpdater saves
systemVersion after each applied step (SystemUpdater.module lines 100-105), the second boot does not retry the early cache reset, so the fatal does not recur, but the exception left FieldtypeFile's version row un-updated and the upgrade
routine's work (setting noParents = 1 on field-* / field-x-* templates of file fields) silently never runs through this path.
On a site with no field-* / field-x-* templates the fix itself is a no-op, but the fatal still occurs.
Suggested fix
Make FieldtypeFile::upgrade() bail when the API is not yet available, so the version row stays un-bumped and checkModuleVersion() re-fires the upgrade later in a fully-booted context where wire('fields') exists:
$fields = $this->wire('fields');
if(!$fields) return; // not yet boot-complete; will re-run when next initialized
Alternatively (broader): defer SystemUpdater's resetCache() until after the API variables are wired, since any module version upgrade touching $fields/$templates in that window would hit the same null.
Environment
ProcessWire 3.0.273 (dev), upgraded from 3.0.270 (FieldtypeFile module version 107 → 108)
PHP 8.4, reproduced on two independent environments (8.4.12 / macOS MAMP, and 8.4 / cPanel ea-php84 Linux)
Site with file/image fields present; no
field-*/field-x-*templatesError (first boot after the upgrade, CLI)
Steps to reproduce
wire/with 3.0.273.php index.php <anything>.Root cause
Boot order in
ProcessWire::load():$this->updater = $modules->get('SystemUpdater');, SystemUpdater'sinit()runs here. When a system version bump is pending it calls$this->modules->resetCache()(SystemUpdater.module line 112).resetCache()→clearModuleInfoCache()→getModule('FieldtypeFile')→checkModuleVersion()→ version changed 107 → 108 →FieldtypeFile::upgrade(107, 108).fieldsat line 606,templatesat line 608. So insideupgrade():Consequences
systemVersionafter each applied step (SystemUpdater.module lines 100-105), the second boot does not retry the early cache reset, so the fatal does not recur, but the exception left FieldtypeFile's version row un-updated and the upgraderoutine's work (setting
noParents = 1onfield-*/field-x-*templates of file fields) silently never runs through this path.On a site with no
field-*/field-x-*templates the fix itself is a no-op, but the fatal still occurs.Suggested fix
Make
FieldtypeFile::upgrade()bail when the API is not yet available, so the version row stays un-bumped andcheckModuleVersion()re-fires the upgrade later in a fully-booted context wherewire('fields')exists:Alternatively (broader): defer SystemUpdater's
resetCache()until after the API variables are wired, since any module version upgrade touching$fields/$templatesin that window would hit the same null.