How do PHP CS Fixer's cache file and parallel runner speed up runs, and when can each cause trouble?
answer
- .php-cs-fixer.cache by default
- per-file hash plus a signature
- new rules or version: full rerun
- parallel by default since 3.94
- --sequential and ParallelConfig
basics
~20 sPHP CS Fixer skips files whose content hash matches .php-cs-fixer.cache and reruns everything when the rules or tool version change, and since 3.94 it spreads files across worker processes by default; stale caches and constrained CI machines are the usual sources of trouble.
solid answer
~40 sThe cache, `.php-cs-fixer.cache` unless `setCacheFile()` or `--cache-file` says otherwise, stores a **signature** (exact PHP version, fixer version, indent, line ending, rules) and a **hash per file**. If the signature differs, the whole cache is discarded; otherwise only files whose content hash changed are processed. `--using-cache=no` or `setUsingCache(false)` disables it. The **parallel runner**, default since 3.94, uses `ParallelConfigFactory::detect()` to start one worker per available core (leaving one for the main process), handing each 10 files at a time with a 120-second timeout. Trouble comes from a cache committed to git or shared between different setups, changes that the signature cannot see, and CI containers that report more cores than they get; there you set `setParallelConfig(new ParallelConfig(2))` or pass `--sequential`.
code
php · 11 lines<?php
declare(strict_types=1);
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
return (new PhpCsFixer\Config())
->setRules(['@PER-CS' => true])
->setFinder((new PhpCsFixer\Finder())->in(__DIR__ . '/src'))
->setCacheFile(__DIR__ . '/var/cache/.php-cs-fixer.cache')
->setParallelConfig(ParallelConfigFactory::detect(null, null, 4));go deeper
Recall that PHP CS Fixer caches results in .php-cs-fixer.cache and runs in parallel by default, and that the cache file belongs in .gitignore.
Explain the signature-plus-content-hash design, what invalidates the cache, and how ParallelConfig controls workers, batch size and timeout.
Show you can decide cache and worker settings per environment: git-ignored cache locally, no cache or a persisted one in CI, capped workers in containers.
Balance fast developer feedback against a CI verdict that must not depend on leftover state, and set that policy once for all repositories.
## Why speed matters here PHP CS Fixer has to tokenize and transform every selected file on every run. On a large codebase that takes long enough that developers stop running it. Two mechanisms keep it fast: a **result cache** that skips unchanged files, and a **parallel runner** that uses several CPU cores. ## The cache file Caching is **on by default**. The cache lives in `.php-cs-fixer.cache` in the working directory unless configured otherwise: - `->setCacheFile(__DIR__ . '/var/.php-cs-fixer.cache')` or `--cache-file=...` moves it; - `->setUsingCache(false)` or `--using-cache=no` disables it. It stores two things: 1. A **signature** of the run: the exact PHP version string (so even a patch upgrade counts), the PHP CS Fixer version, the indent, the line ending, the full rules configuration and the version of any rule customisation policy. 2. A **hash of each file's content** after it was last found clean or fixed. On the next run, if the signature is **different**, for example because a rule was added or the tool was upgraded, the whole cache is thrown away and every file is processed. If the signature matches, a file is processed only when its current content hash differs from the stored one. The cache is written progressively, so an interrupted run resumes where it stopped, and it works with the parallel runner. ### When the cache causes trouble - **Committing it.** The file is machine-specific output; add it to `.gitignore`. A committed cache causes pointless diffs and conflicts. - **Sharing it across different environments.** A cache produced under one PHP version is discarded under another, which is harmless but useless, so caching in CI only helps if the job restores the file between runs. - **Changes the signature does not capture.** Only the items above invalidate it. If you edit the code of a custom fixer without renaming it or changing its configuration, clean files will not be re-checked; delete the cache file or use `--using-cache=no`. - **Pull-request checks.** Many teams run `check --using-cache=no` in CI, so that every run is a full, independent verdict. ### Cache behaviour in check mode `check` (dry-run) also reads and updates the cache. A file that is clean has its hash recorded; a file whose content no longer matches a stored hash has its entry **removed** rather than updated, because dry-run did not fix it. The next run therefore examines it again instead of treating unfixed code as clean. ## The parallel runner PHP CS Fixer gained a parallel runner in 3.57 and made it the **default in 3.94**. The default `Config` uses `ParallelConfigFactory::detect()`, which: - counts the available CPU cores and **reserves one** for the main orchestrating process; - starts that many worker processes; - hands out files in batches of **10** (`ParallelConfig::DEFAULT_FILES_PER_PROCESS`); - gives a worker **120 seconds** (`DEFAULT_PROCESS_TIMEOUT`) before treating it as stuck. You can tune it explicitly: | Setting | Effect | |---|---| | `->setParallelConfig(new ParallelConfig(4, 20))` | at most 4 workers, 20 files per batch | | `->setParallelConfig(ParallelConfigFactory::sequential())` | a single process | | `ParallelConfigFactory::detect(null, null, 4)` | auto-detect, but cap at 4 (the third argument is the maximum) | | `--sequential` on the command line | force sequential analysis for one run | ### When parallelism causes trouble - **Containers and CI runners** can report the host's core count while giving the job a small CPU quota. Dozens of workers then fight for two cores, and the run gets slower. Cap `maxProcesses`. - **Debugging a crash** is easier sequentially, because errors come from one process in order; use `--sequential -vvv`. - **Worker startup** needs a writable temporary directory and a PHP binary the main process can spawn; the tool reports a clear error if the temporary directory is not writable. ## Putting it together For a developer machine, both defaults are right: cache on, parallel on. For a CI check that must be a trustworthy verdict, turn the cache off or persist it deliberately, and pin the worker count to the resources the job really has.
- You add a rule to the config. Does PHP CS Fixer re-check files it has cached as clean?Yes. The rules configuration is part of the cache signature, so any change to it, like a tool upgrade or a PHP version change, invalidates the whole cache and every file is processed again on the next run.
- Why can the parallel runner be slower in a CI container?`detect()` sizes the worker pool from the CPU count it can see. A container may see the host's cores while its CPU quota is much smaller, so many workers compete for a little CPU and add process overhead. Capping `maxProcesses`, or `--sequential` on tiny jobs, fixes it.
saying these in an interview costs you the question
- The cache file should be committed so CI starts warm
- Changing the rules requires deleting the cache by hand
- The cache skips files based on modification time
- Parallel mode is opt-in and must be enabled per run
- More workers are always faster regardless of CPU quota