The Problem with Traditional PHP
Traditional PHP follows a “shared-nothing” architecture:
Request → PHP Boots → Load Files → Execute → Shutdown → Response
↓
Next Request → PHP Boots → Load Files → Execute → Shutdown → Response
Every request:
Starts a new PHP process
Loads the framework from disk
Parses and compiles PHP files
Initializes dependencies
Finally executes your code
Destroys everything
Traditional PHP spends 50-80% of request time on startup overhead, not your actual code.
The Always-Alive Solution
Lyger’s Always-Alive architecture keeps PHP running in memory:
┌──────────────────────────────────────┐
│ Rust HTTP Server (Port 8000) │
│ - Ultra-fast request parsing │
│ - Connection handling │
│ - Static file serving │
└──────────────────────────────────────┘
↓ FFI Callback
┌──────────────────────────────────────┐
│ PHP Worker (Persistent Process) │
│ - Framework pre-loaded │
│ - Classes in memory │
│ - Connections pooled │
│ - Routes pre-compiled │
└──────────────────────────────────────┘
With Always-Alive, only your application code executes per request. The framework overhead is paid once at startup.
How It Works
Server Startup
// From your application's entry point
use Lyger\Core\ Engine ;
// Define your router
$router = function ( string $uri , string $method , array $data ) {
// Your routing logic here
return Router :: handle ( $uri , $method );
};
// Start the Always-Alive server
Engine :: startServer ( $router , 8000 );
Behind the Scenes
// From Lyger/Core/Engine.php
public static function startServer ( callable $routerHandler , int $port = 8000 ) : void
{
echo " \n " ;
echo "======================================== \n " ;
echo " LYGER v0.1 - Always-Alive Server \n " ;
echo "======================================== \n\n " ;
// Preload framework into memory
echo "Loading framework into memory... \n " ;
ServerManager :: start ( $routerHandler );
// Start Rust HTTP server if FFI available
$instance = self :: getInstance ();
if ( $instance -> ffi !== null ) {
try {
echo "Starting Rust HTTP server on port { $port }... \n " ;
$instance -> ffi -> lyger_start_server ( $port );
self :: $serverRunning = true ;
} catch ( \ Throwable $e ) {
echo "Note: Using PHP built-in server (FFI start_server not available) \n " ;
}
} else {
echo "Note: Using PHP built-in server (FFI not available) \n " ;
}
// Keep the PHP worker alive
while ( self :: $serverRunning ) {
sleep ( 1 );
}
}
ServerManager
The ServerManager class orchestrates the persistent PHP worker:
// From Lyger/Core/ServerManager.php
class ServerManager
{
private static bool $running = false ;
private static $router = null ;
private static array $loadedClasses = [];
public static function start ( callable $router ) : void
{
self :: $running = true ;
self :: $router = $router ;
// Preload all framework classes into memory
self :: preloadFramework ();
echo " \n " ;
echo "======================================== \n " ;
echo " LYGER SERVER v0.1 \n " ;
echo " Always-Alive Mode \n " ;
echo "======================================== \n\n " ;
echo "✓ Framework loaded in memory \n " ;
echo "✓ Waiting for requests... \n " ;
echo " Ctrl+C to stop \n\n " ;
// Keep PHP alive - waiting for FFI callbacks from Rust
while ( self :: $running ) {
sleep ( 1 );
if ( ! self :: $running ) {
break ;
}
}
}
}
Framework Preloading
// From Lyger/Core/ServerManager.php
private static function preloadFramework () : void
{
$classes = [
// Core
'Lyger\Core\Engine' ,
'Lyger\Routing\Router' ,
'Lyger\Http\Request' ,
'Lyger\Http\Response' ,
// Container
'Lyger\Container\Container' ,
// Database
'Lyger\Database\QueryBuilder' ,
'Lyger\Database\Schema' ,
'Lyger\Database\Model' ,
'Lyger\Database\Collection' ,
// Middleware
'Lyger\Middleware\Middleware' ,
'Lyger\Middleware\CorsMiddleware' ,
'Lyger\Middleware\RateLimitMiddleware' ,
'Lyger\Middleware\AuthMiddleware' ,
// Validation
'Lyger\Validation\Validator' ,
// Cache
'Lyger\Cache\Cache' ,
// Foundation
'Lyger\Foundation\Env' ,
'Lyger\Foundation\Path' ,
];
foreach ( $classes as $class ) {
if ( class_exists ( $class )) {
self :: $loadedClasses [] = $class ;
}
}
}
PHP’s class autoloader caches parsed classes. Once loaded, subsequent instantiations are nearly instant.
Request Handling
When a request arrives:
Rust receives HTTP request (port 8000)
Rust parses request (method, URI, headers, body)
Rust calls PHP via FFI with request data
PHP executes router (already in memory)
PHP returns response to Rust
Rust sends HTTP response to client
// From Lyger/Core/ServerManager.php
public static function handleRequest ( string $uri , string $method , array $data = []) : string
{
if ( self :: $router === null ) {
return json_encode ([ 'error' => 'No router configured' ]);
}
try {
// Execute the router - instant since everything is in memory!
$response = ( self :: $router )( $uri , $method , $data );
return $response ;
} catch ( \ Throwable $e ) {
return json_encode ([
'error' => $e -> getMessage (),
'file' => $e -> getFile (),
'line' => $e -> getLine ()
]);
}
}
Startup Time Comparison
Scenario Traditional PHP Always-Alive Improvement Framework boot ~50ms ~0ms (preloaded) ∞ Class loading ~20ms ~0ms (cached) ∞ Route compilation ~10ms ~0ms (compiled) ∞ DB connection ~5ms ~1ms (pooled) 5x Total overhead ~85ms ~1ms 85x
Your actual application code execution time remains the same. The improvement comes from eliminating startup overhead.
Real-World Impact
Traditional PHP:
85ms overhead + 15ms app code = 100ms total
Only 15% of time spent on your code!
Always-Alive:
1ms overhead + 15ms app code = 16ms total
94% of time spent on your code!
Result: 6.25x faster response time
Memory Considerations
Memory Usage
Traditional PHP per request:
- Process: ~8MB
- Framework: ~10MB
- Application: ~5MB
Total per concurrent request: ~23MB
Always-Alive:
- Single Process: ~8MB
- Framework: ~10MB (shared)
- Application: ~5MB (shared)
Total for all requests: ~23MB
Always-Alive uses less total memory because the framework is loaded once, not per request. However, memory leaks are permanent until server restart.
Memory Leaks
In traditional PHP, memory leaks are cleared after each request. With Always-Alive, you must be careful:
// ❌ Bad: Memory leak
class Controller {
private static array $cache = [];
public function handle ( $request ) {
self :: $cache [] = $request ; // Grows forever!
}
}
// ✅ Good: Bounded memory
class Controller {
public function handle ( $request ) {
$cache = $this -> getCache (); // Instance variable, GC'd after request
$cache -> set ( 'key' , $value );
}
}
Best practices for Always-Alive
Avoid static arrays that grow - Use proper cache implementations
Close resources - Database connections, file handles, etc.
Unset large variables - Help PHP’s garbage collector
Use dependency injection - Avoid globals and static state
Monitor memory usage - Set up alerts for memory growth
Starting the Server
Command Line
This starts the server on port 8000 by default.
Custom Port
php rawr serve --port=8080
Programmatic
use Lyger\Core\ Engine ;
use Lyger\Routing\ Router ;
$router = function ( $uri , $method , $data ) {
return Router :: handle ( $uri , $method );
};
Engine :: startServer ( $router , 8000 );
Stopping the Server
Press Ctrl+C or send SIGTERM:
Programmatically:
use Lyger\Core\ Engine ;
Engine :: stopServer ();
Development vs Production
Development Mode
In development, you’ll want to reload code changes:
# Restart on file changes (requires external tool)
ls * .php | entr -r php rawr serve
During development, restart the server when you change code. Use file watchers to automate this.
Production Mode
In production, the Always-Alive server shines:
# Run as a service (systemd example)
[Unit]
Description = Lyger Always-Alive Server
After = network.target
[Service]
Type = simple
User = www-data
WorkingDirectory = /var/www/app
ExecStart = /usr/bin/php rawr serve
Restart = always
[Install]
WantedBy = multi-user.target
Comparing to Other Solutions
Solution Startup Cost Memory Complexity Traditional PHP Every request Low Simple PHP-FPM Per process Medium Medium RoadRunner Once High Complex Swoole Once High Complex Lyger Always-Alive Once Medium Simple
Lyger provides RoadRunner/Swoole-like performance with much simpler architecture thanks to Rust FFI.
Troubleshooting
Server Won’t Start
// Check FFI
if ( ! extension_loaded ( 'ffi' )) {
echo "FFI extension not loaded \n " ;
}
// Check library
$engine = Engine :: getInstance ();
if ( $engine -> ffi === null ) {
echo "Rust library not found \n " ;
}
Port Already in Use
# Find process using port
lsof -i :8000
# Kill it
kill < pi d >
# Or use a different port
php rawr serve --port=8080
Memory Growth
Monitor memory usage:
echo "Memory: " . memory_get_usage ( true ) / 1024 / 1024 . " MB \n " ;
Set memory limit:
; php.ini
memory_limit = 256M
Next Steps
Architecture Overview Understand the full system architecture
Rust FFI Integration Learn how PHP talks to Rust
Zero-Copy Database Optimize database operations
Routing Build your application routes