TypePHP, native PHP, NativePHP: the ecosystem has a vocabulary problem
PHP names things by accretion. A word gets used for something, then reused for something adjacent, then reused again by a project that picked the name because it was available on Packagist. Nobody arbitrates. Ten years later you have a conference hallway conversation where two people agree enthusiastically about “native PHP” and turn out to be discussing entirely different technologies.
Right now we have two live examples running at once: a project name that two unrelated teams shipped in the same year, and a single adjective doing six jobs. They’re the same problem at different scales.
Start with TypePHP
The Swoole team open-sourced TypePHP very recently. Two things are true about it at the same time, and both of them mislead:
- It is an AOT compiler: it converts PHP source to C++17, then to native machine code. No opcodes, no OPcache, no JIT warm-up.
- It is an extended type system:
int,floatandboolcan map directly onto C++ scalars, and it adds typed containers, high-precision numerics, compile-time attributes, and universal methods on primitives.
The name suggests “TypeScript, but for PHP”, a gradual, additive layer you sprinkle on top of code that still runs everywhere. That is not what this is. TypeScript converts to JavaScript. TypePHP compiles to a binary and deliberately supports a subset of PHP: the global scope is declaration-only, binary builds require a main(), and a list of dynamic patterns are documented as unsupported. It is closer in spirit to Hack than to TypeScript.
The compiler itself is written in PHP and is self-hosting: tpc is produced by compiling the compiler’s own PHP source with TypePHP.
Top-level executable statements are rejected in binary mode: a program needs a main(), and executable code has to live in a function or a method. That single constraint tells you more about the project than any benchmark: this is not a drop-in for your existing scripts.
The part that actually pays off is opting into native storage. A function can declare use native_types, at which point int becomes a C++ int64_t and the arithmetic compiles to plain CPU instructions instead of ZendVM dispatch. The project’s own numbers on php-src’s bench.php are roughly 8× against the interpreter, and their typed std::array beats PHP arrays by about 10× on a container-heavy loop. Those are their measurements on their hardware, not a promise.
There are three build modes, which matters later: -m bin (an executable), -m ext (a loadable PHP extension), and -m lib (a shared library). Same source, three artifacts.
And now the other TypePHP
There is a second project called TypePHP. Different author, different repo, different Packagist vendor, no relation.
typephp/typephp by Reymart A. Calicdan, PHP 8.1+ and MIT-licensed, is a transparent, pure-PHP runtime type checker. It reads your PHPDoc annotations, including generics, typed arrays, array shapes, key-of/value-of extractions, and enforces them at runtime, by registering a stream wrapper that AST-transforms files as they’re included. Its README opens with the line “No transpilation. No build steps. No C-extensions.”
Read that next to the Swoole project, whose entire premise is transpilation to C++, a build step, and a C-extension output mode. The two TypePHPs are not just unrelated; on the axis that matters most, they are opposites. One removes the runtime. The other adds work to it.
And here is the sharp part: the userland one is the project people think they’re getting when they hear the name. It really is the “TypeScript for PHP” idea: a gradual layer over code that still runs anywhere. The name you’d expect to belong to project A belongs to project B.
What it catches
The flagship feature is runtime generics, with the bound tracked per object instance in a \WeakMap:
/**
* @template T
*/
class Collection
{
/** @param T $item */
public function add(mixed $item): void { /* ... */ }
}
/** @var Collection<User> $users */
$users = new Collection();
$users->add(new User('Alice')); // fine
$users->add(new Product('SKU-100'));
// TypeError: Argument $item (template T = User) must be of type User, Product given
And return contracts that the engine itself will never check, because : array is all PHP has:
/**
* @return list<int>
*/
public function assignableRoles(): array
{
return Role::cases(); // returns enum instances, not ints
}
// TypeError: Return value[0] must be of type int, App\Enums\Role returned
It’s conceptually modelled on Python’s beartype, and it’s honest about being opt-out: TYPEPHP_DISABLE=true, an enabled => false config switch, and @typephp-ignoredocblock tags. It also detects when it’s running under phpstan, psalm, pint, rector or composer and stays out of the way.
Two projects, one name
swoole/typephp |
typephp/typephp |
|
|---|---|---|
| What it is | AOT compiler to native code | runtime PHPDoc enforcement |
| When types are checked | compile time | runtime |
| Build step | C++ toolchain, CMake, libphp |
none |
| Types come from | its own syntax (use native_types, std::) |
PHPDoc you probably already wrote |
| PHP support | a documented subset of 8.4–8.5 | 8.1+, all of it |
| Output | binary / extension / shared library | your app, unchanged |
| License | GPL-3.0 | MIT |
Both are named for the thing PHP core still doesn’t have. Neither did anything wrong: “TypePHP” is the obvious name for a project that adds types to PHP, which is precisely why two people reached for it. Obvious names are the ones that collide.
If you write about either, use the vendor prefix. swoole/typephp and typephp/typephp are unambiguous; “TypePHP” is now a coin flip.
Now say “native PHP” out loud
Here is the count. In current usage, “native” in a PHP sentence can mean at least six different things.
| Phrase | What it actually means |
|---|---|
| “compiles to native code” | machine instructions for the CPU, as opposed to bytecode |
| “faster than native PHP” | stock, interpreted PHP: the thing you apt install |
| “just native PHP, no framework” | vanilla PHP, no Laravel, no Symfony |
| “that’s a native function” | implemented in C inside the engine, not userland |
“use native_types“ |
TypePHP’s fixed C++ scalar storage |
| “NativePHP” | the project for building desktop and mobile applications |
The second and the first are already fighting. When Swoole’s own promotional material said TypePHP is “tens to hundreds of times faster than native PHP,” native PHP meant the interpreter, the allegedly traditional slow thing. Two paragraphs later, native meant the compiled binary, the fast thing. The word is on both sides of its own benchmark.
The third and fourth meanings have coexisted uneasily for two decades. Someone posts “I did it in native PHP” meaning no dependencies; someone replies about array_map being native, meaning written in C. They’re both right and they’re talking past each other. TypePHP makes this worse rather than better, because its -m ext mode produces a real PHP extension from PHP source: code that is now native in the C-implementation sense while having been written in the userland sense.
And then there’s NativePHP, Marcel Pociot’s project, which lets you ship a Laravel app as a desktop application, via Electron, or a mobile one or a Desktop application, because it keeps on adding new destinations. Here “native” means a native application: a piece of software with a window and a dock icon and a system notification, as opposed to a page in a browser tab. It’s the ordinary consumer-software sense of the word, imported wholesale into a language ecosystem that was already using it for two other purposes.
The irony is worth sitting with. NativePHP produces applications that are native in the packaging sense but rendered in a webview: not native in the platform-UI sense that iOS or GTK developers mean. Meanwhile TypePHP produces genuinely native machine code but, in binary mode, has no UI at all. If you wanted to build a native application with native PHP compiled to native code, you would need both projects, and the word would mean something different in each clause.
Somebody, somewhere, is going to write “a NativePHP app with native types compiled to a native binary, replacing our native PHP implementation.” That sentence is grammatically correct, semantically meaningful, and completely unparsable without context. And yes, unparsable is not a word in many dictionaries, except may be online.
This is a vocabulary shortage, not a naming failure
It’s tempting to blame the projects. That’s unfair. And making fun like this until now is possibly over the edge. Nobody did anything wrong: Swoole’s compiler emits native code, so native_types is the honest name. NativePHP builds native apps, so NativePHP is the honest name. array_map is native to the engine. Two projects that add types to PHP both landed on TypePHP because that is what they do. The problem isn’t that anyone chose badly. It’s that we have a small pool of obvious words, no shared habit of reaching for a more specific one, and no place to look one up.
Other ecosystems have more words because they needed them earlier. Rust distinguishes std from core from alloc. The JVM world separates intrinsic, native (JNI), and ahead-of-time. Python has builtin, C extension, CPython, and frozen module, and people mostly use them correctly.
PHP could pick up the same discipline. Say interpreted PHP or stock PHP when you mean the ZendVM path. Say internal function when you mean C-implemented — the engine’s own term, already in the manual. Say AOT-compiled when you mean lowered to machine code. Say framework-free or vanilla when you mean no dependencies. Say desktop app or mobile app when that’s what you’re shipping. Reserve native for the cases where you’d have to define it anyway.
And when two projects share a name, write the vendor prefix. It costs seven characters and it removes the ambiguity completely.
None of that requires an RFC. It requires people writing docs, blog posts, and conference abstracts to spend one extra word.
The ecosystem is producing new concepts faster than it’s producing terms for them, and 2026 is going to add more: WASI targets, embedded runtimes, hybrid compiled/interpreted deployments. Each one will arrive needing a name, and the path of least resistance will be to overload something we already have.
If you want to help, the PHP dictionary is the right place to start: the ultimate fix for a vocabulary shortage is more vocabulary, written down where people can find it.

