Autoloading with Composer

Using the Composer autoloader with namespaced classes and non-namespaced classes.

By Bob Ray  |  March 11, 2025  |  4 min read
Autoloading with Composer

In the previous article, we looked at using namespaces with an autoloader. In this one, we'll look at using Composer to create an optimized autoloader for our project.

Composer

Composer is a dependency manager for PHP code. There's a nice description of it here, along with instructions on how to install it on various platforms.

One reason Composer became so popular, is that it made it much easier to install and maintain third-party packages. Instead of downloading a package like the Monolog logging system and putting its files in place, you can simply type "Composer require Monolog/Monolog." That's it. Monolog not only gets installed, you can update it to a new version at any time with Composer by typing "Composer update Monolog/Monolog".

A second reason Composer became so popular is that it makes it much easier to use those installed packages, by providing a built-in autoloader for every package. Before Composer, people had to add long lists of include or require statements to use the classes and other parts of the package in their code. With Composer, they can simply "require" the vendor/autoload file provided with the package and use every class in it just by using $someClass = new SomeClass();. The autoloader will find the class file and include it. Depending on your command to Composer to create the autoloader, it may already know where the class is.

We'll see how to use Composer to create the autoloader in a bit, but first, we'll review the guidelines for using a PSR-4 autoloader. We've been following them, but we haven't really spelled all of them out. Composer and the PSR-4 guidelines work well with namespaced files, but they're not required for Composer to create an autoloader. Since we already have namespaced files from the previous article, we'll do those first. Later, we'll look at using Composer on non-namespaced files.

Autoloader Recommendations

The PHP-FIG group is a collection of people who know a lot about PHP and interoperability (PHP-FIG stands for PHP Framework Interop Group). Their mission is to create standards that will help PHP users write code that conforms to recommendations that make it easier to read and write PHP code and let different PHP programs and packages work with each other. Periodically, they release numbered PSRs that include those recommendations. PSR stands for "PHP Standard Recommendation."

At this writing, they have released 18 PSRs that you can view here.

Two of the PSRs deal with autoloaders: PSR-0 and PSR-4. We'll be dealing with PSR-4, the latest autoloading recommendations, though some projects still use PSR-0 autoloaders. PSR-4 is more flexible than PSR-0, and autoloaders that implement it are capable of finding class files without being told where they are. If you looked at the list or PSRs you may have noticed that PSR-O is not there. If you're curious, you can see it here.

You can see the full PSR-4 text here. It's not very long.

Here's a quick summary:

  • A fully qualified namespace begins with a VendorName

  • The VendorName part of the namespace may be followed by one or more SubNamespaceNames

  • The parts of the namespace should be separated by backslashes ()

  • The namespace must end with the name of a class

  • The letters in a fully qualified class name may be (but don't have to be) a mixture of upper-case and lower-case letters

  • The file holding the class code must be in the form: ClassName.php (the form Classname.class.php, (as you'd see in MODX 2) for example, would not be allowed)

    • The case of the filename must match the case in the namespace

    • The beginning of a fully qualified namespace (which may, or may not, include one or more sub-namespaces) should correspond to at least one "base directory"; it can be considered a "namespace prefix"

  • It's not explicitly stated in PSR-4, but in practice the namespace prefix has to correspond to a base directory (or several), but the directory doesn't have to have the same name as the namespace prefix.

  • The parts of the namespace after the namespace prefix should correspond to subdirectories under the prefix's base directory, with the backslashes in the namespace corresponding to directory separators in the path to the class files

  • The case of the subdirectories in the rule above must match the case of the sub-namespaces

  • Autoloaders should not throw exceptions or raise errors

Let's look at the namespaces and directory structure from our VeterinaryOffice project in light of the guidelines above. Here's the directory structure:

/* The base path is the full path to the VeterinaryOffice
   directory; it ends in a slash */
PROJECT_BASE_PATH
    src/
        Controllers
            Controller.php
        Model
            Pets
                Dog.php
                Cat.php

Here's a sample class file:

<?php
namespace VeterinaryOffice\Model\Pets;

class Cat {
    function __construct() {

    }
    function speak() {
        return 'meow';
    }
}

Our namespace prefix is VeterinaryOffice, and it begins every namespace line and every fully qualified class name. Although VeterinaryOffice matches a directory name, the path to that directory will not be the base path for our autoloader. That base path will be the path to the src/ directory. We'll set that up in the instructions to Composer for creating the autoload file (more on that in a bit).

The namespace prefix can, and often does have a sub-namespace. Ours could have been Veterinary\Office, but there was no reason to do that. Official Composer packages generally have a two-part namespace (we'll see why a little later in this article), but since our project will never be distributed to the world, we kept it simple.

The case of the Cat class and the case of the Cat class file Cat.php match. The fully qualified name of the Cat class is VeterinaryOffice\Model\Pets\Cat. If we replace the namespace prefix with src/ and convert the backslashes to directory separators (usually '/'), and add the .php suffix we have an actual, relative path to the file (relative to our VeterinaryOffice directory).

So we're in pretty good shape with respect to the PSR-4 Guidelines. We'll leave the issue of not throwing an error in the autoloader to Composer, which will be creating the autoloader.

The composer.json File

Composer can create a wide variety of autoloaders, so we need a way to tell it what kind we want. That's done in the composer.json file. The composer.json file can provide lots of information to Composer about what packages to install, what versions of them we want, and how to update them. Here, we're concerned with a small part of the composer.jsonfile: the autoload section.

The composer.json file is written in the style of a JavaScript language concept called JSON, which stands for JavaScript Object Notation. It's used mainly for storing data. JSON objects are surrounded by a set of curly braces ({}), and you'll find one at the beginning and end of the file (there will be more of them inside).

Subsections of the file are also surrounded by curly braces, and there is a comma (,) separating the sections. The colon (:) is used in the way => is used in PHP — to connect a key to a value. In many cases, you can think of it as an = sign.

Sections of the file are separated by commas (,) but unlike a PHP array, you can't put a comma after the last section.

Here's a simple example of a composer.json file that just specifies the details for the autoloader (it could be the whole file, in fact it will be for our examples):

{
  "autoload": {
    "psr-4": {
      "VeterinaryOffice\\": "src/"
    }
  }
}

The code above tells Composer that we want a PSR-4 autoloader. It gives our namespace prefix, VeterinaryOffice\. There are two backslashes in the prefix specification because all backslashes in JSON have to be escaped with a second backslash. The next line after the psr-4 line "maps" that namespace prefix to the src/ directory, so that's where the autoloader will expect to find our files.

Notice that there are no full paths in the file. The composer.json file is at the same directory level as the vendor/ directory (directly under the VeterinaryOffice/ directory). Composer assumes that the base path we've specified (src/) is in the same directory as the composer.json file (in other words, the VeterinaryOffice/ directory. It doesn't do this because of the namespace prefix in the file. The prefix is only used to tell it the prefix on our namespace. If we renamed the VeterinaryOffice/ directory. Our autoloader would still work fine, as long as we didn't change the directory structure or the namespace scheme.

The command to composer that tells it to create (or update) the autoload file is very simple:

composer dump_autoload

Optimization

In a previous article, we created a crude classmap for our own autoloader. The classmap was an array where the keys were class names, and the values were the paths to each class file. We can tell composer to make a classmap like that for us by adding -o to the dump-autoload command:

composer dump-autoload -o

If you run that command successfully, you can look at the vendor/composer/autoload_classmap file and see the classmap.

You can try this with our Veterinary Project files. It's a good test of your namespace and directory structure because, while creating the classmap, Composer will display an error for any class files that do not satisfy the PSR-4 rules. If, for example, you change the name of our Cat class file to Cat.class.php, Composer will display a message like this :

Class VeterinaryOffice\Model\Pets\Cat located in C:/xampp/htdocs/addons/VeterinaryOffice/src\Model\Pets\Cat.class.php does not comply with psr-4 autoloading standard. Skipping.

A close examination of the error message will often tell you exactly what's wrong, though sometimes it's still not clear what the issue is. In the error message above, you can see that the name of the class isn't the same as the name of the file.

When the command has finished, the other files will be listed in the classmap, but the Cat class will be missing from the list.

The nice thing about PSR-4 autoloading is that as long as the file is under the specified location (the src/ directory, and meets the PSR-4 standards, the autoloader will still load it. It does this by walking through every file in the src/ directory looking for a class called Cat (assuming that the Cat class file has the proper namespace). As you might imagine, this can slow things down, especially for large projects. Before putting your code to work on a production server or a live website, you want to make sure that every class is in the classmap.

Even Faster

You can speed things up even more by telling Composer that the classmap is authoritative by replacing the -o in the code above with -a. That tells the autoloader that if a class file isn't in the classmap, it shouldn't walk the files looking for it. Obviously, you should never use this option unless you're absolutely sure that all your class files will make it into the classmap.

Using -a automatically creates the classmap, so using -a and -o together is unnecessary.

Faster Yet

Even more speed can be gained if you tell Composer to have the autoloader use the APC User cache (APCu) to store the class files in its cache. Important: this will only work if you have the APCu cache installed and enabled on your server.

Here's the command for that. It can't be used with -a or -o, but if the classes are cached, there's no need for a classmap:

composer dump-autoload --apcu

Mapping Multiple Directories to one Namespace Prefix

Suppose our VeterinaryOffice project also had class files in an Includes/ directory under the VeterinaryOffice/ directory. There are several ways to make our Composer autoloader handle that situation.

If possible, just move the Includes/ directory under the src/ directory and change the namespace in each class file to VeterinaryOffice\Includes\ ..., with the dots replaced by either the class name or one or more directory names followed by the class name.

Sometimes that's not possible. For example, some or all of the files in the Includes/ directory may be from a third party. In that case you don't want to mess with their namespaces.

Another very easy (but usually not the best) way to go is to change your composer.json autoload section to something like this:

{
  "autoload": {
    "psr-4": {
      "VeterinaryOffice\\": ["src/",""]
    }
  }
}

The classes in the Includes/ directory would have to use this namespace line:

namespace VeterinaryOffice\Includes;

And you'd have to use that namespace when referring to them in your code.

With the new composer.json autoload section above, our project now has two base paths. The square brackets ([]) create a JSON array with two members, "src/" and "". The first one points to the src/ directory as it did before, the second one points to the root directory of our project (the VeterinaryProject directory).

When Composer is creating the classmap, it will look in the src/ directory, just as it did before, and save the class names and paths there. When it's finished, it will walk through the entire project (including the src/ directory) looking for classes it missed and complaining if it finds any that don't meet the PSR-4 standards.

A better method, is to create a separate line in composer.json that essentially creates a second namespace prefix for the files in the Includes/ directory:

{
  "autoload": {
    "psr-4": {
      "VeterinaryOffice\\": "src/",
      "VeterinaryOffice\\Includes\\": "Includes/"
    }
  }
}

The files would still have VeterinaryOffice\Includes namespace we saw in the previous section, and you'd still have to use that in referring to them. Here's an example.

Class File:

<?php
namespace VeterinaryOffice\Includes;

class SomeClass {
    function __construct() {
        /* constructor code here */
    }
}

Code to use it:

<?php
namespace VeterinaryOffice; /* Optional */

use VeterinaryOffice\Includes\SomeClass;

$someClass = new SomeClass();

More complete composer.json File.

The composer.json file has several jobs. Directing the creation of the autoloader is just one of them, so you'll often see other things in them. Just to show you what that looks like, here's a stripped down version of one of the composer.json files in the GuzzleHttp project. You can see the autoload section at the end. Notice that the sections are separated by commas:

{
    "name": "guzzlehttp/psr7",
    "type": "library",
    "description": "PSR-7 message implementation that also provides common utility methods",
    "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "[email protected]",
            "homepage": "https://github.com/mtdowling"
        },
        {
            "name": "Tobias Schultze",
            "homepage": "https://github.com/Tobion"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "psr/http-message": "~1.0",
    },
    "require-dev": {
        "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10",
        "ext-zlib": "*"
    },
    "suggest": {
        "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Psr7\\": "src/"
        },
        "files": ["src/functions_include.php"]
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Tests\\Psr7\\": "tests/"
        }
    }
}

The autoload-dev section at the end adds autoloading for unit test files in your development environment.

You can look here to see the official docs for composer.json. The composer.json file can also have a config section that may be very simple, or quite complex. You can read about that here.

Using Composer's Autoloader with Non-namespaced Code

You're free to create your own autoloader, as we saw two articles ago, but by now you may have come to appreciate the Composer PSR-4 autoloader. In this section, we'll see how to use it with non-namespaced code, in case you have some non-namespaced classes that you don't want to rewrite.

We'll continue to use our VeterinaryOffice code. For this section, all you need to do is comment out all the namespace lines in the files, and all the use statements. If you're using the PhpStorm code editor, you can select the parts you want to comment out, and press Ctrl-shift-slash (the slash is next to the period on most keyboards). Later, you can uncomment the code by including the comment tags in your selection and pressing the same keys. This process also works in HTML files, CSS, and JavaScript files, providing the correct comment tags for each type of file. In PHP, you can press Ctrl-slash with the cursor at the beginning of a line to convert the line to a single-line comment (//).

Here's a version of composer.json that will work to autoload our non-namespaced files, even files in the Includes\ directory:

{
  "autoload": {
    "classmap": ["src/","Includes/"]
  }
}

If you run the code above with composer dump-autoload, the autoloader will autoload the files, and it will autoload new files that were not present when the command was run. This will be slower because the autoloader will walk through all the files in those directories looking for every new class.

If you run the code above with composer dump-autoload -o, Composer will generate a classmap for the files, which make it much faster, but if you add a new file, it will not be found until you run the command again.

This method will allow you to mix namespaced and non-namespaced files in either directory (even with the -o or -a options). To use the namespaced files, though, you'll have to create a use statement for them, or use a fully qualified class name.

One common strategy is to put all the PSR-4 compliant, namespaced files in one directory (usually src/) and the non-namespaced files in another directory (Includes/ in this example). Then you can do this in your composer.json file's autoload section:

{
  "autoload": {
    "psr-4": {
      "VeterinaryOffice\\": "src/",
      "": "Includes/"
    }
  }

New classes in either directory will be found with or without the -o option. With the -a option, they will not be found until you run composer dump-autoload again.

Composer Packages in the Real World

To simplify things, we put our vendor/ directory inside our project. Packages that are developed, installed, updated, and distributed using Composer have a slightly more complex structure. They are stored in the main Composer repository on the internet, called Packagist. You can read about that repository here.

Packagist packages are required to have a dual name: Vendorname/Packagename. The pair must be in all lowercase. So, if we wanted to distribute our VeterinaryOffice code as a Composer package, veterinaryoffice would be the package name, but we'd have to come up with a unique vendor name. Sometimes the vendor name and the package name are the same (monolog/monolog), sometimes the vendor name is the name of the author (bobray/veterinaryoffice), sometimes the vendor name is the GitHub username of the author (bobray/veterinaryoffice) sometimes, the two parts are similar (guzzlehttp/guzzle).

The second part (the package name) is a lower-case version of the package's primary namespace prefix (veterinaryoffice in our case).

The main point here, though, is that a large project that uses several Composer packages will often have only one vendor/ directory. All the packages will be in that directory, the composer.json file will be in the directory above the vendor/ directory, and the project's autoload files will be in a composer/ directory, directly below the vendor/ directory. The code for each package will also be in the vendor/ directory, with this structure:

composer.json
vendor/
    autoload.php
    composer/
        autoload_classmap.php
       /* other autoload files */
    somevendorname/
        somepackagename/
            src/
                /* class files */

Here's a more concrete example:

composer.json
vendor/
    autoload.php
    composer/
        autoload_classmap.php
       /* other autoload files */
    guzzlehttp/
        guzzle/
            src/
                /* Class files */
    phpmailer/
        phpmailer
            language/
            src/
                /* class files */
    pimple
        pimple
          /src
              /* class files */

That said, it's completely acceptable to put your code outside the vendor directory, and to have your own vendor/ directory and your own composer.json file, as long as their locations conform to where Composer would expect them, as we've done in our project.

Of course if you're not going to use Composer or its autoloader, you're free to ignore all this and put your files wherever you like.

Even in that case, it's not a bad idea to use this structure. It will make it easier for you and others to find things in your code, and if you later decide to start using Composer, it will make the transition infinitely easier.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.