cc-codeconductor 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/index.js +1636 -1078
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +46 -2
- package/presets/claude/CLAUDE.md +62 -2
- package/presets/claude/skills/android/SKILL.md +119 -0
- package/presets/claude/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/claude/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/claude/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/claude/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/claude/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/claude/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/claude/skills/php-pro/SKILL.md +208 -0
- package/presets/claude/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/claude/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/claude/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/claude/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/claude/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/codex/AGENTS.md +47 -2
- package/presets/codex/skills/android/SKILL.md +119 -0
- package/presets/codex/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/codex/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/codex/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/codex/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/codex/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/codex/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/codex/skills/php-pro/SKILL.md +208 -0
- package/presets/codex/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/codex/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/codex/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/codex/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/codex/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/opencode/agents/implementer.md +2 -2
- package/presets/opencode/agents/orchestrator.md +132 -5
- package/presets/opencode/agents/reviewer.md +33 -0
- package/presets/opencode/prompts/v0.3.0/architect.md +221 -0
- package/presets/opencode/prompts/v0.3.0/docs.md +189 -0
- package/presets/opencode/prompts/v0.3.0/implementer.md +162 -0
- package/presets/opencode/prompts/v0.3.0/orchestrator.md +360 -0
- package/presets/opencode/prompts/v0.3.0/repo-explorer.md +110 -0
- package/presets/opencode/prompts/v0.3.0/reviewer.md +225 -0
- package/presets/opencode/prompts/v0.3.0/task-coach.md +155 -0
- package/presets/opencode/prompts/v0.3.0/tester.md +251 -0
- package/presets/opencode/skills/android/SKILL.md +119 -0
- package/presets/opencode/skills/laravel-specialist/SKILL.md +264 -0
- package/presets/opencode/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/opencode/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/opencode/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/opencode/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/opencode/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/opencode/skills/php-pro/SKILL.md +208 -0
- package/presets/opencode/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/opencode/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/opencode/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/opencode/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/opencode/skills/php-pro/references/testing-quality.md +466 -0
- package/src/presets/manifests/agy.yml +3 -2
- package/src/presets/manifests/claude.yml +3 -2
- package/src/presets/manifests/codex.yml +3 -2
- package/src/presets/manifests/cursor.yml +3 -2
- package/src/presets/manifests/gemini.yml +3 -2
- package/src/presets/manifests/opencode.yml +3 -2
- package/src/presets/models/opencode.yml +6 -6
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: php-pro
|
|
3
|
+
description: Use when building PHP applications with modern PHP 8.3+ features, Laravel, or Symfony frameworks. Invokes strict typing, PHPStan level 9, async patterns with Swoole, and PSR standards. Creates controllers, configures middleware, generates migrations, writes PHPUnit/Pest tests, defines typed DTOs and value objects, sets up dependency injection, and scaffolds REST/GraphQL APIs. Use when working with Eloquent, Doctrine, Composer, Psalm, ReactPHP, or any PHP API development.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: https://github.com/Jeffallan
|
|
7
|
+
version: "1.1.0"
|
|
8
|
+
domain: language
|
|
9
|
+
triggers: PHP, Laravel, Symfony, Composer, PHPStan, PSR, PHP API, Eloquent, Doctrine
|
|
10
|
+
role: specialist
|
|
11
|
+
scope: implementation
|
|
12
|
+
output-format: code
|
|
13
|
+
related-skills: fullstack-guardian, fastapi-expert
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# PHP Pro
|
|
17
|
+
|
|
18
|
+
Senior PHP developer with deep expertise in PHP 8.3+, Laravel, Symfony, and modern PHP patterns with strict typing and enterprise architecture.
|
|
19
|
+
|
|
20
|
+
## Core Workflow
|
|
21
|
+
|
|
22
|
+
1. **Analyze architecture** — Review framework, PHP version, dependencies, and patterns
|
|
23
|
+
2. **Design models** — Create typed domain models, value objects, DTOs
|
|
24
|
+
3. **Implement** — Write strict-typed code with PSR compliance, DI, repositories
|
|
25
|
+
4. **Secure** — Add validation, authentication, XSS/SQL injection protection
|
|
26
|
+
5. **Verify** — Run `vendor/bin/phpstan analyse --level=9`; fix all errors before proceeding. Run `vendor/bin/phpunit` or `vendor/bin/pest`; enforce 80%+ coverage. Only deliver when both pass clean.
|
|
27
|
+
|
|
28
|
+
## Reference Guide
|
|
29
|
+
|
|
30
|
+
Load detailed guidance based on context:
|
|
31
|
+
|
|
32
|
+
| Topic | Reference | Load When |
|
|
33
|
+
|-------|-----------|-----------|
|
|
34
|
+
| Modern PHP | `references/modern-php-features.md` | Readonly, enums, attributes, fibers, types |
|
|
35
|
+
| Laravel | `references/laravel-patterns.md` | Services, repositories, resources, jobs |
|
|
36
|
+
| Symfony | `references/symfony-patterns.md` | DI, events, commands, voters |
|
|
37
|
+
| Async PHP | `references/async-patterns.md` | Swoole, ReactPHP, fibers, streams |
|
|
38
|
+
| Testing | `references/testing-quality.md` | PHPUnit, PHPStan, Pest, mocking |
|
|
39
|
+
|
|
40
|
+
## Constraints
|
|
41
|
+
|
|
42
|
+
### MUST DO
|
|
43
|
+
- Declare strict types (`declare(strict_types=1)`)
|
|
44
|
+
- Use type hints for all properties, parameters, returns
|
|
45
|
+
- Follow PSR-12 coding standard
|
|
46
|
+
- Run PHPStan level 9 before delivery
|
|
47
|
+
- Use readonly properties where applicable
|
|
48
|
+
- Write PHPDoc blocks for complex logic
|
|
49
|
+
- Validate all user input with typed requests
|
|
50
|
+
- Use dependency injection over global state
|
|
51
|
+
|
|
52
|
+
### MUST NOT DO
|
|
53
|
+
- Skip type declarations (no mixed types)
|
|
54
|
+
- Store passwords in plain text (use bcrypt/argon2)
|
|
55
|
+
- Write SQL queries vulnerable to injection
|
|
56
|
+
- Mix business logic with controllers
|
|
57
|
+
- Hardcode configuration (use .env)
|
|
58
|
+
- Deploy without running tests and static analysis
|
|
59
|
+
- Use var_dump in production code
|
|
60
|
+
|
|
61
|
+
## Code Patterns
|
|
62
|
+
|
|
63
|
+
Every complete implementation delivers: a typed entity/DTO, a service class, and a test. Use these as the baseline structure.
|
|
64
|
+
|
|
65
|
+
### Readonly DTO / Value Object
|
|
66
|
+
|
|
67
|
+
```php
|
|
68
|
+
<?php
|
|
69
|
+
|
|
70
|
+
declare(strict_types=1);
|
|
71
|
+
|
|
72
|
+
namespace App\DTO;
|
|
73
|
+
|
|
74
|
+
final readonly class CreateUserDTO
|
|
75
|
+
{
|
|
76
|
+
public function __construct(
|
|
77
|
+
public string $name,
|
|
78
|
+
public string $email,
|
|
79
|
+
public string $password,
|
|
80
|
+
) {}
|
|
81
|
+
|
|
82
|
+
public static function fromArray(array $data): self
|
|
83
|
+
{
|
|
84
|
+
return new self(
|
|
85
|
+
name: $data['name'],
|
|
86
|
+
email: $data['email'],
|
|
87
|
+
password: $data['password'],
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Typed Service with Constructor DI
|
|
94
|
+
|
|
95
|
+
```php
|
|
96
|
+
<?php
|
|
97
|
+
|
|
98
|
+
declare(strict_types=1);
|
|
99
|
+
|
|
100
|
+
namespace App\Services;
|
|
101
|
+
|
|
102
|
+
use App\DTO\CreateUserDTO;
|
|
103
|
+
use App\Models\User;
|
|
104
|
+
use App\Repositories\UserRepositoryInterface;
|
|
105
|
+
use Illuminate\Support\Facades\Hash;
|
|
106
|
+
|
|
107
|
+
final class UserService
|
|
108
|
+
{
|
|
109
|
+
public function __construct(
|
|
110
|
+
private readonly UserRepositoryInterface $users,
|
|
111
|
+
) {}
|
|
112
|
+
|
|
113
|
+
public function create(CreateUserDTO $dto): User
|
|
114
|
+
{
|
|
115
|
+
return $this->users->create([
|
|
116
|
+
'name' => $dto->name,
|
|
117
|
+
'email' => $dto->email,
|
|
118
|
+
'password' => Hash::make($dto->password),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### PHPUnit Test Structure
|
|
125
|
+
|
|
126
|
+
```php
|
|
127
|
+
<?php
|
|
128
|
+
|
|
129
|
+
declare(strict_types=1);
|
|
130
|
+
|
|
131
|
+
namespace Tests\Unit\Services;
|
|
132
|
+
|
|
133
|
+
use App\DTO\CreateUserDTO;
|
|
134
|
+
use App\Models\User;
|
|
135
|
+
use App\Repositories\UserRepositoryInterface;
|
|
136
|
+
use App\Services\UserService;
|
|
137
|
+
use PHPUnit\Framework\MockObject\MockObject;
|
|
138
|
+
use PHPUnit\Framework\TestCase;
|
|
139
|
+
|
|
140
|
+
final class UserServiceTest extends TestCase
|
|
141
|
+
{
|
|
142
|
+
private UserRepositoryInterface&MockObject $users;
|
|
143
|
+
private UserService $service;
|
|
144
|
+
|
|
145
|
+
protected function setUp(): void
|
|
146
|
+
{
|
|
147
|
+
parent::setUp();
|
|
148
|
+
$this->users = $this->createMock(UserRepositoryInterface::class);
|
|
149
|
+
$this->service = new UserService($this->users);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public function testCreateHashesPassword(): void
|
|
153
|
+
{
|
|
154
|
+
$dto = new CreateUserDTO('Alice', 'alice@example.com', 'secret');
|
|
155
|
+
$user = new User(['name' => 'Alice', 'email' => 'alice@example.com']);
|
|
156
|
+
|
|
157
|
+
$this->users
|
|
158
|
+
->expects($this->once())
|
|
159
|
+
->method('create')
|
|
160
|
+
->willReturn($user);
|
|
161
|
+
|
|
162
|
+
$result = $this->service->create($dto);
|
|
163
|
+
|
|
164
|
+
$this->assertSame('Alice', $result->name);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Enum (PHP 8.1+)
|
|
170
|
+
|
|
171
|
+
```php
|
|
172
|
+
<?php
|
|
173
|
+
|
|
174
|
+
declare(strict_types=1);
|
|
175
|
+
|
|
176
|
+
namespace App\Enums;
|
|
177
|
+
|
|
178
|
+
enum UserStatus: string
|
|
179
|
+
{
|
|
180
|
+
case Active = 'active';
|
|
181
|
+
case Inactive = 'inactive';
|
|
182
|
+
case Banned = 'banned';
|
|
183
|
+
|
|
184
|
+
public function label(): string
|
|
185
|
+
{
|
|
186
|
+
return match($this) {
|
|
187
|
+
self::Active => 'Active',
|
|
188
|
+
self::Inactive => 'Inactive',
|
|
189
|
+
self::Banned => 'Banned',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Output Templates
|
|
196
|
+
|
|
197
|
+
When implementing a feature, deliver in this order:
|
|
198
|
+
1. Domain models (entities, value objects, enums)
|
|
199
|
+
2. Service/repository classes
|
|
200
|
+
3. Controller/API endpoints
|
|
201
|
+
4. Test files (PHPUnit/Pest)
|
|
202
|
+
5. Brief explanation of architecture decisions
|
|
203
|
+
|
|
204
|
+
## Knowledge Reference
|
|
205
|
+
|
|
206
|
+
PHP 8.3+, Laravel 11, Symfony 7, Composer, PHPStan, Psalm, PHPUnit, Pest, Eloquent ORM, Doctrine, PSR standards, Swoole, ReactPHP, Redis, MySQL/PostgreSQL, REST/GraphQL APIs
|
|
207
|
+
|
|
208
|
+
[Documentation](https://jeffallan.github.io/claude-skills/skills/language/php-pro/)
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Async PHP Patterns
|
|
2
|
+
|
|
3
|
+
## Swoole HTTP Server
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
|
|
8
|
+
declare(strict_types=1);
|
|
9
|
+
|
|
10
|
+
use Swoole\HTTP\Server;
|
|
11
|
+
use Swoole\HTTP\Request;
|
|
12
|
+
use Swoole\HTTP\Response;
|
|
13
|
+
|
|
14
|
+
$server = new Server('0.0.0.0', 9501);
|
|
15
|
+
|
|
16
|
+
$server->set([
|
|
17
|
+
'worker_num' => 4,
|
|
18
|
+
'max_request' => 10000,
|
|
19
|
+
'task_worker_num' => 2,
|
|
20
|
+
'enable_coroutine' => true,
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
$server->on('start', function (Server $server) {
|
|
24
|
+
echo "Swoole HTTP server started at http://0.0.0.0:9501\n";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
$server->on('request', function (Request $request, Response $response) {
|
|
28
|
+
$response->header('Content-Type', 'application/json');
|
|
29
|
+
|
|
30
|
+
match ($request->server['request_uri']) {
|
|
31
|
+
'/api/users' => handleUsers($request, $response),
|
|
32
|
+
'/api/health' => $response->end(json_encode(['status' => 'healthy'])),
|
|
33
|
+
default => $response->status(404)->end(json_encode(['error' => 'Not found'])),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function handleUsers(Request $request, Response $response): void
|
|
38
|
+
{
|
|
39
|
+
// Coroutine for concurrent DB queries
|
|
40
|
+
go(function () use ($response) {
|
|
41
|
+
$users = queryDatabase('SELECT * FROM users LIMIT 10');
|
|
42
|
+
$response->end(json_encode(['data' => $users]));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$server->start();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Swoole Coroutines
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
<?php
|
|
53
|
+
|
|
54
|
+
declare(strict_types=1);
|
|
55
|
+
|
|
56
|
+
use Swoole\Coroutine;
|
|
57
|
+
use Swoole\Coroutine\Http\Client;
|
|
58
|
+
|
|
59
|
+
// Concurrent HTTP requests
|
|
60
|
+
Coroutine\run(function () {
|
|
61
|
+
$results = [];
|
|
62
|
+
|
|
63
|
+
// Create multiple coroutines
|
|
64
|
+
$wg = new Coroutine\WaitGroup();
|
|
65
|
+
|
|
66
|
+
$urls = [
|
|
67
|
+
'https://api.example.com/users',
|
|
68
|
+
'https://api.example.com/posts',
|
|
69
|
+
'https://api.example.com/comments',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
foreach ($urls as $url) {
|
|
73
|
+
$wg->add();
|
|
74
|
+
go(function () use ($url, &$results, $wg) {
|
|
75
|
+
$client = new Client(parse_url($url, PHP_URL_HOST), 443, true);
|
|
76
|
+
$client->set(['timeout' => 5]);
|
|
77
|
+
$client->get(parse_url($url, PHP_URL_PATH));
|
|
78
|
+
|
|
79
|
+
$results[$url] = [
|
|
80
|
+
'status' => $client->statusCode,
|
|
81
|
+
'body' => $client->body,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
$client->close();
|
|
85
|
+
$wg->done();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$wg->wait();
|
|
90
|
+
|
|
91
|
+
print_r($results);
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Swoole Async MySQL
|
|
96
|
+
|
|
97
|
+
```php
|
|
98
|
+
<?php
|
|
99
|
+
|
|
100
|
+
declare(strict_types=1);
|
|
101
|
+
|
|
102
|
+
use Swoole\Coroutine;
|
|
103
|
+
use Swoole\Coroutine\MySQL;
|
|
104
|
+
|
|
105
|
+
Coroutine\run(function () {
|
|
106
|
+
$mysql = new MySQL();
|
|
107
|
+
|
|
108
|
+
$connected = $mysql->connect([
|
|
109
|
+
'host' => '127.0.0.1',
|
|
110
|
+
'port' => 3306,
|
|
111
|
+
'user' => 'root',
|
|
112
|
+
'password' => 'password',
|
|
113
|
+
'database' => 'test',
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
if (!$connected) {
|
|
117
|
+
throw new \RuntimeException($mysql->connect_error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Async query
|
|
121
|
+
$result = $mysql->query('SELECT * FROM users WHERE active = 1');
|
|
122
|
+
|
|
123
|
+
foreach ($result as $row) {
|
|
124
|
+
echo "User: {$row['name']}\n";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Prepared statements
|
|
128
|
+
$stmt = $mysql->prepare('SELECT * FROM users WHERE id = ?');
|
|
129
|
+
$stmt->execute([42]);
|
|
130
|
+
$user = $stmt->fetchAll();
|
|
131
|
+
|
|
132
|
+
$mysql->close();
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Swoole Channel (Communication)
|
|
137
|
+
|
|
138
|
+
```php
|
|
139
|
+
<?php
|
|
140
|
+
|
|
141
|
+
declare(strict_types=1);
|
|
142
|
+
|
|
143
|
+
use Swoole\Coroutine;
|
|
144
|
+
use Swoole\Coroutine\Channel;
|
|
145
|
+
|
|
146
|
+
Coroutine\run(function () {
|
|
147
|
+
$channel = new Channel(10); // Buffer size: 10
|
|
148
|
+
|
|
149
|
+
// Producer
|
|
150
|
+
go(function () use ($channel) {
|
|
151
|
+
for ($i = 1; $i <= 5; $i++) {
|
|
152
|
+
$channel->push("Task {$i}");
|
|
153
|
+
echo "Produced: Task {$i}\n";
|
|
154
|
+
Coroutine::sleep(0.5);
|
|
155
|
+
}
|
|
156
|
+
$channel->close();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Consumer
|
|
160
|
+
go(function () use ($channel) {
|
|
161
|
+
while (true) {
|
|
162
|
+
$task = $channel->pop();
|
|
163
|
+
if ($task === false && $channel->errCode === SWOOLE_CHANNEL_CLOSED) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
echo "Consumed: {$task}\n";
|
|
167
|
+
Coroutine::sleep(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## ReactPHP Event Loop
|
|
174
|
+
|
|
175
|
+
```php
|
|
176
|
+
<?php
|
|
177
|
+
|
|
178
|
+
declare(strict_types=1);
|
|
179
|
+
|
|
180
|
+
require 'vendor/autoload.php';
|
|
181
|
+
|
|
182
|
+
use React\EventLoop\Loop;
|
|
183
|
+
use React\Http\Message\Response;
|
|
184
|
+
use Psr\Http\Message\ServerRequestInterface;
|
|
185
|
+
|
|
186
|
+
// HTTP Server
|
|
187
|
+
$server = new React\Http\HttpServer(function (ServerRequestInterface $request) {
|
|
188
|
+
return new Response(
|
|
189
|
+
200,
|
|
190
|
+
['Content-Type' => 'application/json'],
|
|
191
|
+
json_encode([
|
|
192
|
+
'method' => $request->getMethod(),
|
|
193
|
+
'uri' => (string) $request->getUri(),
|
|
194
|
+
'timestamp' => time(),
|
|
195
|
+
])
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
$socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
|
200
|
+
$server->listen($socket);
|
|
201
|
+
|
|
202
|
+
echo "Server running at http://0.0.0.0:8080\n";
|
|
203
|
+
|
|
204
|
+
// Periodic timer
|
|
205
|
+
Loop::addPeriodicTimer(5.0, function () {
|
|
206
|
+
echo "Heartbeat: " . date('H:i:s') . "\n";
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// One-time timer
|
|
210
|
+
Loop::addTimer(10.0, function () {
|
|
211
|
+
echo "This runs once after 10 seconds\n";
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## ReactPHP Async MySQL
|
|
216
|
+
|
|
217
|
+
```php
|
|
218
|
+
<?php
|
|
219
|
+
|
|
220
|
+
declare(strict_types=1);
|
|
221
|
+
|
|
222
|
+
require 'vendor/autoload.php';
|
|
223
|
+
|
|
224
|
+
use React\MySQL\Factory;
|
|
225
|
+
use React\MySQL\QueryResult;
|
|
226
|
+
|
|
227
|
+
$factory = new Factory();
|
|
228
|
+
|
|
229
|
+
$connection = $factory->createLazyConnection('root:password@localhost/database');
|
|
230
|
+
|
|
231
|
+
$connection->query('SELECT * FROM users WHERE active = 1')
|
|
232
|
+
->then(
|
|
233
|
+
function (QueryResult $result) {
|
|
234
|
+
echo "Found " . count($result->resultRows) . " users\n";
|
|
235
|
+
foreach ($result->resultRows as $row) {
|
|
236
|
+
echo "User: {$row['name']}\n";
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
function (\Exception $error) {
|
|
240
|
+
echo "Error: " . $error->getMessage() . "\n";
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Prepared statements
|
|
245
|
+
$connection->query('SELECT * FROM users WHERE id = ?', [42])
|
|
246
|
+
->then(function (QueryResult $result) {
|
|
247
|
+
$user = $result->resultRows[0] ?? null;
|
|
248
|
+
var_dump($user);
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## ReactPHP Promises
|
|
253
|
+
|
|
254
|
+
```php
|
|
255
|
+
<?php
|
|
256
|
+
|
|
257
|
+
declare(strict_types=1);
|
|
258
|
+
|
|
259
|
+
use React\Promise\Promise;
|
|
260
|
+
use React\Promise\Deferred;
|
|
261
|
+
use function React\Promise\all;
|
|
262
|
+
|
|
263
|
+
// Creating promises
|
|
264
|
+
function fetchUser(int $id): Promise
|
|
265
|
+
{
|
|
266
|
+
$deferred = new Deferred();
|
|
267
|
+
|
|
268
|
+
// Simulate async operation
|
|
269
|
+
Loop::addTimer(1.0, function () use ($deferred, $id) {
|
|
270
|
+
$deferred->resolve([
|
|
271
|
+
'id' => $id,
|
|
272
|
+
'name' => "User {$id}",
|
|
273
|
+
]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return $deferred->promise();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Using promises
|
|
280
|
+
fetchUser(42)
|
|
281
|
+
->then(function ($user) {
|
|
282
|
+
echo "Got user: {$user['name']}\n";
|
|
283
|
+
return fetchUserPosts($user['id']);
|
|
284
|
+
})
|
|
285
|
+
->then(function ($posts) {
|
|
286
|
+
echo "Got " . count($posts) . " posts\n";
|
|
287
|
+
})
|
|
288
|
+
->catch(function (\Exception $error) {
|
|
289
|
+
echo "Error: " . $error->getMessage() . "\n";
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Parallel promises
|
|
293
|
+
all([
|
|
294
|
+
fetchUser(1),
|
|
295
|
+
fetchUser(2),
|
|
296
|
+
fetchUser(3),
|
|
297
|
+
])->then(function ($users) {
|
|
298
|
+
echo "Fetched " . count($users) . " users\n";
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## PHP Fibers (Native PHP 8.1+)
|
|
303
|
+
|
|
304
|
+
```php
|
|
305
|
+
<?php
|
|
306
|
+
|
|
307
|
+
declare(strict_types=1);
|
|
308
|
+
|
|
309
|
+
// Simple async function using fibers
|
|
310
|
+
function async(callable $callback): Fiber
|
|
311
|
+
{
|
|
312
|
+
return new Fiber($callback);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function await(Fiber $fiber): mixed
|
|
316
|
+
{
|
|
317
|
+
if (!$fiber->isStarted()) {
|
|
318
|
+
return $fiber->start();
|
|
319
|
+
}
|
|
320
|
+
if ($fiber->isTerminated()) {
|
|
321
|
+
return $fiber->getReturn();
|
|
322
|
+
}
|
|
323
|
+
return $fiber->resume();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Simulate async I/O
|
|
327
|
+
function fetchData(string $url): Fiber
|
|
328
|
+
{
|
|
329
|
+
return async(function () use ($url) {
|
|
330
|
+
echo "Fetching: {$url}\n";
|
|
331
|
+
Fiber::suspend('pending');
|
|
332
|
+
|
|
333
|
+
// Simulate network delay
|
|
334
|
+
sleep(1);
|
|
335
|
+
|
|
336
|
+
return "Data from {$url}";
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Usage
|
|
341
|
+
$fiber1 = fetchData('https://api.example.com/users');
|
|
342
|
+
$fiber2 = fetchData('https://api.example.com/posts');
|
|
343
|
+
|
|
344
|
+
await($fiber1);
|
|
345
|
+
await($fiber2);
|
|
346
|
+
|
|
347
|
+
$result1 = await($fiber1);
|
|
348
|
+
$result2 = await($fiber2);
|
|
349
|
+
|
|
350
|
+
echo "{$result1}\n";
|
|
351
|
+
echo "{$result2}\n";
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Amphp Framework
|
|
355
|
+
|
|
356
|
+
```php
|
|
357
|
+
<?php
|
|
358
|
+
|
|
359
|
+
declare(strict_types=1);
|
|
360
|
+
|
|
361
|
+
require 'vendor/autoload.php';
|
|
362
|
+
|
|
363
|
+
use Amp\Http\Server\HttpServer;
|
|
364
|
+
use Amp\Http\Server\Request;
|
|
365
|
+
use Amp\Http\Server\Response;
|
|
366
|
+
use Amp\Http\Server\Router;
|
|
367
|
+
use Amp\Socket\Server as SocketServer;
|
|
368
|
+
use function Amp\async;
|
|
369
|
+
use function Amp\Future\await;
|
|
370
|
+
|
|
371
|
+
// HTTP Server with Amphp
|
|
372
|
+
$router = new Router();
|
|
373
|
+
|
|
374
|
+
$router->addRoute('GET', '/api/users', function (Request $request): Response {
|
|
375
|
+
// Concurrent database queries
|
|
376
|
+
$users = await([
|
|
377
|
+
async(fn() => queryUsers()),
|
|
378
|
+
async(fn() => queryUserStats()),
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
return new Response(
|
|
382
|
+
status: 200,
|
|
383
|
+
headers: ['content-type' => 'application/json'],
|
|
384
|
+
body: json_encode(['users' => $users[0], 'stats' => $users[1]]),
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
$server = new HttpServer(
|
|
389
|
+
servers: [SocketServer::listen('0.0.0.0:8080')],
|
|
390
|
+
requestHandler: $router,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
$server->start();
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Quick Reference
|
|
397
|
+
|
|
398
|
+
| Technology | Use Case | Performance |
|
|
399
|
+
|------------|----------|-------------|
|
|
400
|
+
| Swoole | High-performance servers, WebSockets | Very High |
|
|
401
|
+
| ReactPHP | Event-driven apps, real-time | High |
|
|
402
|
+
| Amphp | Modern async framework | High |
|
|
403
|
+
| Fibers | Native async (PHP 8.1+) | Medium |
|
|
404
|
+
| Generators | Simple async patterns | Medium |
|
|
405
|
+
|
|
406
|
+
| Feature | Swoole | ReactPHP | Amphp |
|
|
407
|
+
|---------|--------|----------|-------|
|
|
408
|
+
| Coroutines | Yes | No (Promises) | Yes (Fibers) |
|
|
409
|
+
| HTTP Server | Built-in | Via package | Via package |
|
|
410
|
+
| WebSockets | Built-in | Via package | Via package |
|
|
411
|
+
| Extension | Required | Not required | Not required |
|
|
412
|
+
| Learning Curve | Medium | Low | Medium |
|