Getting started
Project configuration
Configure source discovery, generated output, analysis stubs, exclusions, and Composer runtime loading.Configure your source directories and build output in ppphp.json. Configure your namespaces and autoloaded files in the root composer.json. Together, they let you edit and check .ppphp source while your application loads the generated PHP through Composer.
This page is the complete ppphp.json reference. Unknown properties are rejected, so a misspelled field cannot silently change the project model.
Start with the generated configuration
Run the initializer in the Composer project root:
vendor/bin/ppphp init
It creates ppphp.json and the configured output, cache, and stub directories. It preserves an existing configuration unless you explicitly pass --force.
{
"$schema": "https://github.com/atatusoft-ltd/ppphp-src/releases/download/2026.3.1-rc-1/ppphp.schema.json",
"source": ["src"],
"output": "build/ppphp",
"cache": ".ppphp-cache",
"targetPhpVersion": "8.4",
"stubs": ["stubs"],
"exclude": ["vendor", "build", ".ppphp-cache"]
}
Paths are resolved from the project root, even when --config selects a file in a subdirectory. Prefer relative paths so the same configuration works in local checkouts, CI, and production builds.
ppphp.json field reference
| Field | Required | Value | Meaning |
|---|---|---|---|
$schema |
No | Non-empty string | Gives editors and JSON tools the schema for this exact compiler release. |
source |
Yes | Non-empty list of unique paths | Selects directories containing application .php and .ppphp source. |
output |
Yes | Non-empty path | Selects the replaceable tree that receives compiled and copied PHP. |
cache |
Yes | Non-empty path | Selects temporary analysis and incremental compilation storage. |
targetPhpVersion |
Yes | Supported version string | Selects the PHP language level used by generated code. |
stubs |
No | List of unique paths | Adds declaration-only .stub.php files for interoperability analysis. |
exclude |
No | List of unique paths | Removes exact paths and directory subtrees from source discovery. |
$schema
The release initializer writes the immutable schema URL belonging to the installed compiler. Editors can use it for completion and immediate validation. The compiler itself validates with its bundled rules and does not fetch this URL during init, check, build, or clean, so normal work does not depend on GitHub or network access.
Keep the URL aligned with the installed release. When upgrading ++PHP, run the new compiler's initializer in a temporary directory or inspect its published schema before changing the field. Do not point a released project at a moving branch URL.
source
Every path names a directory that the compiler searches recursively for .php and .ppphp files. A complete check analyzes this entire source set. A complete build compiles .ppphp files and copies .php files into the output tree.
Use more than one root when application code genuinely lives in separate trees:
{
"source": ["src", "modules/Billing/src", "tests"]
}
The compiler preserves each file's path relative to the source root that owns it. Two roots can therefore attempt to produce the same output path. That collision is reported before output is replaced. Keep roots narrow enough to express ownership clearly, and do not add vendor as application source. Installed packages are understood through Composer metadata instead.
Source roots must stay inside the project and must exist as real directories when checking or building. Directory symbolic links are not traversed.
output
This directory is the runtime application tree. A pathless build prepares the complete replacement in isolation, validates it, and only then replaces the existing output. Do not keep hand-written files in this directory because a successful complete build may replace them.
The output must stay inside the project, cannot be the project root, and cannot overlap source, stubs, cache, or the configuration file. It also cannot pass through a symbolic link. These rules protect source and application files from build and clean operations.
Changing output also changes the destination expected by Composer runtime mappings. After changing it, preview and apply composer:configure again before regenerating Composer's autoloader.
cache
The cache stores analysis results that speed up later checks and builds. You can delete it when no compiler command is running; the next command recreates what it needs. ppphp clean removes it together with generated output after validating both paths.
Give the cache its own directory inside the project. It cannot be the project root, pass through a symbolic link, or overlap source, stubs, or output. Add it to source control ignore rules and to exclude when it sits beneath a broad source root.
targetPhpVersion
This field controls the PHP language level of the generated output, which is separate from the PHP version that runs the compiler. Each compiler release accepts the targets listed in the schema it ships with; ppphp init writes the default for the release you installed, and the compiler rejects a value it does not support (P0007).
stubs
Stub directories provide declarations for APIs that the compiler must understand but should not emit. Files are discovered recursively and only names ending in .stub.php are loaded. Stubs can describe types, generic PHPDoc, and @throws contracts for framework code, extensions, generated clients, or dynamic integrations whose implementation is not ordinary analyzable source.
Stub paths must be real directories inside the project. They are automatically excluded from application source discovery and never copied into output. A stub directory cannot overlap output or cache. Keep runtime implementations in Composer packages or application source, not in the stub tree.
If no project stubs are needed, use an empty list or omit the field:
{
"stubs": []
}
exclude
Each entry excludes one path and everything beneath it from source discovery. Entries are paths, not glob patterns. Use them for generated files, fixtures that are intentionally invalid, vendored application assets beneath a broad source root, or any subtree that is not part of the checked application.
{
"source": ["."],
"exclude": [
"vendor",
"build",
".ppphp-cache",
"var/generated",
"tests/Fixtures/InvalidSource"
]
}
Prefer focused source roots over a project-wide "." whenever possible. Exclusions are easier to reason about when they remove exceptional subtrees instead of reconstructing the application boundary from a long denylist. Stub directories are excluded automatically and do not need to be repeated.
How composer.json fits in
ppphp.json decides what the compiler reads and writes. It does not declare PHP namespaces. Composer's root autoload and autoload-dev sections remain the authority for namespace and file loading.
Before running composer:configure for the first time, point Composer at your source directories. This example assumes both src and tests are listed in source:
{
"autoload": {
"psr-4": {
"App\\": "src/"
},
"classmap": ["src/Legacy"],
"files": ["src/functions.ppphp"]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
Preview the changes, then update Composer to load the generated PHP:
vendor/bin/ppphp composer:configure --dry-run
vendor/bin/ppphp composer:configure
composer update --lock --no-interaction --no-scripts
The command handles project paths beneath configured source roots in psr-4, classmap, and files, in both autoload and autoload-dev. It leaves unrelated mappings and dependency settings untouched. It reads JSON as data and does not run Composer, project scripts, plugins, application PHP, or autoload files.
After the command runs, Composer loads generated PHP from build/ppphp. The original source paths are saved under extra.ppphp so ++PHP can still find your source:
{
"autoload": {
"psr-4": {
"App\\": "build/ppphp/"
},
"classmap": ["build/ppphp/Legacy"],
"files": ["build/ppphp/functions.php"]
},
"extra": {
"ppphp": {
"source-autoload": {
"psr-4": {
"App\\": "src/"
},
"classmap": ["src/Legacy"],
"files": ["src/functions.ppphp"]
},
"source-autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
},
"classmap": [],
"files": []
}
}
}
}
Use extra.ppphp.source-autoload and extra.ppphp.source-autoload-dev to describe where your editable source lives. PHP does not load files from these fields; Composer uses autoload and autoload-dev for that. Each source entry corresponds to a generated-output entry:
| Composer metadata | Used by PHP at runtime | Used by ++PHP for source analysis |
|---|---|---|
autoload.psr-4, classmap, files |
Yes | Alongside preserved metadata when relevant |
autoload-dev.psr-4, classmap, files |
In development installs | Alongside preserved metadata when relevant |
extra.ppphp.source-autoload |
No | Yes |
extra.ppphp.source-autoload-dev |
No | Yes |
PSR-4 values may be a string or a list of paths. classmap and files must be lists. Paths beneath a configured source directory are changed to their generated destinations; paths outside those directories remain unchanged. A .ppphp entry in files becomes the corresponding .php path in output. The command updates only your root PSR-4, classmap, and files entries. Existing PSR-0 mappings and dependency metadata can still help ++PHP find declarations during checking.
Change mappings without losing the source view
After running composer:configure, keep each generated-output entry paired with its saved source entry. For example, App\\ should point to build/ppphp/ in autoload.psr-4 and to src/ in extra.ppphp.source-autoload.psr-4. Conflicting entries produce a diagnostic instead of changing which code your application loads.
When adding, renaming, or removing an application namespace:
- Update the relevant
extra.ppphp.source-autoloadorsource-autoload-deventry to describe the source path. - Add, rename, or remove the matching key in
autoloadorautoload-dev. Its value should point to the generated destination beneathoutput. - Run
composer:configure --dry-runand inspect the result. - Run
composer:configure, refreshcomposer.lock, run a complete build, and then regenerate Composer's autoloader.
Changing only the runtime side can hide the source relationship from analysis. Changing only the preserved side can leave PHP without a matching runtime mapping. Keeping the pair together makes the source and deployed application describe the same namespaces.
A safe configuration workflow
Use this sequence whenever source roots, output, or Composer mappings change:
vendor/bin/ppphp composer:configure --dry-run
vendor/bin/ppphp composer:configure
composer update --lock --no-interaction --no-scripts
vendor/bin/ppphp check --format=json
vendor/bin/ppphp build
composer dump-autoload --classmap-authoritative --no-interaction
vendor/bin/phpunit
Build before generating an optimized or authoritative classmap so Composer sees the complete generated file set. Normal PSR-4 mappings do not need to be regenerated after every source edit, but classmap and files mappings can change when generated files are added or removed.
Commit ppphp.json, composer.json, and composer.lock together when their relationship changes. Production needs the configured output tree, root Composer metadata, and installed runtime dependencies. It does not need .ppphp-cache, project stubs, or the compiler package when development dependencies are omitted.
Validate and troubleshoot configuration
Every project command validates ppphp.json. Use a complete check for the source model and a Composer dry run for the runtime mapping:
vendor/bin/ppphp check
vendor/bin/ppphp composer:configure --dry-run
composer validate --strict
The compiler rejects missing required fields, unknown fields, wrong value types, duplicate paths, unsupported targets, unsafe paths, overlapping managed directories, invalid Composer shapes, and mapping conflicts. Fix the configuration instead of bypassing the diagnostic. --format=json exposes the same configuration failures to CI and editor integrations.
Use --working-directory to operate on another project root and --config to select a different configuration file inside that root:
vendor/bin/ppphp check \
--working-directory=/srv/example \
--config=config/ppphp.json
Continue with The CLI for command selection and exit statuses, or Mixed projects for a practical PHP migration structure.