skip to content

In a composer.json, when do you use classmap or files autoloading instead of psr-4, and what does autoload-dev change?

level: middleimportance: should knowfreq 40%

answer

  1. classmap scans .php and .inc
  2. files load on every request
  3. functions cannot be autoloaded
  4. root files load last
  5. autoload-dev is root-only

basics

~20 s

classmap suits code that does not follow PSR-4: Composer scans directories at dump time, so new classes need dump-autoload. files includes function files on every request. autoload-dev holds test-only rules, is root-only and is skipped by --no-dev.

solid answer

~40 s

`classmap` takes directories or files; at dump time Composer scans every `.php` and `.inc` file in them and records each class's path in `vendor/composer/autoload_classmap.php`. It suits legacy or generated code whose file layout does not match its namespaces, and the cost is that new classes need `composer dump-autoload`. `files` is for code PHP cannot autoload at all, mainly **plain functions** and constants: each listed file is included **every time** `vendor/autoload.php` is included, dependencies first and the root package last, so keep them small. `autoload-dev` takes the same keys for test and tooling code; it is **root-only**, so a library's test classes never reach consumers, and `--no-dev` installs leave it out of the generated autoloader. `exclude-from-classmap` keeps paths such as `/Tests/` out of any classmap, including an optimized one.

code

json · 10 lines
json
{
    "autoload": {
        "psr-4": { "Shop\\": "src/" },
        "classmap": ["legacy/"],
        "files": ["src/helpers.php"]
    },
    "autoload-dev": {
        "psr-4": { "Shop\\Tests\\": "tests/" }
    }
}

go deeper

for a junior

Know that functions go in files, legacy classes can use classmap, and tests go in autoload-dev.

for a middle

Explain that classmap is built by scanning at dump time, files load on every request in dependency order, and autoload-dev is root-only and skipped by --no-dev.

for a senior

Keep files entries side-effect free, exclude test paths from classmaps, and migrate classmap code toward PSR-4 when it is touched.

for a principal

Decide how far to invest in moving a legacy codebase from classmap to PSR-4, weighing rebuild friction and ambiguity risks against migration cost.

## Four ways to tell Composer where code lives Composer's `autoload` section accepts `psr-4`, the legacy `psr-0`, `classmap` and `files`. PSR-4 is the default choice because it needs no rebuild when you add classes. The other two exist for code that PSR-4 cannot describe. | Key | Resolves | Rebuild when adding code? | Cost per request | |---|---|---|---| | `psr-4` | class name → path by prefix | no | a filesystem check on first use of each class | | `classmap` | class name → recorded path | yes | an array lookup | | `files` | not by name: always included | yes, when adding a file | every listed file is loaded, used or not | ## `classmap`: for code that does not follow PSR-4 ```json { "autoload": { "classmap": ["legacy/", "lib/Payment.php", "modules/*/lib/"] } } ``` During `install`, `update` or `dump-autoload`, Composer **scans** every `.php` and `.inc` file in the listed paths, finds each class-like declaration, and records its path in `vendor/composer/autoload_classmap.php`. A `*` in a path matches any directory name. Use it for: - legacy code with several classes per file, or file names unrelated to class names; - generated code that lands in one directory; - a library that simply prefers an explicit map. The price: a class added later is unknown until `composer dump-autoload` rescans. If two files declare the same class, Composer warns about an ambiguous class resolution. ## `files`: for what PHP cannot autoload PHP's autoloading works only for **classes, interfaces, traits and enums**. A namespaced **function** or a constant defined with `define()` has no autoload hook, so a package that ships helper functions lists their file under `files`: ```json { "autoload": { "files": ["src/functions.php"] } } ``` Every `files` entry is included **whenever `vendor/autoload.php` is included**, right after the class loader is registered. Order follows the dependency graph: if package A depends on B, B's files load first; ties are alphabetical; the **root package's files load last**. So you cannot use `files` to override a dependency's function; the documentation suggests including your own file *before* `vendor/autoload.php` for that. Because they run on every request, `files` should declare functions and nothing else: no database connections, no configuration loading, no output. ## `autoload-dev`: rules for development only `autoload-dev` accepts the same keys and holds rules for test suites, fixtures and dev tooling: 1. It is **root-only**. Composer reads it only from the project where it runs, so a library's test namespaces are never registered in a consumer's autoloader. 2. It is **skipped** when the autoloader is generated with `--no-dev`, as in `composer install --no-dev`. `dump-autoload` infers dev mode from the last `install` or `update`, and accepts `--dev` or `--no-dev` to override it. 3. Keeping tests out of `autoload` keeps production's maps smaller and stops test classes from being loadable in production. ## Keeping paths out of a classmap `exclude-from-classmap` lists paths, relative to the package root, that the classmap generator ignores, with `*` and `**` wildcards: ```json { "autoload": { "exclude-from-classmap": ["/tests/", "/legacy/fixtures/"] } } ``` It also applies when an optimized autoloader converts PSR-4 rules into a classmap, which is its most common use. ## A legacy migration pattern A common real situation is an older application whose classes live in `lib/` with names like `Shop_Order_Invoice` and no namespaces. A gradual migration looks like this: - keep `"classmap": ["lib/"]` so the old classes keep loading; - add `"psr-4": {"Shop\\": "src/"}` for all new, namespaced code; - move classes from `lib/` to `src/` one area at a time, adding namespaces as you go; - remember that every class added to `lib/` in the meantime needs `composer dump-autoload`, which is the friction that motivates finishing the move. Both rule types coexist in one autoloader, and the classmap is always consulted first. ## Choosing 1. New code: `psr-4`. 2. Helper functions: `files`, one small file per package. 3. Code that cannot follow PSR-4: `classmap`, and remember to dump. 4. Anything test-related: `autoload-dev`.

  • A package lists a 2,000-line bootstrap file under files. Why is that a problem even if the application never calls it?
    Every `files` entry is included each time `vendor/autoload.php` is included, which is every web request and every CLI run. The file is parsed and executed whether or not anything uses it, and any side effects run too. `files` should contain small function declarations only.
  • Can you override a dependency's helper function by declaring the same function in your own files entry?
    No. Root `files` are included last, after dependencies, so the dependency's function is already declared and a second declaration is a fatal error. Composer's documentation suggests including your own file before `vendor/autoload.php` if you really need to define the function first.

saying these in an interview costs you the question

  • Namespaced functions can be autoloaded with psr-4 like classes
  • files entries are loaded lazily, only when a function from them is called
  • A library's autoload-dev rules are registered in the projects that install it
  • Classes added to a classmap directory are found without dump-autoload
  • The root package's files are loaded first so they can override dependencies