Building maintainable PHP apps using Composer

Published on by

Building maintainable PHP apps using Composer image
This tutorial brought to you by

Recognize the problem of trying to use somebody’s library and having to copy it into your codebase, use submodules, or even PEAR, which causes headaches for many PHP developers. Composer is here to solve these problems, similar to NPM for JavaScript devs, and Bundler for Ruby devs, by defining a unified way to specify dependencies and install them for you.

Composer has made big waves in the PHP community in recent years. Thanks to Composer’s creators, Jordi Boggiano and Nils Adermann, Composer has become the absolute backbone of PHP’s package infrastructure today.

In this blog post, we shall be introducing Composer, from the ground up. We will see what packages are, how they should be versioned, and how to install them into your application. Learn about Composer and never look back!

What is a package?

Before we dig in, it will be useful to define some terminology. In composer the word package describes both libraries and projects, however, this term is often abused to only refer to libraries. Be careful when reading other resources that may misuse this term.

A library refers to a package that you can install into your application, such as the Laravel Framework, or the Bugsnag PHP Notifier, and a project refers to an actual application. Libraries can be dependencies of other libraries or projects, but a project should never be a dependency of anything.

The Early Days

Composer started out life back in April 2011 as a PHP port of openSUSE’s Libzypp satsolver. Composer was first adopted by the Symfony Framework 2, which shipped in July 2011, and celebrated its first stable release in April 2016, essentially replacing PEAR.

Installing Composer

First things first! You’ll want to install Composer — here’s the documentation on how to do that.

If you’re developing on a Mac, you can simply install Composer with brew:

brew install composer

If you’re on a Linux machine, you can usually do something along the lines of:

curl -sS | php
mv composer.phar /usr/local/bin/composer

Using Composer Packages

Now that you’ve installed Composer, you’ll want to pull in a package for your application or start building your app. To initialize a new Composer package, you need to add a composer.json file to the root directory of your project:

"name": "foo/bar",
"license": "MIT",
"require": {
"php": "^7.0"
"require-dev": {
"phpunit/phpunit": "^5.7"
"autoload": {
"psr-4": {
"Foo\\": "src/"

You can also run the composer init command as a quick start to creating a composer.json file to avoid writing this manually. Here’s how.

By default, projects are libraries; however, if you are looking to build an application, you should set the project type field to project.

In the require field, you can put the libraries and platform dependencies your package needs. For example, if you are using monolog, you could have:

"php": "^7.0",
“monolog/monolog”: “^1.22”

Other valid fields are description, that expects a short textual description of the package, and authors. You can read the full details of all valid fields here.

Understanding package versioning with Composer

It is common for packages to follow semantic versioning; however, this is not a requirement, and one should be careful to check the exact versioning policy packages use. For example, the Laravel Framework does not follow SemVer, rather, it follows an offset variant, whereby minor releases are breaking, and patch releases are used to introduce new features as well as bug fixes, leaving major releases for major architectural changes, and the like. By comparison, the Symfony framework follows SemVer, with some minor caveats.

Semantic Versioning is a modern versioning specification that defines that package versions should be of the form “x.y.z,” with optional stability suffixes. We say that “x” is the major version, “y” is the minor version, and “z” is the patch version. This is useful because it defines a unified versioning system that everybody understands. No working out what 2016.05.5a-tuesday means anymore!

In Semantic Versioning, we require that the major version is incremented whenever there is a breaking change to the code, the minor version is incremented whenever there is a new feature, and patch version whenever there are only bug fixes. It is important to note that during 0.x releases, it is permitted for minor releases to contain breaking changes.

Here you can read the whole spec.

If you are interested in the fine details of all the supported version formats in composer, I’d recommend having a read of Semver, which offers utilities, version constraint parsing, and validation. In particular, reading the test suite would give clarity.

Best practices for installing packages

Now that you have your composer.json file, and have seen how package versioning, you are ready to start installing packages! Composer provides a way to require packages from the command line:

composer require monolog/monolog

Be careful when using this feature, however, since Composer will assume the target package uses semantic versioning when it chooses a version constraint for you. If you’d like to choose your version constraint, you can:

composer require illuminate/support:5.4.*

If you’d like to add things to the dev requirements, you can add the –dev flag:

composer require phpunit/phpunit --dev

You will see that Composer creates a composer.lock file when you install dependencies. This file locks your dependencies at a known state, allowing you to use version constraints in your composer.json file. You should not modify this lock file yourself, however, if you’d like to update your dependencies, you can run the composer update command. The composer install command will install dependencies from the lock file if present, and if not, it will be the equivalent of running composer update.

You can pass the –no-dev flag to the install/update commands to tell Composer that you do not want to install the development dependencies (this will also not do dev-autoload autoloading, as we will see). When deploying in production, the command to use is composer install --no-dev. By default, Composer will install dev dependencies.

It is conventional to commit your composer.lock file with your application, but never the vendor folder Composer generates. This is very large, and Composer is here to solve the very problem of having to commit your dependencies to your codebase.

Release Operators

It is important to note that the ^ operator exactly follows Semantic Versioning. It says get the highest version I can, without any breaking changes. This means ^1.0 resolves to the largest 1.x release, with version at least 1.0.0, and ^1.4.6 means get the largest 1.x release with version at least 1.4.6. Note that ^0.5.6 would mean get the largest 0.5.x release with version at least 0.5.6.

We also have the ~ operator which is similar, but not the same. It’s easiest to see this with some examples. ~1.1.2 means get the largest 1.1.x with version at least 1.0.2. ~1.3 means get the largest 1.x with version at least 1.3.0. This operator does distinguish between 0.x versions like ^ does.

Autoloading: Automagic Class Loading

As seen in the earlier example, there is an autoload field. This allows Composer to manage the autoloading of your code. As soon as you try to use a class that’s not loaded, PHP will go ahead and automatically load it for you. It is common to use PSR-4 autoloading these days. The low down is the directory structure approximately matches your namespacing structure (up to a prefix), and the class/interface/trait name should match the filename. You can read the full specification here.

Under the hood, Composer will register an autoloader with PHP’s spl_autoload_register function, which will cause the callback registered to be called whenever your code tries to reference a class/interface/trait not already in memory. No need to require each file as you need it yourself now. All you need to do is to require one file in your application — the Composer autoloader:


As well as using the PSR-4 autoloading standard, Composer also supports class map autoloading. This is where Composer will scan your files and take note of which classes are in what files, and then, whenever you want to load a class, it will go and require the needed file for you. This could be useful for defining test stubs for example. Another real world example is Laravel’s migration system.

It is also possible to only autoload things when in development. You can use the dev-autoload field to only autoload things such as your tests. For a concrete example of autoloading, dev-autoloading, and class maps in action, take a look at the boiler-plate Laravel application.


We have seen the basics of Composer, what packages are, and how to version them. Now, go and build something great! In our next blog post, we will look at Packagist and the wider PHP ecosystem.

Learn more about Laravel error reporting with Bugsnag.

Graham Campbell photo

Prominent PHP open source developer and StyleCI founder.


Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Forge
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo


The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo


Providing innovation and stability to ensure your web application succeeds.

Shift logo


Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Bacancy logo


Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo


The official Laravel job board

Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with Vue and Livewire stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo


Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies


The latest

View all →
Audit MySQL Databases in Laravel With the DB Auditor Package image

Audit MySQL Databases in Laravel With the DB Auditor Package

Read article
The Ability to Prevent Destructive Commands From Running is Coming to Laravel 11 image

The Ability to Prevent Destructive Commands From Running is Coming to Laravel 11

Read article
Configuring Laravel With Additional Environment Files image

Configuring Laravel With Additional Environment Files

Read article
Real Time Command Line Monitoring With Laravel Top image

Real Time Command Line Monitoring With Laravel Top

Read article
A New Validation Rule and the Ability to Manually Fail a Command in Laravel 11.8 image

A New Validation Rule and the Ability to Manually Fail a Command in Laravel 11.8

Read article
New Array Find Functions in PHP 8.4 image

New Array Find Functions in PHP 8.4

Read article