Skip to content

fix(install): survive an autoloader rename during package upgrade (1.14.1-beta1, #779) - #780

Merged
biz87 merged 2 commits into
betafrom
dev/1.14.1-beta1
Sep 24, 2026
Merged

biz87 merged 2 commits into
betafrom
dev/1.14.1-beta1

Conversation

@biz87

@biz87 biz87 commented Sep 24, 2026

Copy link
Copy Markdown
Member

Экстренный патч: 1.14.0-beta1 не устанавливается поверх предыдущих версий (#779).

Механизм

bootstrap.php:9 подключает vendor/autoload.php на каждом запросе MODX. Поэтому при обновлении в одном запросе:

  1. MODX стартует, в памяти оказывается composer/autoload_real.php установленной версии.
  2. Транспорт распаковывает новый vendor/ поверх.
  3. resolver_02_migrations.php → phinx_vendor.php → require vendor/autoload.php: файл на диске уже новый, читается заново.
  4. Его require_once __DIR__ . '/composer/autoload_real.php' — no-op, путь не изменился и уже подключён.
  5. Строка 25 зовёт ComposerAutoloaderInit541a2bb3…::getLoader(). Класса нет. Fatal, установка обрывается целиком.

Нужны два условия, и оба сошлись в 1.14.0-beta1:

  • имя класса сменилось — Composer выводит суффикс из состава пакетов, а в require-dev добавился modxkit/testbench. Раньше состав между релизами не менялся и имя совпадало случайно;
  • плоский require вместо require_once — регрессия из Миграции: Phinx из vendor MiniShop3, не из чужого extra #726, есть только в 1.14.0-beta1. До неё резолвер делал require_once и до строки 25 не доходил.

Замечание из issue про dev-vendor в пакете оказалось не побочным: именно изменение состава require-dev и сдвинуло хеш.

Что сделано

phinx_vendor.php — ms3PhinxEnsureAutoloaderInit() вычитывает имя класса из нового autoload.php и, если оно не объявлено, требует composer/autoload_real.php напрямую, в обход кэша require_once. Новое имя с прежним не столкнётся — это и есть условие, при котором мы сюда попали.

Это единственная часть, которая спасает уже пострадавших: они обновляются со старого хеша, и один autoloader-suffix им не поможет — новый autoload.php всё равно позовёт класс, которого нет в памяти. Защита приезжает внутри пакета и исполняется уже из новых файлов.

composer.json — config.autoloader-suffix: "MiniShop3". Имя класса больше не зависит от состава пакетов. Проверено: одинаково с --no-dev и без. composer.lock не изменился.

_build/vendor_guard.php + вызов в build.php до создания vehicle — сборка отказывается паковать vendor/ с require-dev (читает dev и dev-package-names из installed.json) и печатает, что запустить. В 1.14.0-beta1 в пакет уехали PHPUnit, PHPStan и testbench: 45 МБ вместо примерно семи.

Проверка

Воспроизведено на живом сайте, где лежал старый хеш:

1. установленный автолоадер в памяти: ComposerAutoloaderInit541a2bb3a6d325164f8600968e830d0d
2. распакован новый vendor, его класс: ComposerAutoloaderInitMiniShop3
3. phinx_vendor.php: как в 1.14.0-beta1
   → Error: Class "ComposerAutoloaderInitMiniShop3" not found
     в ...\vendor\autoload.php:25

Тот же файл и та же строка, что в логе установки из issue. С исправленным phinx_vendor.php — ClassLoader получен, установка продолжается.

Тесты

  • tests/PhinxVendorAutoloadUpgradeTest.php — строит поддельный vendor/, проигрывает последовательность обновления и падает, если ловушка перестала воспроизводиться; дальше проверяет защиту, первый запуск, отсутствующий vendor/ и закреплённый суффикс.
  • tests/BuildVendorGuardTest.php — dev-сборка отклоняется с инструкцией, --no-dev проходит, installed.json без dev-маркеров проходит, отсутствующий installed.json отклоняется, порядок вызова в build.php.

Локально: PHPStan 458 файлов без ошибок, smoke 119 тестов зелёные, composer validate чистый.

Closes #779

🤖 Generated with Claude Code

bootstrap.php require_once's vendor/autoload.php on every MODX request, so the
installed version's composer/autoload_real.php is already in memory when the
transport unpacks a new vendor over it. The migrations resolver then re-reads
autoload.php, its require_once on the unchanged autoload_real.php path is a
no-op, and the call to ComposerAutoloaderInit* hits a class nobody declared.
Composer derives that name from the package set, which moved in 1.14.0-beta1
when modxkit/testbench joined require-dev — so upgrades from 1.13 died with
`Class "ComposerAutoloaderInit541a2bb3..." not found`.

The plain require that exposes the trap arrived in #726; before it the resolver
used require_once and skipped the file wholesale.

- ms3PhinxEnsureAutoloaderInit() reads the expected class out of the new
  autoload.php and requires composer/autoload_real.php directly when it is not
  declared. This is what rescues upgrades from versions built before the fix.
- composer.json pins config.autoloader-suffix so the class name stops moving
  with the package set.
- _build/vendor_guard.php refuses to package a vendor/ that still holds
  require-dev packages: 1.14.0-beta1 shipped PHPUnit, PHPStan and the testbench,
  45 MB instead of roughly seven.

chore(release): 1.14.1-beta1
The guard ran at the top of process(), but the constructor has already built
the transport package and category by then, so a rejected build left a stub
.transport.zip behind. Move it ahead of initialize().
@biz87
biz87 merged commit ea9d666 into beta Sep 24, 2026
6 checks passed
@biz87
biz87 deleted the dev/1.14.1-beta1 branch September 24, 2026 15:02
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.

[Bug] Установка 1.14.0-beta1: Class ComposerAutoloaderInit… not found

1 participant