Migrating legacy PHP code to modern Composer packages

Legacy PHP applications rarely arrive as one clean codebase. They often combine procedural controllers, PEAR libraries, PECL extensions, include_path lookups and framework helpers that assume the host has been configured in a particular way. A Composer-based package migration can remove that guesswork, but only if the old coupling is understood before it is hidden behind autoloading.

The practical goal is not a wholesale rewrite. It is a reproducible build: one manifest, one lock file, an explicit extension list, and deployment steps another engineer can repeat from a clean machine. That gives small Australian software teams a safer way to retire brittle deployment scripts without changing business behaviour in the same release.

Why Composer changes the risk profile

Composer turns loosely coupled library references into a declared dependency graph. Namespaces and PSR-4 autoloading replace fragile path matching, while composer.lock records the exact versions used for a release. The application and its operating system become easier to describe, rebuild and audit as a single unit.

The difficult work sits at the boundaries. A legacy global function may belong in an autoloaded files entry, a class may need a namespace before it can be discovered, or a bundled library may conflict with the version available through Packagist. The external technical material can provide a second reference while the team compares those choices, but the application’s own tests must remain the authority on behaviour.

Composer also changes how releases are operated. A production deployment should install from the committed lock file rather than resolve fresh versions during the release. This creates a repeatable package baseline and makes rollback a matter of restoring a known artefact, not hoping a remote registry still serves the same code.

Mapping dependencies before changing code

Start with an inventory rather than a composer require command. Search for require, include, get_include_path, dynamic class names, registered shutdown functions and extension checks. Record PEAR and PECL dependencies separately because some PECL modules still need to be installed by the operating system or container image rather than by Composer.

Next, translate the dependency map into composer.json. Keep production requirements in require, development tools in require-dev, and use replace, provide or conflict only when the relationships are genuine. Packages that were once copied into the repository should be classified first: a clean upstream release may become a normal requirement, while an internally modified fork may need a secured VCS or artefact repository. Composer plugins and package scripts can execute code during installation, so review them with an explicit allow-plugins policy and keep registry credentials in CI secrets.

Before refactoring names or includes, capture the current behaviour. Exercise login, checkout, scheduled jobs, email delivery and administrative workflows with automated tests where possible. Add PHPStan or Psalm as a baseline rather than treating every existing warning as a migration failure. A measured baseline lets the team distinguish pre-existing defects from regressions introduced by the package conversion.

A practical migration sequence

The first wave should be small enough to deploy and reverse within one business day. Pick one module with clear ownership and limited use of global state, then move through the following sequence.

  • Record the current PHP version, enabled extensions, environment variables, cron jobs, writable paths and public URLs.
  • Create or correct the relevant entries in composer.json, keeping development-only tools out of the production dependency set.
  • Convert directory classes to namespaced PSR-4 paths and load procedural function files through an explicit autoload.files entry.
  • Deploy to a staging or canary environment, compare outputs and remove the old include or PEAR installation only after the new path is proven.

Once the module works, make the checks part of continuous integration. The same commands should run locally and in the build pipeline, with failures stopping publication of an artefact.

  • Run composer validate --strict to catch malformed metadata and abandoned package constraints.
  • Run composer install --no-dev --prefer-dist --no-interaction to build from the committed lock file.
  • Run composer check-platform-reqs and composer audit, then execute unit and integration tests plus the PHPStan suite.
  • Build an immutable deployment artefact, store its checksum, and promote that exact artefact through staging and production.

Treat exceptions as recorded decisions. A private repository, an unusual extension or a package that cannot meet the supported PHP baseline may require a wrapper service, a container change or a temporary fork. Every exception needs an owner, a reason and an expiry date instead of remaining hidden in an old server configuration.

Aligning packages with production infrastructure

Application dependencies are only half of the runtime. PHP extensions, ICU data, OpenSSL, timezone files and the PHP-FPM or CLI configuration must match the versions used during testing. Container images can make this repeatable by installing extensions in the image, locking the base image and running Composer in a controlled build stage. The production host should receive the built artefact, not build unreviewed packages while traffic is flowing.

For containerised workloads, my notes on container monitoring with cAdvisor show how runtime measurements can be collected alongside deployment metrics. Container CPU and memory figures are useful, but PHP request latency, worker saturation, database time and queue depth still need application-level visibility. A migration is complete only when the team can see whether the new package layout changed performance.

Australian infrastructure choices add constraints that are easy to miss in generic advice. Many local hosting providers still offer cPanel and Plesk environments, where the available PHP version and extensions may differ from those in a current container image. Sydney and Melbourne cloud regions can keep latency low for local users, yet backups, support access and disaster recovery may cross borders. If the system handles personal information, document its Australian Privacy Act 1988 and Australian Privacy Principles obligations, including breach-response responsibilities. Also check that daylight-saving changes in eastern states cannot cause cron-based batch jobs or expiry logic to run twice or miss a scheduled window.

A controlled rollout and next step

Release the migrated code behind a feature flag or route a small share of traffic to it while the old implementation remains available. Keep the previous lock file, container image and configuration snapshot together so rollback is a tested operation rather than a hopeful reconstruction. After observation, remove duplicated code and old infrastructure deliberately; carrying both paths indefinitely makes the next upgrade harder.

The same separation of trust and traffic applies to supporting networks. My explanation of VLANs and managed switches covers a practical approach to segmenting management, storage and user devices, whether the project is a home lab or a small business environment. Composer does not secure an exposed PHP application by itself, so firewalling, patching and least-privilege access belong in the same migration plan.

If you want outside help with this work, I provide independent infrastructure planning, cloud migration and systems architecture advice. Contact me through this site’s contact details to arrange a scoped review of the legacy code, Composer manifest, CI pipeline and target hosting environment. The engagement can deliver a staged package baseline, a verified rollback path and deployment documentation your team can operate with confidence.

Experience

Information Technology Consulting

Independent Practice

Provides IT consulting services focused on infrastructure planning, cloud migration strategy, and systems architecture. Engagements draw on years of hands-on sysadmin and development experience across Linux, Windows, and hybrid environments.

K9 Search & Rescue Volunteer

Ongoing

Active participant in K9 Search & Rescue operations, combining technical logistics skills with field support for canine search teams.

Karl Katzke's Blog

October 2006 – May 2014

Published a long-running personal technology blog covering cloud vs. in-house infrastructure, F# and Mono on OSX, hardware vendor critiques, RAID card performance analysis, and sysadmin storytelling. Notable posts include "When Sysadmins Ruled the Earth" (May 15, 2014) and "Getting Started with F# and Mono on OSX" (December 22, 2012).

Credentials

A small badge icon with a shield shape in muted blue tones on a light background

Systems Administration

Deep experience with Linux (RHEL, SLES, CentOS), high-availability clusters, and STONITH configurations.

A small badge icon with a gear shape in muted blue tones on a light background

Cloud Infrastructure

Practical knowledge of AWS EC2, reserved instances, and cost analysis for cloud vs. on-premises deployments.

A small badge icon with a code symbol in muted blue tones on a light background

Development

Proficient in F#, PHP (Symfony), and cross-platform tooling including Mono and MonoDevelop on OSX.

Studies

F# & Functional Programming

Self-directed, 2012

Explored strongly typed functional programming with F# on OSX using the Mono runtime. Published a detailed getting-started guide covering toolchain setup and cross-platform game development research.

High-Availability & Cluster Management

Professional Development, 2009

Configured and documented crm_mon email alerting for STONITH events on SLES11-HAE clusters, integrating with Nagios monitoring for production environments.

Hardware & Storage Performance

Ongoing

Conducted hands-on benchmarking of SATA/SAS RAID controllers including HighPoint RocketRaid 2740 and LSI/SuperMicro AOC-USASLP2-H8iR, comparing against software RAID configurations.

Skills

A small icon representing a server with clean geometric lines in slate blue

Linux Administration

RHEL, SLES, CentOS — package management, kernel tuning, HA clustering, and monitoring integration.

A small icon representing a cloud shape with clean geometric lines in slate blue

Cloud Architecture

AWS EC2, reserved-instance planning, cost modeling, and hybrid infrastructure strategy.

A small icon representing code brackets with clean geometric lines in slate blue

F# & .NET/Mono

Functional programming on OSX, MonoDevelop toolchain, and cross-platform game-dev exploration.

A small icon representing a database cylinder with clean geometric lines in slate blue

PHP & Symfony

Web application development with the Symfony framework and the broader PHP ecosystem.

A small icon representing a storage drive with clean geometric lines in slate blue

Storage & RAID

SATA/SAS controller evaluation, md RAID configuration, and performance benchmarking.

A small icon representing a shield with clean geometric lines in slate blue

High Availability

Pacemaker, STONITH, crm_mon alerting, and Nagios integration for production cluster monitoring.