Как я ускорил установку PHP-зависимостей в 5 раз с помощью Go
- вторник, 23 декабря 2025 г. в 00:00:16
TL;DR
Переписал Composer на Go, получил 3-5x ускорение благодаря параллельной загрузке пакетов и отсутствию PHP runtime overhead. Проект полностью совместим с экосистемой Composer/Packagist (почти, об этом будет подробнее внизу).
Каждый PHP‑разработчик знаком с этим чувством: запускаешь composer install и идёшь заваривать чай. Для небольшого проекта — минута, для Symfony/Laravel — несколько минут. В CI/CD пайплайне это превращается в существенные затраты времени.
Основные проблемы PHP Composer:
Интерпретируемый язык — PHP не может конкурировать с компилируемым Go по скорости выполнения
Последовательная загрузка — пакеты загружаются один за другим
Тяжёлый runtime — даже для простой операции нужен весь PHP‑стек
Сложное разрешение зависимостей — алгоритм SAT‑solver работает медленно
Я решил проверить гипотезу: можно ли существенно ускорить установку зависимостей, используя конкурентную модель Go?
go-composer/
├── main.go # Точка входа
├── cmd/ # CLI команды
│ ├── root.go # Корневая команда
│ ├── init.go # go-composer init
│ ├── install.go # go-composer install
│ ├── update.go # go-composer update
│ └── require.go # go-composer require
├── pkg/
│ ├── composer/ # Парсинг composer.json/lock
│ ├── packagist/ # API клиент Packagist
│ ├── resolver/ # Разрешение зависимостей
│ ├── installer/ # Параллельная установка
│ └── autoload/ # Генерация autoload
└── examples/ # Примеры проектов┌─────────────────┐
│ composer.json │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Resolver │────▶│ Packagist API │
│ (semver logic) │◀────│ (p2/*.json) │
└────────┬────────┘ └─────────────────┘
│
│ Граф зависимостей
▼
┌─────────────────┐
│ Installer │
│ (goroutines) │
└────────┬────────┘
│
┌────┴────┬────────┬────────┐
▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ pkg1 │ │ pkg2 │ │ pkg3 │ │ pkgN │ ← Параллельная загрузка
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
└────────┴────────┴────────┘
│
▼
┌─────────────────┐
│ Autoload │
│ Generator │
└────────┬────────┘
│
▼
┌─────────────────┐
│ vendor/ │
│ ├── autoload │
│ ├── composer/ │
│ └── packages/ │
└─────────────────┘Одна из главных проблем при работе с Packagist API — нестандартные ответы. Поле require-dev может быть объектом, строкой или null. Я решил это с помощью кастомного UnmarshalJSON:

Это позволяет корректно обрабатывать ~99.9% пакетов из Packagist, включая те, где метаданные имеют нестандартный формат.
Resolver поддерживает все стандартные Composer constraints:
Оператор | Пример | Значение |
|
|
|
|
|
|
|
|
|
|
| Любая из веток |
|
| Любая версия |

Главная фишка go‑composer — параллельная загрузка всех пакетов через горутины:

Что происходит внутри installPackage:
Загрузка ZIP‑архива с Packagist
Проверка SHA-256 контрольной суммы
Распаковка в vendor/{vendor}/{package}/
Формирование данных для go-composer.lock
Генератор создаёт все необходимые файлы для совместимости с экосистемой Composer
vendor/
├── autoload.php # Главный autoload файл
├── ClassLoader.php # PSR-4/PSR-0 загрузчик классов
├── autoload_runtime.php # Для Symfony Runtime
└── composer/
├── installed.json # Список установленных пакетов
├── InstalledVersions.php # API Composer\InstalledVersions
├── platform_check.php # Проверка версии PHP
└── autoload_classmap.php # Classmap автозагрузка
Тестирование проводилось на MacBook Pro M1 с интернет‑соединением 300 Mbps.
Проект | Пакеты | PHP Composer | go-composer | Ускорение |
|---|---|---|---|---|
Monolog only | 3 | 5-8 сек | 2 сек | 3-4x |
Symfony App | 36 | 15-20 сек | 3-5 сек | 4-5x |
Laravel App | 80+ | 45-60 сек | 12-15 сек | 4-5x |
Параллелизм: При 36 пакетах вместо 36 последовательных загрузок происходят ~36 параллельных
Компилируемый код: Go на порядок быстрее PHP для CPU‑bound операций
Нет runtime overhead: Один бинарник 10MB vs PHP interpreter + множество файлов Composer
Эффективная работа с памятью: Go управляет памятью более эффективно
composer.json — полный парсинг (require, autoload, authors и так далее)
composer.lock — чтение (пока нет полной совместимости, поэтому не генерируется, а только читается)
go-composer.lock — генерация
Packagist API — интеграция через P2 API
Semver constraints — полная поддержка (^, ~, >=, ||, |, *)
Рекурсивное разрешение зависимостей
SHA-256 проверка целостности
PSR-4 автозагрузка
PSR-0 автозагрузка (legacy)
Classmap
Files
Автоматическое подключение bootstrap файлов
php — версия PHP (пропускается)
ext-* — расширения PHP (пропускаются)
lib-* — системные библиотеки (пропускаются)
composer-runtime-api / composer-plugin-api
Тут все просто, в данный момент не реализована полная совместимость генерируемых lock файлов, поэтому при установке «голого» проекта без composer.lock, собирается свой go‑composer.lock и используется. Если же в проекте уже существует composer.lock, то библиотеки будут собираться из него и go‑composer.lock создаваться не будет. Можно поиграться параметрами new-lock и force-new-lock. Они позволяют управлять поведением работы с go‑composer.lock. По умолчанию new-lock установлен в true и это значит что мы будем пытаться создать go‑composer.lock всегда, если нет composer.lock. Что касается force-new-lock, данный флаг позволяет игнорировать наличие compose.lock и независимо от его существования соберет go‑composer.lock и установит из него зависимости.
git clone https://github.com/xman12/go-composer.git
cd go-composer
make build
sudo make installПосле сборки создается бинарник go‑composer. Так же для удобства добавил команду build-all она собирает под разные платформы бинарники и складывает в папку bin
После чего можно перейти в папку examples/simple-monolog и выполнить команду
go-composer install

Ограничение | Статус | Комментарий |
|---|---|---|
Composer scripts | ❌ | Scripts требуют PHP runtime |
Composer plugins | ❌ | Плагины — это PHP-код |
VCS repositories | ❌ | Только Packagist |
Private Packagist | ❌ | Требуется авторизация, не поддерживается в данный момент |
Platform validation | 🟡 | Определяются, но не валидируются |
composer.lock | ❌🟡 | Нет полной обратной совместимости |
Реализовать полную обратную совместимость с composer.lock
Git repositories — установка напрямую из VCS
Private Packagist — поддержка приватных репозиториев
Хочется еще как‑то ускорить, подумаю над кэшами и как их реализовать.
go‑composer демонстрирует, что переход на компилируемый язык с хорошей поддержкой конкурентности даёт существенный прирост производительности для I/O‑bound операций. Были протестированы проекты на Symfony, Laravel и они запустились). Прирост производительности очень вдохновляет продолжать заниматься проектом, но сейчас в проекте хватает над чем работать и текущее состояние не позволяет его использовать в продакшене, по крайней мере я вам это не рекомендую, так‑как возможны сайдэффекты, но работа над проектом продолжается и я нацелен довести проект до полной совместимости что позволит его использовать просто заменив один инструмент на другой. Очень буду благодарен за любую обратную связь.