hamdallah90 / laravel-typephp
Compile the hot paths of a Laravel app to native code with TypePHP (swoole/typephp) and load them as a PHP extension, with transparent fallback to the interpreter.
Requires
- php: ^8.4
- illuminate/console: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- symfony/process: ^7.0 || ^8.0
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
- swoole/typephp: The TypePHP compiler. Install with `composer require --dev swoole/typephp` (needs Symfony 8, i.e. Laravel 13+), or install the `tpc` release binary and set TYPEPHP_TPC.
Provides
None
Conflicts
None
Replaces
None
README
Compile the hot paths of a Laravel application to native code with TypePHP, the AOT PHP-to-C++ compiler from the Swoole team, and load the result as a PHP extension. When the extension is not loaded, the very same files run in the interpreter through Composer's autoloader, so nothing breaks in development, CI, or on a host without the toolchain.
app/Native/Fib.php --tpc--> storage/typephp/app_native.so --extension=--> App\Native\Fib is an internal class
Measured on PHP 8.4, fib(32) inside a Laravel 13 app:
| runtime | time |
|---|---|
| interpreter | 0.215 s |
| interpreter + opcache JIT | 0.089 s |
TypePHP -O3 + use native_types; |
0.006 s |
What this is not
TypePHP compiles a strict, statically typed subset of PHP. It does not support
reflection tricks, Closure::bind, variable variables, top-level statements, or
most of the dynamic behaviour Laravel's container, Eloquent and facades rely on.
This package therefore does not compile the framework or your whole app/.
It compiles the code you put in app/Native (or wherever config/typephp.php
points): pure computation, parsers, encoders, pricing engines, matrix math, hashing
and similar CPU-bound classes that take and return scalars, arrays and plain objects.
Requirements
- PHP 8.4 or 8.5 CLI with headers (
php-config), same build that will load the extension - Laravel 11, 12 or 13 on PHP 8.4+
- The TypePHP compiler:
composer require --dev swoole/typephp(it needs Symfony 8, which means Laravel 13; on Laravel 11/12 install thetpcrelease binary instead and setTYPEPHP_TPC) - C++17 toolchain: clang or gcc 9+, cmake 3.24+,
libgmp-dev,libmpfr-dev libphpxbuilt once fromvendor/swoole/phpx(see below)
Install
composer require hamdallah90/laravel-typephp
composer require --dev swoole/typephp
php artisan vendor:publish --tag=typephp-config # optional
Build phpx (the C++ runtime the generated code links against). It must match the PHP that will load the extension:
cmake -S vendor/swoole/phpx -B vendor/swoole/phpx/build -DCMAKE_BUILD_TYPE=Release cmake --build vendor/swoole/phpx/build --target phpx -j8
On Linux also install it so the dynamic linker finds libphpx.so (the extension carries
no rpath there):
sudo cmake --install vendor/swoole/phpx/build && sudo ldconfig
On macOS with Homebrew: brew install php cmake gmp mpfr, pass
-Dphp_dir=/opt/homebrew/opt/php to the first cmake call, and skip the install step
(the extension gets an rpath to vendor/swoole/phpx/lib).
Use
php artisan typephp:make Fib # app/Native/Fib.php from a stub that follows the TypePHP rules php artisan typephp:compile # storage/typephp/app_native.so php artisan typephp:status # is it built? loaded? which classes run native?
Load the extension in every SAPI that should use it (CLI, FPM, Octane workers):
extension=/var/www/app/storage/typephp/app_native.so
or ad hoc: php -d extension=$PWD/storage/typephp/app_native.so artisan typephp:status.
Then just call your classes. No wrapper, no FFI:
use App\Native\Fib; Route::get('/fib/{n}', fn (int $n) => Fib::fib($n));
LaravelTypePHP\TypePHP::loaded() and TypePHP::isNative(Fib::class) tell you at
runtime which path you are on.
Writing native classes
- Classes only. Free functions collide with Composer's
filesautoloading ("Cannot redeclare function") once the extension is loaded. - Add
use native_types;and type everything. Untyped code compiles but stays on the slow, boxed-value path (at-O3it was slower than opcache's JIT in our test). - Do not
requirethe native files yourself. Composer autoloads them only when the extension is absent, which is exactly the fallback you want. - Everything else: Incompatible PHP features.
Configuration (config/typephp.php)
| key | default | notes |
|---|---|---|
name |
app_native |
artifact <name>.so, Zend module typephp_<name> |
sources |
[app/Native] |
files or directories |
ignore |
[] |
paths excluded from sources |
output_dir |
storage/typephp |
where the .so and project.yml land |
build_dir |
storage/typephp/build |
generated C++ and object cache |
optimize |
3 |
-O level, env TYPEPHP_OPTIMIZE |
php_version |
8.4 |
syntax level accepted by tpc |
jobs |
4 |
parallel C++ jobs |
ext_deps |
[] |
PHP extensions the native code needs |
link_libs |
[] |
extra -l libraries |
tpc |
auto | vendor/bin/tpc.php, then tpc on PATH, env TYPEPHP_TPC |
php |
the PHP running artisan | PHP 8.4+ used to run tpc.php, env TYPEPHP_PHP |
typephp:compile accepts --optimize=N, --debug, --force, --dry.
Deploying
The .so is tied to the PHP version and platform it was compiled for and needs
libphpx.so at runtime. Build on the target platform, or in Docker with the shipped
builder image, then copy both libraries to the server (libphpx.so into
/usr/local/lib followed by ldconfig):
docker build -f vendor/hamdallah90/laravel-typephp/stubs/Dockerfile -t app-typephp . docker run --rm -v "$PWD/storage/typephp:/out" app-typephp
The runtime host needs libgmp and libmpfr (apt-get install libgmp10 libmpfr6).
Do not put app/Native in an opcache preload script: preloading requires the files,
which redeclares the classes the extension already registered.
Tests
composer install vendor/bin/phpunit
License
MIT. TypePHP itself is GPL-3.0; it is a build-time tool here and is not bundled.