init
This commit is contained in:
412
.agents/skills/php-pro/references/async-patterns.md
Normal file
412
.agents/skills/php-pro/references/async-patterns.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# Async PHP Patterns
|
||||
|
||||
## Swoole HTTP Server
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Swoole\HTTP\Server;
|
||||
use Swoole\HTTP\Request;
|
||||
use Swoole\HTTP\Response;
|
||||
|
||||
$server = new Server('0.0.0.0', 9501);
|
||||
|
||||
$server->set([
|
||||
'worker_num' => 4,
|
||||
'max_request' => 10000,
|
||||
'task_worker_num' => 2,
|
||||
'enable_coroutine' => true,
|
||||
]);
|
||||
|
||||
$server->on('start', function (Server $server) {
|
||||
echo "Swoole HTTP server started at http://0.0.0.0:9501\n";
|
||||
});
|
||||
|
||||
$server->on('request', function (Request $request, Response $response) {
|
||||
$response->header('Content-Type', 'application/json');
|
||||
|
||||
match ($request->server['request_uri']) {
|
||||
'/api/users' => handleUsers($request, $response),
|
||||
'/api/health' => $response->end(json_encode(['status' => 'healthy'])),
|
||||
default => $response->status(404)->end(json_encode(['error' => 'Not found'])),
|
||||
};
|
||||
});
|
||||
|
||||
function handleUsers(Request $request, Response $response): void
|
||||
{
|
||||
// Coroutine for concurrent DB queries
|
||||
go(function () use ($response) {
|
||||
$users = queryDatabase('SELECT * FROM users LIMIT 10');
|
||||
$response->end(json_encode(['data' => $users]));
|
||||
});
|
||||
}
|
||||
|
||||
$server->start();
|
||||
```
|
||||
|
||||
## Swoole Coroutines
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
|
||||
// Concurrent HTTP requests
|
||||
Coroutine\run(function () {
|
||||
$results = [];
|
||||
|
||||
// Create multiple coroutines
|
||||
$wg = new Coroutine\WaitGroup();
|
||||
|
||||
$urls = [
|
||||
'https://api.example.com/users',
|
||||
'https://api.example.com/posts',
|
||||
'https://api.example.com/comments',
|
||||
];
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$wg->add();
|
||||
go(function () use ($url, &$results, $wg) {
|
||||
$client = new Client(parse_url($url, PHP_URL_HOST), 443, true);
|
||||
$client->set(['timeout' => 5]);
|
||||
$client->get(parse_url($url, PHP_URL_PATH));
|
||||
|
||||
$results[$url] = [
|
||||
'status' => $client->statusCode,
|
||||
'body' => $client->body,
|
||||
];
|
||||
|
||||
$client->close();
|
||||
$wg->done();
|
||||
});
|
||||
}
|
||||
|
||||
$wg->wait();
|
||||
|
||||
print_r($results);
|
||||
});
|
||||
```
|
||||
|
||||
## Swoole Async MySQL
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\MySQL;
|
||||
|
||||
Coroutine\run(function () {
|
||||
$mysql = new MySQL();
|
||||
|
||||
$connected = $mysql->connect([
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 3306,
|
||||
'user' => 'root',
|
||||
'password' => 'password',
|
||||
'database' => 'test',
|
||||
]);
|
||||
|
||||
if (!$connected) {
|
||||
throw new \RuntimeException($mysql->connect_error);
|
||||
}
|
||||
|
||||
// Async query
|
||||
$result = $mysql->query('SELECT * FROM users WHERE active = 1');
|
||||
|
||||
foreach ($result as $row) {
|
||||
echo "User: {$row['name']}\n";
|
||||
}
|
||||
|
||||
// Prepared statements
|
||||
$stmt = $mysql->prepare('SELECT * FROM users WHERE id = ?');
|
||||
$stmt->execute([42]);
|
||||
$user = $stmt->fetchAll();
|
||||
|
||||
$mysql->close();
|
||||
});
|
||||
```
|
||||
|
||||
## Swoole Channel (Communication)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
Coroutine\run(function () {
|
||||
$channel = new Channel(10); // Buffer size: 10
|
||||
|
||||
// Producer
|
||||
go(function () use ($channel) {
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$channel->push("Task {$i}");
|
||||
echo "Produced: Task {$i}\n";
|
||||
Coroutine::sleep(0.5);
|
||||
}
|
||||
$channel->close();
|
||||
});
|
||||
|
||||
// Consumer
|
||||
go(function () use ($channel) {
|
||||
while (true) {
|
||||
$task = $channel->pop();
|
||||
if ($task === false && $channel->errCode === SWOOLE_CHANNEL_CLOSED) {
|
||||
break;
|
||||
}
|
||||
echo "Consumed: {$task}\n";
|
||||
Coroutine::sleep(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## ReactPHP Event Loop
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\Http\Message\Response;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
// HTTP Server
|
||||
$server = new React\Http\HttpServer(function (ServerRequestInterface $request) {
|
||||
return new Response(
|
||||
200,
|
||||
['Content-Type' => 'application/json'],
|
||||
json_encode([
|
||||
'method' => $request->getMethod(),
|
||||
'uri' => (string) $request->getUri(),
|
||||
'timestamp' => time(),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
$socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
||||
$server->listen($socket);
|
||||
|
||||
echo "Server running at http://0.0.0.0:8080\n";
|
||||
|
||||
// Periodic timer
|
||||
Loop::addPeriodicTimer(5.0, function () {
|
||||
echo "Heartbeat: " . date('H:i:s') . "\n";
|
||||
});
|
||||
|
||||
// One-time timer
|
||||
Loop::addTimer(10.0, function () {
|
||||
echo "This runs once after 10 seconds\n";
|
||||
});
|
||||
```
|
||||
|
||||
## ReactPHP Async MySQL
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use React\MySQL\Factory;
|
||||
use React\MySQL\QueryResult;
|
||||
|
||||
$factory = new Factory();
|
||||
|
||||
$connection = $factory->createLazyConnection('root:password@localhost/database');
|
||||
|
||||
$connection->query('SELECT * FROM users WHERE active = 1')
|
||||
->then(
|
||||
function (QueryResult $result) {
|
||||
echo "Found " . count($result->resultRows) . " users\n";
|
||||
foreach ($result->resultRows as $row) {
|
||||
echo "User: {$row['name']}\n";
|
||||
}
|
||||
},
|
||||
function (\Exception $error) {
|
||||
echo "Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
);
|
||||
|
||||
// Prepared statements
|
||||
$connection->query('SELECT * FROM users WHERE id = ?', [42])
|
||||
->then(function (QueryResult $result) {
|
||||
$user = $result->resultRows[0] ?? null;
|
||||
var_dump($user);
|
||||
});
|
||||
```
|
||||
|
||||
## ReactPHP Promises
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use function React\Promise\all;
|
||||
|
||||
// Creating promises
|
||||
function fetchUser(int $id): Promise
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
// Simulate async operation
|
||||
Loop::addTimer(1.0, function () use ($deferred, $id) {
|
||||
$deferred->resolve([
|
||||
'id' => $id,
|
||||
'name' => "User {$id}",
|
||||
]);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
// Using promises
|
||||
fetchUser(42)
|
||||
->then(function ($user) {
|
||||
echo "Got user: {$user['name']}\n";
|
||||
return fetchUserPosts($user['id']);
|
||||
})
|
||||
->then(function ($posts) {
|
||||
echo "Got " . count($posts) . " posts\n";
|
||||
})
|
||||
->catch(function (\Exception $error) {
|
||||
echo "Error: " . $error->getMessage() . "\n";
|
||||
});
|
||||
|
||||
// Parallel promises
|
||||
all([
|
||||
fetchUser(1),
|
||||
fetchUser(2),
|
||||
fetchUser(3),
|
||||
])->then(function ($users) {
|
||||
echo "Fetched " . count($users) . " users\n";
|
||||
});
|
||||
```
|
||||
|
||||
## PHP Fibers (Native PHP 8.1+)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Simple async function using fibers
|
||||
function async(callable $callback): Fiber
|
||||
{
|
||||
return new Fiber($callback);
|
||||
}
|
||||
|
||||
function await(Fiber $fiber): mixed
|
||||
{
|
||||
if (!$fiber->isStarted()) {
|
||||
return $fiber->start();
|
||||
}
|
||||
if ($fiber->isTerminated()) {
|
||||
return $fiber->getReturn();
|
||||
}
|
||||
return $fiber->resume();
|
||||
}
|
||||
|
||||
// Simulate async I/O
|
||||
function fetchData(string $url): Fiber
|
||||
{
|
||||
return async(function () use ($url) {
|
||||
echo "Fetching: {$url}\n";
|
||||
Fiber::suspend('pending');
|
||||
|
||||
// Simulate network delay
|
||||
sleep(1);
|
||||
|
||||
return "Data from {$url}";
|
||||
});
|
||||
}
|
||||
|
||||
// Usage
|
||||
$fiber1 = fetchData('https://api.example.com/users');
|
||||
$fiber2 = fetchData('https://api.example.com/posts');
|
||||
|
||||
await($fiber1);
|
||||
await($fiber2);
|
||||
|
||||
$result1 = await($fiber1);
|
||||
$result2 = await($fiber2);
|
||||
|
||||
echo "{$result1}\n";
|
||||
echo "{$result2}\n";
|
||||
```
|
||||
|
||||
## Amphp Framework
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Amp\Http\Server\HttpServer;
|
||||
use Amp\Http\Server\Request;
|
||||
use Amp\Http\Server\Response;
|
||||
use Amp\Http\Server\Router;
|
||||
use Amp\Socket\Server as SocketServer;
|
||||
use function Amp\async;
|
||||
use function Amp\Future\await;
|
||||
|
||||
// HTTP Server with Amphp
|
||||
$router = new Router();
|
||||
|
||||
$router->addRoute('GET', '/api/users', function (Request $request): Response {
|
||||
// Concurrent database queries
|
||||
$users = await([
|
||||
async(fn() => queryUsers()),
|
||||
async(fn() => queryUserStats()),
|
||||
]);
|
||||
|
||||
return new Response(
|
||||
status: 200,
|
||||
headers: ['content-type' => 'application/json'],
|
||||
body: json_encode(['users' => $users[0], 'stats' => $users[1]]),
|
||||
);
|
||||
});
|
||||
|
||||
$server = new HttpServer(
|
||||
servers: [SocketServer::listen('0.0.0.0:8080')],
|
||||
requestHandler: $router,
|
||||
);
|
||||
|
||||
$server->start();
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Technology | Use Case | Performance |
|
||||
|------------|----------|-------------|
|
||||
| Swoole | High-performance servers, WebSockets | Very High |
|
||||
| ReactPHP | Event-driven apps, real-time | High |
|
||||
| Amphp | Modern async framework | High |
|
||||
| Fibers | Native async (PHP 8.1+) | Medium |
|
||||
| Generators | Simple async patterns | Medium |
|
||||
|
||||
| Feature | Swoole | ReactPHP | Amphp |
|
||||
|---------|--------|----------|-------|
|
||||
| Coroutines | Yes | No (Promises) | Yes (Fibers) |
|
||||
| HTTP Server | Built-in | Via package | Via package |
|
||||
| WebSockets | Built-in | Via package | Via package |
|
||||
| Extension | Required | Not required | Not required |
|
||||
| Learning Curve | Medium | Low | Medium |
|
||||
Reference in New Issue
Block a user