Navigation

Php

Writing Asynchronous PHP with Swoole and Laravel Octane

Master asynchronous PHP programming with Swoole and Laravel Octane. Learn coroutines, WebSockets, high-performance HTTP servers, real-time applications, and concurrent programming patterns to build scalable PHP applications.
Writing Asynchronous PHP with Swoole and Laravel Octane

PHP's traditional synchronous execution model has served the web development community well for decades, but modern applications demand more. Asynchronous PHP with Swoole and Laravel Octane transforms PHP from a request-response cycle into a high-performance, event-driven platform capable of handling thousands of concurrent connections with remarkable efficiency.

Table Of Contents

Breaking Free from Synchronous Limitations

Traditional PHP applications process one request at a time, creating and destroying resources with each request cycle. This model works well for simple applications but becomes a bottleneck for high-traffic systems. Asynchronous PHP changes this paradigm entirely, enabling non-blocking I/O operations, persistent connections, and concurrent request handling.

Swoole extends PHP with asynchronous networking capabilities, coroutines, and high-performance features. Laravel Octane builds upon this foundation, bringing async capabilities to Laravel applications without requiring fundamental architecture changes. Together, they represent PHP's evolution into a modern, high-performance runtime.

Understanding Swoole's Architecture

Swoole operates on an event-driven architecture with multiple process types working in harmony:

Swoole's process model with master process

<?php

use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;

// Basic Swoole HTTP server
$server = new Server("0.0.0.0", 8080);

$server->set([
    'worker_num' => 4,      // Number of worker processes
    'task_worker_num' => 2, // Number of task worker processes
    'dispatch_mode' => 2,   // Round-robin dispatch
    'max_request' => 1000,  // Max requests per worker before restart
    'enable_coroutine' => true,
    'hook_flags' => SWOOLE_HOOK_ALL,
]);

$server->on('start', function ($server) {
    echo "Swoole HTTP server started on http://127.0.0.1:8080\n";
});

$server->on('request', function (Request $request, Response $response) {
    // Asynchronous request handling
    go(function () use ($request, $response) {
        // This coroutine handles the request
        $start = microtime(true);
        
        // Simulate async database query
        $data = yield from fetchUserData($request->get['id'] ?? 1);
        
        // Simulate async API call
        $apiData = yield from callExternalAPI('https://api.example.com/data');
        
        $elapsed = microtime(true) - $start;
        
        $response->header('Content-Type', 'application/json');
        $response->end(json_encode([
            'user' => $data,
            'api_data' => $apiData,
            'processing_time' => $elapsed,
            'worker_id' => swoole_get_local_ip(),
            'coroutine_id' => Swoole\Coroutine::getCid()
        ]));
    });
});

$server->start();

// Async database query using coroutines
function fetchUserData(int $userId): Generator
{
    $mysql = new Swoole\Coroutine\MySQL();
    $mysql->connect([
        'host' => '127.0.0.1',
        'port' => 3306,
        'user' => 'root',
        'password' => 'password',
        'database' => 'test',
    ]);
    
    $result = yield $mysql->query("SELECT * FROM users WHERE id = {$userId}");
    $mysql->close();
    
    return $result[0] ?? null;
}

// Async HTTP client
function callExternalAPI(string $url): Generator
{
    $client = new Swoole\Coroutine\Http\Client('api.example.com', 443, true);
    $client->setHeaders(['User-Agent' => 'Swoole HTTP Client']);
    
    yield $client->get('/data');
    $result = $client->body;
    
    $client->close();
    return json_decode($result, true);
}

Coroutines: The Heart of Async PHP

Coroutines enable cooperative multitasking, allowing functions to yield control and resume execution later:

<?php

use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use Swoole\Coroutine\WaitGroup;

// Advanced coroutine patterns
class AsyncTaskManager
{
    private Channel $taskQueue;
    private Channel $resultQueue;
    private int $maxConcurrency;

    public function __construct(int $maxConcurrency = 100)
    {
        $this->taskQueue = new Channel($maxConcurrency);
        $this->resultQueue = new Channel($maxConcurrency);
        $this->maxConcurrency = $maxConcurrency;
    }

    public function addTask(callable $task, array $params = []): void
    {
        $this->taskQueue->push(['task' => $task, 'params' => $params]);
    }

    public function processTasks(): array
    {
        $wg = new WaitGroup();
        $results = [];

        // Spawn worker coroutines
        for ($i = 0; $i < $this->maxConcurrency; $i++) {
            $wg->add();
            
            go(function () use ($wg) {
                while (!$this->taskQueue->isEmpty()) {
                    $taskData = $this->taskQueue->pop(0.1); // Non-blocking pop
                    
                    if ($taskData === false) {
                        continue; // Channel is empty
                    }

                    try {
                        $result = call_user_func_array(
                            $taskData['task'], 
                            $taskData['params']
                        );
                        
                        $this->resultQueue->push([
                            'success' => true,
                            'result' => $result,
                            'coroutine_id' => Coroutine::getCid()
                        ]);
                    } catch (Throwable $e) {
                        $this->resultQueue->push([
                            'success' => false,
                            'error' => $e->getMessage(),
                            'coroutine_id' => Coroutine::getCid()
                        ]);
                    }
                }
                
                $wg->done();
            });
        }

        // Wait for all workers to complete
        $wg->wait();

        // Collect results
        while (!$this->resultQueue->isEmpty()) {
            $results[] = $this->resultQueue->pop();
        }

        return $results;
    }
}

// Usage example
Coroutine\run(function () {
    $manager = new AsyncTaskManager(50);

    // Add various async tasks
    for ($i = 1; $i <= 100; $i++) {
        $manager->addTask(function ($id) {
            // Simulate async database query
            Coroutine::sleep(0.1); // Non-blocking sleep
            return "User data for ID: {$id}";
        }, [$i]);

        $manager->addTask(function ($url) {
            // Simulate HTTP request
            $client = new Swoole\Coroutine\Http\Client('httpbin.org', 443, true);
            $client->get('/delay/1');
            $response = $client->body;
            $client->close();
            return $response;
        }, ["https://httpbin.org/delay/1"]);
    }

    $start = microtime(true);
    $results = $manager->processTasks();
    $elapsed = microtime(true) - $start;

    echo "Processed " . count($results) . " tasks in {$elapsed} seconds\n";
    echo "Success rate: " . count(array_filter($results, fn($r) => $r['success'])) . "/" . count($results) . "\n";
});

Laravel Octane Integration

Laravel Octane brings async capabilities to Laravel applications seamlessly. For optimal performance, choosing the right HTTP server configuration is crucial when deploying Octane applications:

<?php

// Install Laravel Octane
// composer require laravel/octane

// Configuration: config/octane.php
return [
    'server' => env('OCTANE_SERVER', 'swoole'),
    'https' => env('OCTANE_HTTPS', false),
    'host' => env('OCTANE_HOST', '127.0.0.1'),
    'port' => env('OCTANE_PORT', 8000),
    'max_execution_time' => 30,
    'memory_limit' => 512,
    
    'swoole' => [
        'options' => [
            'log_file' => storage_path('logs/swoole_http.log'),
            'package_max_length' => 10 * 1024 * 1024,
            'buffer_output_size' => 10 * 1024 * 1024,
            'socket_buffer_size' => 128 * 1024 * 1024,
            'max_request' => 1000,
            'send_yield' => true,
            'reload_async' => true,
            'enable_coroutine' => true,
            'max_coroutine' => 100000,
        ],
    ],
];

// Async Laravel controller
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Laravel\Octane\Facades\Octane;
use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;

class AsyncController extends Controller
{
    public function dashboard(Request $request)
    {
        // Check if running on Octane
        if (!Octane::hasTable('analytics')) {
            Octane::table('analytics', [
                'size' => 1000,
                'columns' => [
                    'page_views' => Octane::SWOOLE_TABLE_TYPE_INT,
                    'unique_visitors' => Octane::SWOOLE_TABLE_TYPE_INT,
                    'last_updated' => Octane::SWOOLE_TABLE_TYPE_INT,
                ],
            ]);
        }

        // Concurrent data fetching
        $wg = new WaitGroup();
        $data = [];

        // Fetch user data
        $wg->add();
        go(function () use (&$data, $wg, $request) {
            $data['user'] = User::find($request->user()->id);
            $wg->done();
        });

        // Fetch recent orders using advanced Eloquent techniques
        $wg->add();
        go(function () use (&$data, $wg, $request) {
            // Learn more about optimizing Eloquent queries: https://mycuriosity.blog/advanced-eloquent-techniques-and-optimizations-in-laravel
            $data['orders'] = Order::where('user_id', $request->user()->id)
                ->latest()
                ->limit(10)
                ->get();
            $wg->done();
        });

        // Fetch analytics from external API
        $wg->add();
        go(function () use (&$data, $wg) {
            $client = new \Swoole\Coroutine\Http\Client('analytics.api.com', 443, true);
            $client->setHeaders(['Authorization' => 'Bearer ' . config('services.analytics.token')]);
            
            $client->get('/dashboard-stats');
            $data['analytics'] = json_decode($client->body, true);
            $client->close();
            
            $wg->done();
        });

        // Fetch notification count
        $wg->add();
        go(function () use (&$data, $wg, $request) {
            $data['notifications'] = Notification::where('user_id', $request->user()->id)
                ->whereNull('read_at')
                ->count();
            $wg->done();
        });

        // Wait for all concurrent operations
        $wg->wait();

        // Update analytics table
        $this->updateAnalytics();

        return response()->json($data);
    }

    private function updateAnalytics(): void
    {
        if (Octane::hasTable('analytics')) {
            $table = Octane::table('analytics');
            $current = $table->get('dashboard', 'page_views') ?? 0;
            
            $table->set('dashboard', [
                'page_views' => $current + 1,
                'last_updated' => time(),
            ]);
        }
    }
}

// Async middleware for Laravel
class AsyncAuthMiddleware
{
    public function handle($request, \Closure $next)
    {
        if (Coroutine::getCid() > 0) {
            // We're in a coroutine context
            go(function () use ($request) {
                // Async auth checks
                $this->performAsyncSecurityChecks($request);
            });
        }

        return $next($request);
    }

    private function performAsyncSecurityChecks($request): void
    {
        $wg = new WaitGroup();

        // Check rate limiting - learn about API rate limiting: https://mycuriosity.blog/laravel-api-rate-limiting-protect-your-endpoints-from-abuse
        $wg->add();
        go(function () use ($request, $wg) {
            $this->checkRateLimit($request->ip());
            $wg->done();
        });

        // Check user permissions
        $wg->add();
        go(function () use ($request, $wg) {
            if ($request->user()) {
                $this->validateUserPermissions($request->user());
            }
            $wg->done();
        });

        // Log request - for API security best practices, see: https://mycuriosity.blog/laravel-api-development-best-practices-and-security
        $wg->add();
        go(function () use ($request, $wg) {
            $this->logSecurityEvent($request);
            $wg->done();
        });

        $wg->wait();
    }
}

Real-Time Applications with WebSockets

Build real-time applications using Swoole's WebSocket capabilities. For Laravel-specific WebSocket implementations, check out Laravel Broadcasting with WebSockets:

<?php

use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;
use Swoole\Table;

class RealtimeChatServer
{
    private Server $server;
    private Table $connections;
    private Table $rooms;
    private Table $users;

    public function __construct()
    {
        $this->server = new Server("0.0.0.0", 8081);
        
        // Connection tracking table
        $this->connections = new Table(1024);
        $this->connections->column('user_id', Table::TYPE_INT);
        $this->connections->column('room_id', Table::TYPE_STRING, 64);
        $this->connections->column('username', Table::TYPE_STRING, 64);
        $this->connections->column('connected_at', Table::TYPE_INT);
        $this->connections->create();

        // Room management table
        $this->rooms = new Table(256);
        $this->rooms->column('name', Table::TYPE_STRING, 64);
        $this->rooms->column('user_count', Table::TYPE_INT);
        $this->rooms->column('created_at', Table::TYPE_INT);
        $this->rooms->create();

        // User status table
        $this->users = new Table(1024);
        $this->users->column('status', Table::TYPE_STRING, 16);
        $this->users->column('last_seen', Table::TYPE_INT);
        $this->users->create();

        $this->setupEventHandlers();
    }

    private function setupEventHandlers(): void
    {
        $this->server->on('open', [$this, 'onOpen']);
        $this->server->on('message', [$this, 'onMessage']);
        $this->server->on('close', [$this, 'onClose']);
        $this->server->on('task', [$this, 'onTask']);
        
        $this->server->set([
            'worker_num' => 2,
            'task_worker_num' => 1,
            'enable_coroutine' => true,
            'heartbeat_check_interval' => 60,
            'heartbeat_idle_time' => 120,
        ]);
    }

    public function onOpen(Server $server, $request): void
    {
        // Authenticate user (simplified)
        $token = $request->get['token'] ?? '';
        $user = $this->authenticateToken($token);
        
        if (!$user) {
            $server->close($request->fd);
            return;
        }

        // Store connection info
        $this->connections->set($request->fd, [
            'user_id' => $user['id'],
            'room_id' => $request->get['room'] ?? 'general',
            'username' => $user['username'],
            'connected_at' => time(),
        ]);

        // Update user status
        $this->users->set($user['id'], [
            'status' => 'online',
            'last_seen' => time(),
        ]);

        // Join room
        $this->joinRoom($request->fd, $request->get['room'] ?? 'general');

        // Send welcome message
        $server->push($request->fd, json_encode([
            'type' => 'system',
            'message' => 'Connected to chat server',
            'user_count' => $this->getRoomUserCount($request->get['room'] ?? 'general'),
        ]));
    }

    public function onMessage(Server $server, Frame $frame): void
    {
        $connection = $this->connections->get($frame->fd);
        if (!$connection) {
            return;
        }

        $data = json_decode($frame->data, true);
        
        switch ($data['type'] ?? '') {
            case 'chat':
                $this->handleChatMessage($server, $frame->fd, $data);
                break;
                
            case 'typing':
                $this->handleTypingIndicator($server, $frame->fd, $data);
                break;
                
            case 'join_room':
                $this->handleRoomChange($server, $frame->fd, $data);
                break;
                
            case 'private_message':
                $this->handlePrivateMessage($server, $frame->fd, $data);
                break;
        }
    }

    private function handleChatMessage(Server $server, int $fd, array $data): void
    {
        $connection = $this->connections->get($fd);
        
        // Message validation and filtering
        $message = $this->sanitizeMessage($data['message'] ?? '');
        if (empty($message)) {
            return;
        }

        // Prepare broadcast message
        $broadcastData = [
            'type' => 'chat',
            'username' => $connection['username'],
            'message' => $message,
            'timestamp' => time(),
            'room' => $connection['room_id'],
        ];

        // Broadcast to room members
        go(function () use ($server, $connection, $broadcastData) {
            $this->broadcastToRoom($server, $connection['room_id'], $broadcastData);
        });

        // Store message (async task)
        $server->task([
            'action' => 'store_message',
            'data' => $broadcastData,
        ]);
    }

    private function handleTypingIndicator(Server $server, int $fd, array $data): void
    {
        $connection = $this->connections->get($fd);
        
        $typingData = [
            'type' => 'typing',
            'username' => $connection['username'],
            'is_typing' => $data['is_typing'] ?? false,
            'room' => $connection['room_id'],
        ];

        // Broadcast typing indicator to others in room
        go(function () use ($server, $connection, $typingData, $fd) {
            $this->broadcastToRoom($server, $connection['room_id'], $typingData, [$fd]);
        });
    }

    private function broadcastToRoom(Server $server, string $roomId, array $data, array $excludeFds = []): void
    {
        foreach ($this->connections as $fd => $connection) {
            if ($connection['room_id'] === $roomId && !in_array($fd, $excludeFds)) {
                if ($server->isEstablished($fd)) {
                    $server->push($fd, json_encode($data));
                }
            }
        }
    }

    public function onClose(Server $server, int $fd): void
    {
        $connection = $this->connections->get($fd);
        
        if ($connection) {
            // Update user status
            $this->users->set($connection['user_id'], [
                'status' => 'offline',
                'last_seen' => time(),
            ]);

            // Notify room of disconnection
            $this->broadcastToRoom($server, $connection['room_id'], [
                'type' => 'user_left',
                'username' => $connection['username'],
                'timestamp' => time(),
            ]);

            // Remove connection
            $this->connections->del($fd);
            
            // Update room user count
            $this->updateRoomUserCount($connection['room_id']);
        }
    }

    public function onTask(Server $server, int $taskId, int $srcWorkerId, $data): void
    {
        switch ($data['action']) {
            case 'store_message':
                $this->storeMessageInDatabase($data['data']);
                break;
                
            case 'send_push_notification':
                $this->sendPushNotification($data['data']);
                break;
        }
    }

    public function start(): void
    {
        echo "WebSocket Chat Server starting on ws://127.0.0.1:8081\n";
        $this->server->start();
    }
}

// Start the server
$chatServer = new RealtimeChatServer();
$chatServer->start();

High-Performance HTTP Client

Build efficient HTTP clients for external API integration:

<?php

use Swoole\Coroutine\Http\Client;
use Swoole\Coroutine\Channel;
use Swoole\Coroutine\WaitGroup;

class AsyncHttpClient
{
    private array $config;
    private Channel $pool;

    public function __construct(array $config = [])
    {
        $this->config = array_merge([
            'timeout' => 10,
            'max_connections' => 100,
            'keep_alive' => true,
            'retry_count' => 3,
            'retry_delay' => 0.5,
        ], $config);

        $this->pool = new Channel($this->config['max_connections']);
        $this->initializePool();
    }

    private function initializePool(): void
    {
        for ($i = 0; $i < $this->config['max_connections']; $i++) {
            $this->pool->push(null); // Pool tokens
        }
    }

    public function request(string $method, string $url, array $options = []): array
    {
        // Get connection from pool
        $this->pool->pop();

        try {
            $result = $this->makeRequest($method, $url, $options);
            return $result;
        } finally {
            // Return connection to pool
            $this->pool->push(null);
        }
    }

    private function makeRequest(string $method, string $url, array $options): array
    {
        $urlParts = parse_url($url);
        $host = $urlParts['host'];
        $port = $urlParts['port'] ?? ($urlParts['scheme'] === 'https' ? 443 : 80);
        $ssl = $urlParts['scheme'] === 'https';

        $client = new Client($host, $port, $ssl);
        $client->set([
            'timeout' => $this->config['timeout'],
            'keep_alive' => $this->config['keep_alive'],
        ]);

        $headers = $options['headers'] ?? [];
        $body = $options['body'] ?? '';
        $path = $urlParts['path'] . ($urlParts['query'] ? '?' . $urlParts['query'] : '');

        // Set headers
        if (!empty($headers)) {
            $client->setHeaders($headers);
        }

        // Make request with retry logic
        $attempt = 0;
        $lastError = null;

        while ($attempt < $this->config['retry_count']) {
            try {
                $success = match(strtoupper($method)) {
                    'GET' => $client->get($path),
                    'POST' => $client->post($path, $body),
                    'PUT' => $client->put($path, $body),
                    'DELETE' => $client->delete($path),
                    'PATCH' => $client->patch($path, $body),
                    default => throw new InvalidArgumentException("Unsupported method: {$method}")
                };

                if ($success) {
                    $result = [
                        'status_code' => $client->statusCode,
                        'headers' => $client->headers,
                        'body' => $client->body,
                        'stats' => [
                            'total_time' => $client->totalTime ?? 0,
                            'name_lookup_time' => $client->nameLookupTime ?? 0,
                            'connect_time' => $client->connectTime ?? 0,
                        ],
                    ];

                    $client->close();
                    return $result;
                }

                $lastError = "HTTP request failed: " . $client->errMsg;
            } catch (Throwable $e) {
                $lastError = $e->getMessage();
            }

            $attempt++;
            if ($attempt < $this->config['retry_count']) {
                \Swoole\Coroutine::sleep($this->config['retry_delay']);
            }
        }

        $client->close();
        throw new Exception("Request failed after {$this->config['retry_count']} attempts: {$lastError}");
    }

    public function batch(array $requests): array
    {
        $wg = new WaitGroup();
        $results = [];

        foreach ($requests as $index => $request) {
            $wg->add();
            
            go(function () use (&$results, $index, $request, $wg) {
                try {
                    $results[$index] = $this->request(
                        $request['method'],
                        $request['url'],
                        $request['options'] ?? []
                    );
                } catch (Throwable $e) {
                    $results[$index] = [
                        'error' => $e->getMessage(),
                        'status_code' => 0,
                    ];
                }
                
                $wg->done();
            });
        }

        $wg->wait();
        return $results;
    }

    public function parallel(callable $callback, array $urls, int $concurrency = 10): array
    {
        $channel = new Channel(count($urls));
        $results = [];

        // Add URLs to channel
        foreach ($urls as $index => $url) {
            $channel->push(['index' => $index, 'url' => $url]);
        }

        $wg = new WaitGroup();

        // Start worker coroutines
        for ($i = 0; $i < $concurrency; $i++) {
            $wg->add();
            
            go(function () use ($channel, &$results, $callback, $wg) {
                while (!$channel->isEmpty()) {
                    $item = $channel->pop(0.1);
                    
                    if ($item === false) {
                        continue;
                    }

                    try {
                        $results[$item['index']] = $callback($item['url'], $this);
                    } catch (Throwable $e) {
                        $results[$item['index']] = [
                            'error' => $e->getMessage(),
                            'url' => $item['url'],
                        ];
                    }
                }
                
                $wg->done();
            });
        }

        $wg->wait();
        ksort($results); // Maintain original order
        
        return $results;
    }
}

// Usage examples
\Swoole\Coroutine\run(function () {
    $client = new AsyncHttpClient([
        'timeout' => 5,
        'max_connections' => 50,
        'retry_count' => 2,
    ]);

    // Single request
    $response = $client->request('GET', 'https://api.github.com/users/octocat');
    echo "Status: {$response['status_code']}\n";

    // Batch requests
    $requests = [
        ['method' => 'GET', 'url' => 'https://httpbin.org/delay/1'],
        ['method' => 'GET', 'url' => 'https://httpbin.org/delay/2'],
        ['method' => 'POST', 'url' => 'https://httpbin.org/post', 'options' => [
            'headers' => ['Content-Type' => 'application/json'],
            'body' => json_encode(['test' => 'data'])
        ]],
    ];

    $start = microtime(true);
    $results = $client->batch($requests);
    $elapsed = microtime(true) - $start;

    echo "Batch completed in {$elapsed} seconds\n";
    echo "Results: " . count($results) . "\n";

    // Parallel processing
    $urls = [
        'https://api.github.com/users/github',
        'https://api.github.com/users/microsoft',
        'https://api.github.com/users/google',
        'https://api.github.com/users/facebook',
        'https://api.github.com/users/twitter',
    ];

    $results = $client->parallel(function ($url, $client) {
        return $client->request('GET', $url);
    }, $urls, 3);

    echo "Parallel requests completed: " . count($results) . "\n";
});

Performance Optimization and Monitoring

Optimize and monitor your async PHP applications:

<?php

use Swoole\Coroutine;
use Swoole\Timer;
use Swoole\Process;

class PerformanceMonitor
{
    private array $metrics = [];
    private int $startTime;
    private \SplObjectStorage $coroutineStats;

    public function __construct()
    {
        $this->startTime = time();
        $this->coroutineStats = new \SplObjectStorage();
        $this->setupMonitoring();
    }

    private function setupMonitoring(): void
    {
        // Monitor every 10 seconds
        Timer::tick(10000, function () {
            $this->collectMetrics();
            $this->reportMetrics();
        });

        // Memory monitoring
        Timer::tick(5000, function () {
            $this->checkMemoryUsage();
        });
    }

    private function collectMetrics(): void
    {
        $this->metrics = [
            'timestamp' => time(),
            'uptime' => time() - $this->startTime,
            'memory_usage' => memory_get_usage(true),
            'memory_peak' => memory_get_peak_usage(true),
            'coroutines' => [
                'running' => Coroutine::stats()['coroutine_num'] ?? 0,
                'peak' => Coroutine::stats()['coroutine_peak_num'] ?? 0,
            ],
            'system' => [
                'load_average' => sys_getloadavg(),
                'cpu_count' => swoole_cpu_num(),
            ],
        ];

        // Process-specific metrics
        if (function_exists('getrusage')) {
            $usage = getrusage();
            $this->metrics['cpu_usage'] = [
                'user_time' => $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1000000,
                'system_time' => $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1000000,
            ];
        }
    }

    private function checkMemoryUsage(): void
    {
        $memoryLimit = ini_get('memory_limit');
        $currentUsage = memory_get_usage(true);
        
        if ($memoryLimit !== '-1') {
            $limitBytes = $this->parseMemoryLimit($memoryLimit);
            $usagePercent = ($currentUsage / $limitBytes) * 100;
            
            if ($usagePercent > 80) {
                $this->triggerAlert('HIGH_MEMORY_USAGE', [
                    'usage_percent' => $usagePercent,
                    'current_usage' => $currentUsage,
                    'limit' => $limitBytes,
                ]);
            }
        }
    }

    private function parseMemoryLimit(string $limit): int
    {
        $value = (int) $limit;
        $unit = strtolower(substr($limit, -1));
        
        return match($unit) {
            'g' => $value * 1024 * 1024 * 1024,
            'm' => $value * 1024 * 1024,
            'k' => $value * 1024,
            default => $value,
        };
    }

    public function trackCoroutine(callable $callback): mixed
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        try {
            $result = $callback();
            
            $this->recordCoroutineStats([
                'duration' => microtime(true) - $startTime,
                'memory_used' => memory_get_usage() - $startMemory,
                'success' => true,
            ]);
            
            return $result;
        } catch (Throwable $e) {
            $this->recordCoroutineStats([
                'duration' => microtime(true) - $startTime,
                'memory_used' => memory_get_usage() - $startMemory,
                'success' => false,
                'error' => $e->getMessage(),
            ]);
            
            throw $e;
        }
    }

    private function recordCoroutineStats(array $stats): void
    {
        // Store stats for analysis
        if (!isset($this->metrics['coroutine_performance'])) {
            $this->metrics['coroutine_performance'] = [];
        }
        
        $this->metrics['coroutine_performance'][] = $stats;
        
        // Keep only last 1000 records
        if (count($this->metrics['coroutine_performance']) > 1000) {
            array_shift($this->metrics['coroutine_performance']);
        }
    }

    private function reportMetrics(): void
    {
        echo "\n=== Performance Metrics ===\n";
        echo "Uptime: {$this->metrics['uptime']} seconds\n";
        echo "Memory: " . $this->formatBytes($this->metrics['memory_usage']) . " / " . 
             $this->formatBytes($this->metrics['memory_peak']) . " (peak)\n";
        echo "Coroutines: {$this->metrics['coroutines']['running']} running, " .
             "{$this->metrics['coroutines']['peak']} peak\n";
        echo "Load Average: " . implode(', ', $this->metrics['system']['load_average']) . "\n";
        
        if (isset($this->metrics['coroutine_performance'])) {
            $perfStats = $this->metrics['coroutine_performance'];
            $avgDuration = array_sum(array_column($perfStats, 'duration')) / count($perfStats);
            $successRate = count(array_filter($perfStats, fn($s) => $s['success'])) / count($perfStats);
            
            echo "Avg Coroutine Duration: " . number_format($avgDuration * 1000, 2) . "ms\n";
            echo "Success Rate: " . number_format($successRate * 100, 2) . "%\n";
        }
        
        echo "========================\n\n";
    }

    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $unit = 0;
        
        while ($bytes >= 1024 && $unit < count($units) - 1) {
            $bytes /= 1024;
            $unit++;
        }
        
        return number_format($bytes, 2) . ' ' . $units[$unit];
    }

    private function triggerAlert(string $type, array $data): void
    {
        // Implement alerting logic (email, Slack, monitoring service)
        error_log("ALERT [{$type}]: " . json_encode($data));
    }
}

// Usage in Swoole application
$monitor = new PerformanceMonitor();

// Wrap performance-critical operations
go(function () use ($monitor) {
    $result = $monitor->trackCoroutine(function () {
        // Your async operation here
        Coroutine::sleep(0.1);
        return "Operation completed";
    });
    
    echo "Result: {$result}\n";
});

Conclusion

Asynchronous PHP with Swoole and Laravel Octane represents a paradigm shift that transforms PHP into a high-performance, concurrent runtime. By embracing coroutines, non-blocking I/O, and event-driven architecture, developers can build applications that scale to handle thousands of concurrent connections while maintaining PHP's familiar syntax and ecosystem.

The journey from synchronous to asynchronous PHP requires understanding new concepts – coroutines, channels, and concurrent programming patterns. However, the performance benefits and architectural possibilities make this learning investment worthwhile. Whether building real-time applications, high-throughput APIs, or microservices, async PHP provides the tools to compete with traditionally faster languages while retaining PHP's development velocity.

Related Articles

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Php