Skip to content

FieldtypeFile::upgrade() fatals on first boot after upgrading to 3.0.273, SystemUpdater triggers it before the $fields API exists #2347

Description

@szabeszg

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

  1. On a ProcessWire 3.0.270 installation (FieldtypeFile version 107), replace wire/ with 3.0.273.
  2. Trigger any boot, CLI is easiest: php index.php <anything>.
  3. 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

  1. 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).
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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