HardТеория6 min

Продвинутый Composer

Platform requirements, репозитории, оптимизация, монорепозитории, создание пакетов

Platform requirements

Composer позволяет указывать требования к платформе — версии PHP и расширений. Это гарантирует, что пакет будет установлен только в совместимом окружении.

{
    "require": {
        "php": ">=8.4",
        "ext-pdo": "*",
        "ext-mbstring": "*",
        "ext-intl": "*",
        "ext-redis": "^6.0",
        "ext-openssl": "*",
        "lib-openssl": ">=1.1"
    }
}

Типы platform requirements

Префикс Описание Пример
php Версия PHP "php": ">=8.4"
ext-* PHP-расширение "ext-pdo": "*"
lib-* Системная библиотека "lib-openssl": ">=1.1"
composer Версия Composer "composer": ">=2.7"

Эмуляция платформы

Иногда локальная среда отличается от production. Например, вы разрабатываете на PHP 8.4, а на сервере PHP 8.3. Секция config.platform позволяет эмулировать другую платформу:

{
    "config": {
        "platform": {
            "php": "8.3.0",
            "ext-redis": "6.0.0"
        }
    }
}

Теперь Composer будет разрешать зависимости так, будто у вас PHP 8.3. Это предотвращает установку пакетов, несовместимых с production.

# Check what platform Composer sees
composer show --platform

# Override platform temporarily
composer install --ignore-platform-req=ext-redis
composer install --ignore-platform-reqs  # Ignore all platform requirements

Типы репозиториев

Composer поддерживает несколько типов репозиториев для загрузки пакетов.

VCS Repository

Самый простой способ подключить приватный пакет из Git-репозитория:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "[email protected]:mycompany/auth-service.git"
        },
        {
            "type": "vcs",
            "url": "https://github.com/mycompany/shared-kernel.git"
        }
    ],
    "require": {
        "mycompany/auth-service": "^2.0",
        "mycompany/shared-kernel": "dev-main"
    }
}

Composer автоматически определяет теги как версии, а ветки как dev-версии (dev-main, dev-feature/xxx).

Path Repository

Подключает пакет из локальной директории. Идеально для монорепозиториев и локальной разработки:

{
    "repositories": [
        {
            "type": "path",
            "url": "../shared-kernel",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "mycompany/shared-kernel": "@dev"
    }
}

При symlink: true Composer создаёт символическую ссылку вместо копирования файлов. Изменения в пакете сразу видны без composer update.

Composer Repository

Для подключения к приватным Composer-репозиториям (Satis, Private Packagist):

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://packages.mycompany.com",
            "options": {
                "ssl": {
                    "verify_peer": true
                }
            }
        }
    ]
}

Package Repository

Для пакетов, которые не имеют composer.json (например, legacy-библиотеки):

{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "vendor/legacy-lib",
                "version": "1.0.0",
                "dist": {
                    "url": "https://example.com/legacy-lib-1.0.0.zip",
                    "type": "zip"
                },
                "autoload": {
                    "classmap": ["src/"]
                }
            }
        }
    ]
}

Приоритет репозиториев

Репозитории проверяются в порядке объявления. Первый репозиторий, содержащий пакет, побеждает. Packagist проверяется последним (можно отключить):

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://packages.mycompany.com"
        },
        {
            "packagist.org": false
        }
    ]
}

Aliases — псевдонимы версий

Branch Aliases

Позволяют дать dev-ветке семантическую версию:

{
    "extra": {
        "branch-alias": {
            "dev-main": "3.0.x-dev",
            "dev-develop": "3.1.x-dev"
        }
    }
}

Теперь dev-main воспринимается как версия 3.0.x-dev, и другие пакеты могут требовать ^3.0.

Inline Aliases

Используются в require для замены версии:

{
    "require": {
        "monolog/monolog": "dev-bugfix-123 as 3.5.0"
    }
}

Composer установит ветку bugfix-123, но для разрешения зависимостей будет считать, что это версия 3.5.0. Полезно при тестировании фиксов из feature-веток.

composer create-project

Команда create-project создаёт новый проект из пакета. Это эквивалент git clone + composer install:

# Create Symfony project
composer create-project symfony/skeleton my-symfony-app

# Create Laravel project
composer create-project laravel/laravel my-laravel-app

# Create with specific version
composer create-project symfony/skeleton:"7.2.*" my-app

# Create in current directory
composer create-project symfony/skeleton .

# Create without dev dependencies
composer create-project --no-dev symfony/skeleton my-app

# Create with preferred stability
composer create-project --stability=dev symfony/skeleton my-app

Внутренний процесс:

  1. Загружает пакет с Packagist
  2. Извлекает в указанную директорию
  3. Выполняет composer install
  4. Запускает post-create-project-cmd скрипты

Оптимизация автозагрузки

В production критически важно оптимизировать автозагрузчик для максимальной производительности.

Три уровня оптимизации

# Level 1: Optimized autoloader
# Generates a classmap for all PSR-4/PSR-0 classes
composer dump-autoload --optimize
# or
composer install --optimize-autoloader

# Level 2: Classmap authoritative
# ONLY loads from classmap, skips filesystem checks
composer dump-autoload --classmap-authoritative
# or
composer install --classmap-authoritative

# Level 3: APCu autoloader
# Caches class-to-file map in APCu (requires ext-apcu)
composer dump-autoload --apcu-autoloader
# or
composer install --apcu-autoloader

Сравнение уровней

Уровень Скорость Новые классы Требования
Без оптимизации Базовая Находятся автоматически Нет
--optimize Быстрая Находятся через fallback Нет
--classmap-authoritative Максимальная НЕ находятся (нужен dump) Нет
--apcu Очень быстрая Находятся + кэшируются ext-apcu

Конфигурация в composer.json

{
    "config": {
        "optimize-autoloader": true,
        "classmap-authoritative": false,
        "apcu-autoloader": false,
        "sort-packages": true
    }
}

Рекомендация для production:

composer install --no-dev --optimize-autoloader --no-scripts --no-interaction

Conflict и Replace

conflict

Запрещает установку несовместимых пакетов:

{
    "conflict": {
        "vendor/old-package": "<2.0",
        "another/broken-package": "3.1.0"
    }
}

Если другой пакет требует vendor/old-package:1.5, Composer выдаст ошибку конфликта.

replace

Объявляет, что ваш пакет заменяет другой. Используется в двух случаях:

1. Форк пакета:

{
    "name": "mycompany/monolog-fork",
    "replace": {
        "monolog/monolog": "3.5.0"
    }
}

Теперь все пакеты, которые требуют monolog/monolog, будут удовлетворены вашим форком.

2. Метапакеты и subtree splits:

{
    "name": "symfony/symfony",
    "replace": {
        "symfony/http-kernel": "self.version",
        "symfony/http-foundation": "self.version",
        "symfony/routing": "self.version"
    }
}

Монолитный пакет symfony/symfony заменяет все отдельные компоненты.

Плагины Composer

Плагины расширяют функциональность Composer. Они устанавливаются как обычные пакеты, но имеют тип composer-plugin.

Популярные плагины

{
    "require": {
        "symfony/flex": "^2.4"
    },
    "config": {
        "allow-plugins": {
            "symfony/flex": true,
            "phpstan/extension-installer": true
        }
    }
}

Symfony Flex — автоматическая конфигурация Symfony-бандлов. При установке пакета автоматически создаются конфиг-файлы и регистрируются бандлы.

PHPStan Extension Installer — автоматическое подключение расширений PHPStan.

Безопасность плагинов

С Composer 2.2+ плагины требуют явного разрешения в config.allow-plugins:

{
    "config": {
        "allow-plugins": {
            "symfony/flex": true,
            "phpstan/extension-installer": true,
            "untrusted/plugin": false
        }
    }
}

Безопасность: composer audit

Команда composer audit проверяет установленные пакеты на известные уязвимости:

# Check for security vulnerabilities
composer audit

# Output in JSON format (for CI/CD)
composer audit --format=json

# Only check locked packages
composer audit --locked

Пример вывода:

Found 2 security vulnerability advisories affecting 2 packages:
+-------------------+-----------------------------------------------------+
| Package           | symfony/http-kernel                                 |
| CVE               | CVE-2024-XXXXX                                      |
| Title             | Security issue in HTTP kernel                       |
| Affected versions | >=7.0.0,<7.2.1                                     |
| Fixed version     | 7.2.1                                               |
+-------------------+-----------------------------------------------------+

Рекомендуется добавлять composer audit в CI/CD пайплайн:

# GitHub Actions example
- name: Security audit
  run: composer audit --format=json

composer bump

Команда composer bump обновляет ограничения версий в composer.json до минимальных версий, зафиксированных в composer.lock:

# Before bump:
# composer.json: "monolog/monolog": "^3.0"
# composer.lock: monolog/monolog 3.7.0

composer bump

# After bump:
# composer.json: "monolog/monolog": "^3.7.0"
# composer.lock: monolog/monolog 3.7.0 (unchanged)

# Bump only dev dependencies
composer bump --dev-only

# Dry run (show what would change)
composer bump --dry-run

Это полезно для документирования минимально протестированных версий.

Монорепозиторий с Composer

Монорепозиторий содержит несколько пакетов в одном Git-репозитории.

Структура монорепозитория

monorepo/
├── composer.json              # Root composer.json
├── packages/
│   ├── shared-kernel/
│   │   ├── composer.json      # Package composer.json
│   │   └── src/
│   │       └── ValueObject/
│   │           └── Email.php
│   ├── auth-service/
│   │   ├── composer.json
│   │   └── src/
│   │       └── AuthService.php
│   └── notification-service/
│       ├── composer.json
│       └── src/
│           └── NotificationService.php
└── app/
    ├── composer.json
    └── src/

Root composer.json

{
    "name": "mycompany/monorepo",
    "type": "project",
    "repositories": [
        {
            "type": "path",
            "url": "packages/*",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "mycompany/shared-kernel": "@dev",
        "mycompany/auth-service": "@dev",
        "mycompany/notification-service": "@dev"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

Package composer.json (shared-kernel)

{
    "name": "mycompany/shared-kernel",
    "description": "Shared domain kernel",
    "type": "library",
    "require": {
        "php": ">=8.4"
    },
    "autoload": {
        "psr-4": {
            "MyCompany\\SharedKernel\\": "src/"
        }
    }
}

Package composer.json (auth-service)

{
    "name": "mycompany/auth-service",
    "description": "Authentication service",
    "type": "library",
    "require": {
        "php": ">=8.4",
        "mycompany/shared-kernel": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "MyCompany\\AuthService\\": "src/"
        }
    }
}

Благодаря symlink: true изменения в пакетах сразу видны без переустановки.

Performance: --prefer-dist vs --prefer-source

# Download zip archives (faster, no .git history)
composer install --prefer-dist

# Clone full git repositories (needed for development)
composer install --prefer-source

# Default behavior:
# - Tagged versions: --prefer-dist
# - Dev versions: --prefer-source

Параллельные загрузки

Composer 2 загружает пакеты параллельно по умолчанию. Можно настроить:

{
    "config": {
        "process-timeout": 300,
        "use-parent-dir": false
    }
}

composer.json vs composer.lock в CI/CD

Правильный CI/CD пайплайн

# GitHub Actions
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: pdo, mbstring, intl, redis
          coverage: xdebug

      # Cache Composer dependencies
      - name: Cache Composer
        uses: actions/cache@v4
        with:
          path: vendor
          key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      # CRITICAL: use install, not update
      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist --optimize-autoloader

      - name: Security audit
        run: composer audit

      - name: Static analysis
        run: vendor/bin/phpstan analyse

      - name: Code style
        run: vendor/bin/php-cs-fixer fix --dry-run --diff

      - name: Tests
        run: vendor/bin/phpunit --coverage-clover coverage.xml

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      # Production install
      - name: Install production dependencies
        run: composer install --no-dev --optimize-autoloader --no-interaction --no-scripts

      - name: Warmup cache
        run: php bin/console cache:warmup --env=prod

Создание своего Composer-пакета

Шаг 1: Структура пакета

my-package/
├── composer.json
├── LICENSE
├── README.md
├── src/
│   ├── Calculator.php
│   └── Exception/
│       └── DivisionByZeroException.php
└── tests/
    └── CalculatorTest.php

Шаг 2: composer.json пакета

{
    "name": "myvendor/calculator",
    "description": "A simple calculator library",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "John Doe",
            "email": "[email protected]"
        }
    ],
    "require": {
        "php": ">=8.4"
    },
    "require-dev": {
        "phpunit/phpunit": "^11.5",
        "phpstan/phpstan": "^2.1"
    },
    "autoload": {
        "psr-4": {
            "MyVendor\\Calculator\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "MyVendor\\Calculator\\Tests\\": "tests/"
        }
    },
    "scripts": {
        "test": "phpunit",
        "analyse": "phpstan analyse"
    },
    "minimum-stability": "stable"
}

Шаг 3: Код пакета

<?php

declare(strict_types=1);

namespace MyVendor\Calculator;

use MyVendor\Calculator\Exception\DivisionByZeroException;

/**
 * Simple calculator with basic arithmetic operations.
 */
final readonly class Calculator
{
    /**
     * @param list<float> $history
     */
    public function __construct(
        private array $history = [],
    ) {}

    public function add(float $a, float $b): float
    {
        return $a + $b;
    }

    public function subtract(float $a, float $b): float
    {
        return $a - $b;
    }

    public function multiply(float $a, float $b): float
    {
        return $a * $b;
    }

    /**
     * @throws DivisionByZeroException
     */
    public function divide(float $a, float $b): float
    {
        if ($b === 0.0) {
            throw new DivisionByZeroException('Cannot divide by zero');
        }

        return $a / $b;
    }
}

Шаг 4: Публикация на Packagist

# 1. Push to GitHub
# 2. Go to packagist.org/packages/submit
# 3. Enter repository URL
# 4. Configure GitHub webhook for auto-updates

# Tag a version
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

# Now users can install:
# composer require myvendor/calculator

Продвинутая конфигурация

Полный config блок

{
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true,
        "allow-plugins": {
            "symfony/flex": true
        },
        "platform": {
            "php": "8.4.0"
        },
        "process-timeout": 300,
        "cache-dir": "/tmp/composer-cache",
        "vendor-dir": "vendor",
        "bin-dir": "vendor/bin",
        "discard-changes": false,
        "audit": {
            "abandoned": "report"
        }
    }
}

Переменные окружения

# Set Composer home directory
export COMPOSER_HOME=/opt/composer

# Set memory limit for Composer
export COMPOSER_MEMORY_LIMIT=-1

# Set auth token for GitHub
export COMPOSER_AUTH='{"github-oauth": {"github.com": "TOKEN"}}'

# Disable Packagist (for air-gapped environments)
export COMPOSER_DISABLE_NETWORK=1

Аутентификация

# GitHub token (for rate limit)
composer config --global github-oauth.github.com YOUR_TOKEN

# GitLab token
composer config --global gitlab-token.gitlab.com YOUR_TOKEN

# HTTP Basic auth
composer config http-basic.packages.mycompany.com user password

Эти данные сохраняются в ~/.composer/auth.json:

{
    "github-oauth": {
        "github.com": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "http-basic": {
        "packages.mycompany.com": {
            "username": "user",
            "password": "secret"
        }
    }
}

Проверь себя

5 из 12

Что делает секция replace в composer.json?

Что делает composer audit?

Что означает allow-plugins в секции config?

Как правильно подключить несколько пакетов из монорепозитория через path?

Что делает config.platform.php в composer.json?