Article

Composer Modernization Center

Add Composer to legacy PHP projects, migrate includes to autoloading, set platform constraints, and replace abandoned packages without a big-bang rewrite.

Legacy PHP applications often boot with a forest of require_once calls, a hand-copied library folder, and no lockfile. Composer is the standard way to declare dependencies, generate autoloaders, and keep PHP platform requirements honest—but adopting it mid-life must be staged so production keeps shipping while you gain control.

This center is the Composer modernization index for ZendStudio.net. Use it when you inherited a project without composer.json, when installs fail on platform PHP, or when an abandoned package blocks a runtime upgrade. It pairs with the runtime ladders in PHP 5 to modern PHP and PHP 7 to PHP 8.

What Composer fixes in legacy apps

  • Replaces ad-hoc library copies with versioned packages and a reproducible composer.lock
  • Generates classmaps or PSR-4 autoload maps so new code does not need new includes
  • Documents the PHP version and extensions your app actually needs via config.platform and require.php
  • Surfaces abandoned packages and security advisories before you discover them in production
  • Makes staging and production installs repeatable instead of “copy vendor from Steve’s laptop”

Adoption path (keep the site running)

  1. Commit a backup branch; never run destructive Composer commands only on production.
  2. Add a minimal composer.json beside the real application root (see Adding Composer).
  3. Require vendor/autoload.php once from the front controller—keep existing includes until classes move.
  4. Migrate hot paths from manual includes to PSR-4 or classmap (Manual includes → autoloading).
  5. Pin platform PHP to match staging/production (Platform constraints).
  6. Replace or fork abandoned packages on a schedule (Abandoned packages).
# LEGACY — bootstrap
require_once __DIR__ . '/lib/db.php';
require_once __DIR__ . '/lib/User.php';
require_once __DIR__ . '/lib/helpers.php';
require_once __DIR__ . '/vendor-manual/phpmailer/class.phpmailer.php';

# MODERN — bootstrap
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
# Application classes load via Composer PSR-4 / classmap.
# Keep transitional require_once only for files that are not classes yet.

Platform honesty beats ignore flags

Developer laptops on newer PHP will happily resolve packages your production 7.4/8.1 host cannot install. Set config.platform.php (and extension platform packages when needed) to the version you actually run. Validate with Composer Platform Requirement Checker and explain constraints with constraint explainer. Avoid permanent --ignore-platform-reqs—it hides the mismatch you need to fix.

# TRANSITIONAL composer.json fragment
{
  "require": {
    "php": ">=8.2"
  },
  "config": {
    "platform": {
      "php": "8.2.0"
    }
  },
  "autoload": {
    "psr-4": { "App\\": "src/" },
    "classmap": ["legacy/lib/"]
  }
}

Lockfiles, install vs update

Prefer composer install from a committed lockfile on servers. Use composer update on a branch with tests when you intentionally change versions. Treat lockfile commits as release artifacts. If you must vendor dependencies for a constrained host, document why and how rebuilds happen—do not leave mystery vendor/ trees without a recipe.

Autoloading strategies for mixed trees

  • classmap — fastest transitional win for sprawling legacy directories
  • PSR-4 — required for new modules; guide: PSR-4 modernization
  • files — last resort for function libraries; shrink over time

Validate suspicious paths with the PSR-4 Checker and JSON shape with Composer.json Validator.

Abandoned packages and security advisories

Runtime upgrades stall when a package is abandoned or incompatible with PHP 8. Schedule replacements; do not wait for a production fatal. Run composer audit regularly and track abandoned notices from composer outdated / update output. Deep guide: Abandoned packages. Security context: PHP security modernization.

Framework-specific notes

  • ZF2/ZF3 → Laminas — use the official global laminas-migration flow; it removes vendor/ and lock as part of migration. See ZF→Laminas.
  • ZF1 — Composer-ize your code even if the ZF1 library stays vendored; laminas-migration is not the ZF1 path (ZF1 Composer).
  • Inherited apps — add Composer before large refactors (inherited playbook).

Guides in this section

Workbench tools

Commands that need caution

Prefer composer install from a committed lockfile on servers. Use composer update on a branch with tests. Avoid --ignore-platform-reqs as a permanent production habit. Before large dependency moves, back up the tree and database, and validate on staging that matches production PHP and extensions. After major updates, re-run smoke tests for login, payments, and cron.

Official reference: getcomposer.org/doc.

Minimal composer.json that unblocks modernization

You do not need a perfect package layout on day one. A minimal file that autoloads first-party code and pins PHP is enough to start. Expand requires as you replace zip-dropped libraries. Validate JSON with Composer.json Validator before committing.

# TRANSITIONAL minimal shape
{
  "name": "example/legacy-app",
  "require": {
    "php": ">=7.4"
  },
  "config": {
    "platform": { "php": "7.4.33" }
  },
  "autoload": {
    "classmap": ["src/", "lib/"]
  }
}

Raise the platform pin when the runtime ladder moves—never leave the pin years behind production without a written reason.

CI pattern that catches platform lies

  • Job A: composer install --no-dev on the production PHP version
  • Job B: same on the next candidate PHP
  • Fail if lock cannot install without ignore flags
  • Run smoke tests after install

Environment drift between laptop and server is the most common Composer failure mode in inherited apps—catch it in CI with environment compare.

When vendor commits are unavoidable

Some hosts block outbound Composer during deploy. If you must commit vendor/, document the exact Composer version and command used to generate it, and still keep composer.lock. Treat vendor commits as a packaging strategy, not as an excuse to edit third-party code in place.

Related modernization paths: incremental modernization, testing legacy PHP, and the PHP version migration center.

Related reading