Getting started
Migrating from PHP
Introduce ++PHP to an existing Composer application one useful boundary at a time.++PHP is designed for gradual adoption. You do not have to rewrite an application or replace its packages, runtime, or deployment model. Keep existing .php files, choose one boundary that would benefit from a stronger contract, and move that file to .ppphp when you are ready.
This guide covers a Composer application that owns its own layout: a service, a package, an application built directly on Composer. Applications built on a framework such as Laravel need the framework integration that is in the pipeline; it is not covered here yet.
Start by proving the build
Before changing source syntax, configure ppphp.json and make sure a complete build preserves the behavior of the existing application. Ordinary PHP files remain valid input and are copied byte-for-byte into build/ppphp.
vendor/bin/ppphp init
vendor/bin/ppphp composer:configure --dry-run
vendor/bin/ppphp composer:configure
composer update --lock --no-interaction --no-scripts
composer dump-autoload --optimize
vendor/bin/ppphp check
vendor/bin/ppphp build
Run your normal tests and entrypoints from the generated tree. This establishes a working baseline before any file adopts ++PHP syntax.
Choose one useful boundary
Pick a small service, value object, or domain boundary with tests and reasonably clear inputs and outputs. Avoid starting with the most dynamic file in the application. Rename only that owned source file:
src/CheckoutService.php
↓
src/CheckoutService.ppphp
The surrounding .php classes do not need to move. Their native declarations and PHPDoc remain available while the compiler checks the new .ppphp file.
Make the existing contract explicit
Add explicit types for parameters, properties, return values, and local variables. Write nullability where null is a real state. Replace assignment that implicitly creates a local with a typed declaration:
string $customerId = trim($request->customerId);
?Customer $customer = $repository->find($customerId);
readonly int $retryLimit = 3;
array<Order> $orders = $customer?->orders ?? [];
Use broad types such as mixed, object, or bare array when the boundary is genuinely dynamic. The point is to state what the code knows, not to pretend uncertain data is precise.
Run a focused check while working:
vendor/bin/ppphp check src/CheckoutService.ppphp
The report stays focused on that selection while the compiler still reads related project and package declarations needed to understand it.
Improve relationships when they pay off
Once the file satisfies the basic contract, add richer types where they solve a real maintenance problem:
- Use typed arrays when keys or element types matter to callers.
- Use generics when a reusable API must preserve a caller-selected type.
- Use checked errors when callers need to make an explicit recovery decision.
- Use
whenexpressions when a conditional branch must validate, calculate, iterate, or record work before producing its value. - Use typed locals and
readonlyso local state keeps one clear meaning.
PHPDoc can strengthen adjacent PHP before those files move. A precise @param, @return, template, or @throws declaration gives the ++PHP caller more information without changing how the PHP file runs.
Build and test the complete application
A focused build is useful for inspecting one generated file, but deployment requires the complete tree:
vendor/bin/ppphp check
vendor/bin/ppphp build
composer dump-autoload --classmap-authoritative
vendor/bin/phpunit
Deploy and execute build/ppphp, not the .ppphp source tree. Composer should load the generated namespace mapping maintained by ppphp composer:configure.
Expand at the pace the codebase earns
Convert the next file when its stronger contract will make review, refactoring, or failure handling better. A successful migration is not measured by the number of renamed files. It is measured by whether the contracts catch useful mistakes without making the application harder to operate.
See Mixed projects for detailed build behavior and Installation for the complete setup.