Back to blog

PHP development

PHP Composer and Packagist: How Dependency Management Really Works

Understand Composer, Packagist, composer.json and the lock file, with safer ways to choose, update and deploy PHP dependencies.

One command can add thousands of lines of third party code to a PHP application. The installation is easy. Controlling versions, reproducing builds and maintaining that code safely is the real dependency work. Here is how Composer, Packagist and the lock file fit together.

01

Composer and Packagist do different jobs

Composer is the dependency manager that runs for a PHP project. It reads the requirements in composer.json, works out a compatible set of packages, downloads them and creates the autoloader the application uses to find their classes.

Packagist is the main public package repository used by Composer. It lists PHP packages, their available releases and where the package code can be downloaded. Packagist does not install anything into the application and it does not decide whether a package is suitable for a particular business.

The parts of PHP dependency management
PartWhat it controlsWhat it does not prove
ComposerResolving, downloading and autoloading PHP dependenciesThat a package is appropriate, secure or well maintained
PackagistPublic package names, releases and download metadataThat the most popular result is the right choice
composer.jsonThe project's allowed dependency ranges and configurationThe exact versions used by one tested build
composer.lockThe exact resolved package versions and referencesThat those versions will remain safe without future review
vendorThe installed files used by the applicationA source of truth that should normally be edited or committed

The package code usually lives in a source repository such as GitHub or GitLab. Composer may download a prepared distribution archive or fetch the source, depending on the package and configuration. Packagist provides the catalogue information that connects the package name and release to that source.

That distinction matters when something goes wrong. If Composer cannot resolve a set of compatible versions, inspect the project constraints. If a package release is unavailable, inspect its repository metadata and source. If production behaves differently from testing, check whether both environments installed from the same lock file and met the same PHP extension requirements.

02

composer.json states the policy

The composer.json file describes what the project is allowed to use. A requirement such as ^3.2 expresses a version constraint rather than one exact release. Composer can select a compatible version inside that range while also satisfying the requirements of every other package.

Those other requirements matter. A project may directly require ten packages while the installed dependency tree contains many more. Each direct package can depend on further packages, which may have dependencies of their own. These are transitive dependencies, and they become part of the application operational surface even though nobody typed their names into the root file.

The root file also separates production requirements from development tools. Test frameworks, static analysis and code formatting normally belong in require-dev. A production installation can then omit them. This reduces what is deployed, although it does not remove the need to secure development and build environments.

Version constraints should be deliberate. A constraint that is too broad can admit a larger change than the team intended. Pinning every package to one exact version inside composer.json can make safe updates and dependency resolution needlessly awkward. For an application, the usual balance is to express a sensible compatibility range in composer.json and record the tested exact result in composer.lock.

03

composer.lock records the tested outcome

When Composer resolves dependencies, it writes the exact package versions and references to composer.lock. That file should be committed for an application. Another developer or a deployment pipeline can then run composer install and receive the same versions rather than asking Composer to make a new decision.

Imagine a customer portal that uses a package to create PDF statements. The project allows compatible releases in the 4.x series. Version 4.5.2 passes review and testing, so the lock file records it. A production deployment next week should install 4.5.2 from that lock, even if 4.5.3 has appeared in the meantime.

The newer release may be perfectly good. It simply has not travelled through this application test and release process yet. Reproducibility is the value of the lock file: the dependency decision is made during controlled development, not by a production server in the middle of a deployment.

The vendor directory is different. It is generated from the dependency files and will usually be excluded from version control. Editing a file in vendor creates a local change that disappears on the next clean installation. If a package needs a fix, contribute it upstream, use a maintained fork or apply a controlled temporary patch with a clear removal plan.

04

install and update are not interchangeable

The command names sound similar, but their intent is different. composer install uses the exact versions in composer.lock when that file exists. This is the normal choice for another developer, continuous integration and deployment. It reproduces a dependency decision that has already been made.

composer update asks Composer to resolve allowed versions again and rewrite the lock file. It may change direct packages and packages several levels down the dependency tree. That is a development change and deserves the same review and testing as an application code change.

A broad update can be useful during a planned maintenance cycle. It is rarely the best response to one package needing a security fix. Composer supports targeted updates so the team can change a named package and the dependencies that genuinely need to move with it. A smaller diff is easier to understand, test and reverse.

Do not run an unrestricted update on a production server. It makes the live environment the place where a new dependency set is chosen. Two servers deployed minutes apart could resolve different releases, and the application may start running code that never passed the normal checks.

05

Choosing a package takes more than a download count

Packagist makes discovery quick. The first search result is not automatically the best engineering choice. Popularity can show that many people have encountered the package, but it cannot answer whether it fits the application requirements or the team ability to maintain it.

Start with the purpose. A small package that does one stable job may be a better fit than a large framework added for one helper method. Read the documentation and public interface. Check the supported PHP and framework versions, release history, licence, testing approach and the amount of additional code pulled into the dependency tree.

Maintenance signals need context. Recent activity is useful, but a mature package solving a stable problem may not need weekly releases. Look at how maintainers handle issues and security reports, whether important changes are explained and whether the package has an abandoned notice or a credible successor.

Then consider the exit. If the library stops being maintained, how difficult would it be to remove or replace? A date formatting helper and a package woven through authentication, billing and stored data create very different levels of dependence.

This is not an argument for building everything internally. Well chosen packages reduce duplicated work and let a team use established solutions. The point is to treat selection as an architectural decision proportional to the role the package will play.

06

A dependable production workflow is intentionally boring

A safe workflow moves the same reviewed lock file through development, testing and production. The exact commands vary by platform, but the responsibilities remain consistent.

First, validate the Composer files. Continuous integration should start from a clean checkout, run composer install from the lock file and execute the project tests and static checks. If a dependency change modifies the lock file, that diff belongs in the code review.

Composer audit checks installed or locked packages against security advisory data and can also report abandoned packages. It is valuable in continuous integration and during regular maintenance. A clean audit is evidence about known issues at that moment, not a promise that the entire dependency tree is safe.

The production environment also needs the correct PHP version and extensions. composer check-platform-reqs checks the real platform against the requirements of the installed packages. This catches the unpleasant case where a build succeeds elsewhere but the server is missing an extension used at runtime.

A typical production installation may omit development packages, prefer distribution archives and optimise the autoloader. These flags improve the deployed result, but they do not replace a tested release process. Avoid hiding platform errors with ignore-platform-reqs. If the server cannot meet the package requirements, the deployment should stop before customers discover it.

Composer scripts and plugins deserve particular care because they can execute code during Composer operations. Review which plugins the project allows and understand scripts inherited from packages. Dependency installation is not merely copying passive files.

07

Dependency security is an ongoing job

The lock file narrows uncertainty, but it also preserves old versions until the team deliberately changes them. That is why reliable applications need both reproducible installations and a routine for reviewing updates.

Composer and Packagist include useful protections. Packagist locks the source and distribution references of stable releases so a maintainer cannot quietly move an existing stable tag to different code. Composer can audit the dependency set and current Composer policy can block affected package versions during dependency changes.

Private repositories introduce another risk. Composer searches repositories in priority order, and repositories are canonical by default. Keeping an internal package name inside a higher priority canonical private repository helps prevent dependency confusion, where somebody publishes a higher numbered public package using the same name.

Credentials must stay out of composer.json and version control. Composer supports project or global authentication files, environment based configuration and several token methods. Use the least access required, prefer short lived credentials where the provider supports them and make sure a project level auth.json is ignored by Git.

Security also includes operational ownership. Know who receives advisory alerts, how quickly an important dependency can be upgraded and what evidence is required before releasing the change. A warning without an owner becomes another notification everybody assumes somebody else has read.

08

Private packages can still use the Composer model

Not every useful package belongs on public Packagist. A business may share internal billing, authentication or integration code across several applications without making that code public.

Composer can use additional repositories declared by the root project. A private version control repository can be enough for a small number of packages. Larger organisations may use a hosted private Composer repository, Private Packagist or a self hosted tool such as Satis to provide a controlled catalogue and cached distributions.

The design decision is not simply where the files are stored. Private packages need versioning, ownership, documentation, tests and a release process. If every application points at the moving head of an internal branch, the package is shared but the builds are not predictable.

Repository definitions also belong in the root application. Composer does not recursively load repository settings from dependencies, which keeps the consuming project in control of where its code may be obtained.

09

Keep the dependency tree small enough to understand

Dependencies accumulate quietly. A package added for a short lived feature can remain years after the feature disappears. Another may be used for one function that modern PHP now provides directly.

Review the tree rather than only the direct list. Composer why helps explain which package requires another, while outdated identifies available updates. Remove unused packages through Composer so the dependency files and installed tree change together.

A useful maintenance rhythm includes regular advisory checks, planned update windows and earlier action on packages central to security or business continuity. Apply changes in small, reviewable groups and test the behaviour the package supports, not merely whether the application boots.

The right goal is not zero dependencies. That would often mean maintaining more custom code and recreating solved problems badly. The goal is an intentional dependency set where every important package has a reason to exist, a controlled version and a credible route forward.

10

Composer makes reuse possible, ownership makes it safe

Composer and Packagist have transformed PHP development because they make good shared code easy to discover and install. The benefit is real. So is the responsibility that arrives with it.

Composer manages the dependency decision. Packagist helps the project find releases. composer.json states what is allowed, while composer.lock records the exact result the team tested. None of them removes the need to review packages, protect credentials, monitor advisories and rehearse dependency changes before production.

Treat every important package as part of the application rather than somebody else's code sitting nearby. That small change in thinking produces quieter deployments, clearer upgrades and software the business can continue to trust.

Useful questions

Before adding or updating a PHP package, check:

  • Does the package solve enough real work to justify the new dependency?
  • Is its public interface a good fit for the application rather than only the demo?
  • Are the PHP, framework and extension requirements compatible with production?
  • Is the licence suitable for the way the software will be used?
  • Are maintenance, releases, security handling and any abandonment notice understood?
  • Has the team reviewed the transitive packages that arrive with it?
  • Will composer.lock be committed and used by continuous integration and deployment?
  • Can the package be updated in a small, tested change rather than on the live server?
  • Are private repository credentials kept out of version control?
  • Is there a realistic plan to replace or remove the package if maintenance stops?
Explore Laravel and Vue development
Daniel Mills

Written by Daniel Mills

Business understanding and hands-on software delivery.

I help owners and teams improve the software they rely on, replace fragile processes and turn new ideas into practical systems people can actually use.