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.
Files changed (63) hide show
  1. package/README.md +5 -4
  2. package/dist/index.js +1636 -1078
  3. package/package.json +1 -1
  4. package/presets/agy/AGENTS.md +46 -2
  5. package/presets/claude/CLAUDE.md +62 -2
  6. package/presets/claude/skills/android/SKILL.md +119 -0
  7. package/presets/claude/skills/laravel-specialist/SKILL.md +264 -0
  8. package/presets/claude/skills/laravel-specialist/references/eloquent.md +351 -0
  9. package/presets/claude/skills/laravel-specialist/references/livewire.md +512 -0
  10. package/presets/claude/skills/laravel-specialist/references/queues.md +423 -0
  11. package/presets/claude/skills/laravel-specialist/references/routing.md +362 -0
  12. package/presets/claude/skills/laravel-specialist/references/testing.md +522 -0
  13. package/presets/claude/skills/php-pro/SKILL.md +208 -0
  14. package/presets/claude/skills/php-pro/references/async-patterns.md +412 -0
  15. package/presets/claude/skills/php-pro/references/laravel-patterns.md +377 -0
  16. package/presets/claude/skills/php-pro/references/modern-php-features.md +323 -0
  17. package/presets/claude/skills/php-pro/references/symfony-patterns.md +466 -0
  18. package/presets/claude/skills/php-pro/references/testing-quality.md +466 -0
  19. package/presets/codex/AGENTS.md +47 -2
  20. package/presets/codex/skills/android/SKILL.md +119 -0
  21. package/presets/codex/skills/laravel-specialist/SKILL.md +264 -0
  22. package/presets/codex/skills/laravel-specialist/references/eloquent.md +351 -0
  23. package/presets/codex/skills/laravel-specialist/references/livewire.md +512 -0
  24. package/presets/codex/skills/laravel-specialist/references/queues.md +423 -0
  25. package/presets/codex/skills/laravel-specialist/references/routing.md +362 -0
  26. package/presets/codex/skills/laravel-specialist/references/testing.md +522 -0
  27. package/presets/codex/skills/php-pro/SKILL.md +208 -0
  28. package/presets/codex/skills/php-pro/references/async-patterns.md +412 -0
  29. package/presets/codex/skills/php-pro/references/laravel-patterns.md +377 -0
  30. package/presets/codex/skills/php-pro/references/modern-php-features.md +323 -0
  31. package/presets/codex/skills/php-pro/references/symfony-patterns.md +466 -0
  32. package/presets/codex/skills/php-pro/references/testing-quality.md +466 -0
  33. package/presets/opencode/agents/implementer.md +2 -2
  34. package/presets/opencode/agents/orchestrator.md +132 -5
  35. package/presets/opencode/agents/reviewer.md +33 -0
  36. package/presets/opencode/prompts/v0.3.0/architect.md +221 -0
  37. package/presets/opencode/prompts/v0.3.0/docs.md +189 -0
  38. package/presets/opencode/prompts/v0.3.0/implementer.md +162 -0
  39. package/presets/opencode/prompts/v0.3.0/orchestrator.md +360 -0
  40. package/presets/opencode/prompts/v0.3.0/repo-explorer.md +110 -0
  41. package/presets/opencode/prompts/v0.3.0/reviewer.md +225 -0
  42. package/presets/opencode/prompts/v0.3.0/task-coach.md +155 -0
  43. package/presets/opencode/prompts/v0.3.0/tester.md +251 -0
  44. package/presets/opencode/skills/android/SKILL.md +119 -0
  45. package/presets/opencode/skills/laravel-specialist/SKILL.md +264 -0
  46. package/presets/opencode/skills/laravel-specialist/references/eloquent.md +351 -0
  47. package/presets/opencode/skills/laravel-specialist/references/livewire.md +512 -0
  48. package/presets/opencode/skills/laravel-specialist/references/queues.md +423 -0
  49. package/presets/opencode/skills/laravel-specialist/references/routing.md +362 -0
  50. package/presets/opencode/skills/laravel-specialist/references/testing.md +522 -0
  51. package/presets/opencode/skills/php-pro/SKILL.md +208 -0
  52. package/presets/opencode/skills/php-pro/references/async-patterns.md +412 -0
  53. package/presets/opencode/skills/php-pro/references/laravel-patterns.md +377 -0
  54. package/presets/opencode/skills/php-pro/references/modern-php-features.md +323 -0
  55. package/presets/opencode/skills/php-pro/references/symfony-patterns.md +466 -0
  56. package/presets/opencode/skills/php-pro/references/testing-quality.md +466 -0
  57. package/src/presets/manifests/agy.yml +3 -2
  58. package/src/presets/manifests/claude.yml +3 -2
  59. package/src/presets/manifests/codex.yml +3 -2
  60. package/src/presets/manifests/cursor.yml +3 -2
  61. package/src/presets/manifests/gemini.yml +3 -2
  62. package/src/presets/manifests/opencode.yml +3 -2
  63. package/src/presets/models/opencode.yml +6 -6
@@ -0,0 +1,377 @@
1
+ # Laravel Patterns
2
+
3
+ ## Service Layer Pattern
4
+
5
+ ```php
6
+ <?php
7
+
8
+ declare(strict_types=1);
9
+
10
+ namespace App\Services;
11
+
12
+ use App\DTOs\CreateUserData;
13
+ use App\Models\User;
14
+ use App\Repositories\UserRepositoryInterface;
15
+ use Illuminate\Support\Facades\Hash;
16
+
17
+ final readonly class UserService
18
+ {
19
+ public function __construct(
20
+ private UserRepositoryInterface $userRepository,
21
+ private EmailService $emailService,
22
+ ) {}
23
+
24
+ public function createUser(CreateUserData $data): User
25
+ {
26
+ $user = $this->userRepository->create([
27
+ 'name' => $data->name,
28
+ 'email' => $data->email,
29
+ 'password' => Hash::make($data->password),
30
+ ]);
31
+
32
+ $this->emailService->sendWelcomeEmail($user);
33
+
34
+ return $user;
35
+ }
36
+
37
+ public function suspendUser(int $userId, string $reason): void
38
+ {
39
+ $user = $this->userRepository->findOrFail($userId);
40
+
41
+ $this->userRepository->update($user->id, [
42
+ 'status' => UserStatus::SUSPENDED,
43
+ 'suspension_reason' => $reason,
44
+ 'suspended_at' => now(),
45
+ ]);
46
+
47
+ $this->emailService->sendSuspensionNotice($user, $reason);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Repository Pattern
53
+
54
+ ```php
55
+ <?php
56
+
57
+ declare(strict_types=1);
58
+
59
+ namespace App\Repositories;
60
+
61
+ use App\Models\User;
62
+ use Illuminate\Database\Eloquent\Collection;
63
+
64
+ interface UserRepositoryInterface
65
+ {
66
+ public function findOrFail(int $id): User;
67
+ public function findByEmail(string $email): ?User;
68
+ public function create(array $data): User;
69
+ public function update(int $id, array $data): User;
70
+ public function delete(int $id): void;
71
+ public function getActive(): Collection;
72
+ }
73
+
74
+ final class UserRepository implements UserRepositoryInterface
75
+ {
76
+ public function findOrFail(int $id): User
77
+ {
78
+ return User::findOrFail($id);
79
+ }
80
+
81
+ public function findByEmail(string $email): ?User
82
+ {
83
+ return User::where('email', $email)->first();
84
+ }
85
+
86
+ public function create(array $data): User
87
+ {
88
+ return User::create($data);
89
+ }
90
+
91
+ public function update(int $id, array $data): User
92
+ {
93
+ $user = $this->findOrFail($id);
94
+ $user->update($data);
95
+ return $user->fresh();
96
+ }
97
+
98
+ public function delete(int $id): void
99
+ {
100
+ $this->findOrFail($id)->delete();
101
+ }
102
+
103
+ public function getActive(): Collection
104
+ {
105
+ return User::where('status', UserStatus::ACTIVE)
106
+ ->orderBy('created_at', 'desc')
107
+ ->get();
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Form Requests with Enums
113
+
114
+ ```php
115
+ <?php
116
+
117
+ declare(strict_types=1);
118
+
119
+ namespace App\Http\Requests;
120
+
121
+ use App\Enums\UserRole;
122
+ use Illuminate\Foundation\Http\FormRequest;
123
+ use Illuminate\Validation\Rule;
124
+ use Illuminate\Validation\Rules\Enum;
125
+ use Illuminate\Validation\Rules\Password;
126
+
127
+ final class CreateUserRequest extends FormRequest
128
+ {
129
+ public function authorize(): bool
130
+ {
131
+ return $this->user()?->can('create', User::class) ?? false;
132
+ }
133
+
134
+ public function rules(): array
135
+ {
136
+ return [
137
+ 'name' => ['required', 'string', 'max:255'],
138
+ 'email' => ['required', 'email', 'unique:users,email'],
139
+ 'password' => ['required', Password::min(8)->mixedCase()->numbers()],
140
+ 'role' => ['required', new Enum(UserRole::class)],
141
+ 'settings' => ['sometimes', 'array'],
142
+ 'settings.theme' => ['string', Rule::in(['light', 'dark'])],
143
+ ];
144
+ }
145
+
146
+ public function toDto(): CreateUserData
147
+ {
148
+ return new CreateUserData(
149
+ name: $this->validated('name'),
150
+ email: $this->validated('email'),
151
+ password: $this->validated('password'),
152
+ role: UserRole::from($this->validated('role')),
153
+ );
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## API Resources
159
+
160
+ ```php
161
+ <?php
162
+
163
+ declare(strict_types=1);
164
+
165
+ namespace App\Http\Resources;
166
+
167
+ use Illuminate\Http\Request;
168
+ use Illuminate\Http\Resources\Json\JsonResource;
169
+
170
+ /**
171
+ * @mixin \App\Models\User
172
+ */
173
+ final class UserResource extends JsonResource
174
+ {
175
+ public function toArray(Request $request): array
176
+ {
177
+ return [
178
+ 'id' => $this->id,
179
+ 'name' => $this->name,
180
+ 'email' => $this->email,
181
+ 'status' => $this->status->value,
182
+ 'role' => $this->role->value,
183
+ 'created_at' => $this->created_at->toIso8601String(),
184
+
185
+ // Conditional relationships
186
+ 'posts' => PostResource::collection($this->whenLoaded('posts')),
187
+ 'profile' => new ProfileResource($this->whenLoaded('profile')),
188
+
189
+ // Conditional attributes
190
+ 'is_admin' => $this->when($this->role === UserRole::ADMIN, true),
191
+
192
+ // Pivot data
193
+ 'team_role' => $this->whenPivotLoaded('team_user', fn() =>
194
+ $this->pivot->role
195
+ ),
196
+ ];
197
+ }
198
+ }
199
+
200
+ final class UserCollection extends ResourceCollection
201
+ {
202
+ public function toArray(Request $request): array
203
+ {
204
+ return [
205
+ 'data' => $this->collection,
206
+ 'meta' => [
207
+ 'total' => $this->total(),
208
+ 'per_page' => $this->perPage(),
209
+ ],
210
+ ];
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## Controllers with DTOs
216
+
217
+ ```php
218
+ <?php
219
+
220
+ declare(strict_types=1);
221
+
222
+ namespace App\Http\Controllers\Api;
223
+
224
+ use App\Http\Controllers\Controller;
225
+ use App\Http\Requests\CreateUserRequest;
226
+ use App\Http\Resources\UserResource;
227
+ use App\Services\UserService;
228
+ use Illuminate\Http\JsonResponse;
229
+ use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
230
+
231
+ final class UserController extends Controller
232
+ {
233
+ public function __construct(
234
+ private readonly UserService $userService,
235
+ ) {}
236
+
237
+ public function index(): AnonymousResourceCollection
238
+ {
239
+ $users = User::with('profile')
240
+ ->where('status', UserStatus::ACTIVE)
241
+ ->paginate(20);
242
+
243
+ return UserResource::collection($users);
244
+ }
245
+
246
+ public function store(CreateUserRequest $request): JsonResponse
247
+ {
248
+ $user = $this->userService->createUser($request->toDto());
249
+
250
+ return (new UserResource($user))
251
+ ->response()
252
+ ->setStatusCode(201);
253
+ }
254
+
255
+ public function show(User $user): UserResource
256
+ {
257
+ $user->load(['posts', 'profile']);
258
+ return new UserResource($user);
259
+ }
260
+
261
+ public function destroy(User $user): JsonResponse
262
+ {
263
+ $this->authorize('delete', $user);
264
+
265
+ $this->userService->deleteUser($user->id);
266
+
267
+ return response()->json(null, 204);
268
+ }
269
+ }
270
+ ```
271
+
272
+ ## Jobs & Queues
273
+
274
+ ```php
275
+ <?php
276
+
277
+ declare(strict_types=1);
278
+
279
+ namespace App\Jobs;
280
+
281
+ use App\Models\User;
282
+ use App\Services\EmailService;
283
+ use Illuminate\Bus\Queueable;
284
+ use Illuminate\Contracts\Queue\ShouldQueue;
285
+ use Illuminate\Foundation\Bus\Dispatchable;
286
+ use Illuminate\Queue\InteractsWithQueue;
287
+ use Illuminate\Queue\SerializesModels;
288
+
289
+ final class SendWelcomeEmail implements ShouldQueue
290
+ {
291
+ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
292
+
293
+ public int $tries = 3;
294
+ public int $timeout = 30;
295
+
296
+ public function __construct(
297
+ private readonly int $userId,
298
+ ) {}
299
+
300
+ public function handle(EmailService $emailService): void
301
+ {
302
+ $user = User::findOrFail($this->userId);
303
+ $emailService->sendWelcomeEmail($user);
304
+ }
305
+
306
+ public function failed(\Throwable $exception): void
307
+ {
308
+ \Log::error('Failed to send welcome email', [
309
+ 'user_id' => $this->userId,
310
+ 'error' => $exception->getMessage(),
311
+ ]);
312
+ }
313
+ }
314
+
315
+ // Dispatching jobs
316
+ SendWelcomeEmail::dispatch($user->id);
317
+ SendWelcomeEmail::dispatch($user->id)->delay(now()->addMinutes(5));
318
+ SendWelcomeEmail::dispatch($user->id)->onQueue('emails');
319
+ ```
320
+
321
+ ## Event Listeners
322
+
323
+ ```php
324
+ <?php
325
+
326
+ declare(strict_types=1);
327
+
328
+ namespace App\Events;
329
+
330
+ use App\Models\User;
331
+ use Illuminate\Foundation\Events\Dispatchable;
332
+ use Illuminate\Queue\SerializesModels;
333
+
334
+ final readonly class UserRegistered
335
+ {
336
+ use Dispatchable, SerializesModels;
337
+
338
+ public function __construct(
339
+ public User $user,
340
+ ) {}
341
+ }
342
+
343
+ namespace App\Listeners;
344
+
345
+ use App\Events\UserRegistered;
346
+ use App\Jobs\SendWelcomeEmail;
347
+ use Illuminate\Contracts\Queue\ShouldQueue;
348
+
349
+ final class SendWelcomeNotification implements ShouldQueue
350
+ {
351
+ public function handle(UserRegistered $event): void
352
+ {
353
+ SendWelcomeEmail::dispatch($event->user->id);
354
+ }
355
+ }
356
+
357
+ // In EventServiceProvider
358
+ protected $listen = [
359
+ UserRegistered::class => [
360
+ SendWelcomeNotification::class,
361
+ UpdateUserStatistics::class,
362
+ ],
363
+ ];
364
+ ```
365
+
366
+ ## Quick Reference
367
+
368
+ | Pattern | Purpose | File Location |
369
+ |---------|---------|---------------|
370
+ | Service | Business logic | `app/Services/` |
371
+ | Repository | Data access | `app/Repositories/` |
372
+ | Form Request | Validation | `app/Http/Requests/` |
373
+ | Resource | API responses | `app/Http/Resources/` |
374
+ | Job | Async tasks | `app/Jobs/` |
375
+ | Event | Domain events | `app/Events/` |
376
+ | DTO | Data transfer | `app/DTOs/` |
377
+ | Policy | Authorization | `app/Policies/` |
@@ -0,0 +1,323 @@
1
+ # Modern PHP 8.3+ Features
2
+
3
+ ## Strict Types & Type Declarations
4
+
5
+ ```php
6
+ <?php
7
+
8
+ declare(strict_types=1);
9
+
10
+ namespace App\Domain\User;
11
+
12
+ final readonly class User
13
+ {
14
+ public function __construct(
15
+ public int $id,
16
+ public string $email,
17
+ public UserStatus $status,
18
+ public \DateTimeImmutable $createdAt,
19
+ ) {}
20
+ }
21
+
22
+ function calculateTotal(int $price, float $taxRate): float
23
+ {
24
+ return $price * (1 + $taxRate);
25
+ }
26
+
27
+ // Union types
28
+ function processId(int|string $id): string
29
+ {
30
+ return is_int($id) ? (string)$id : $id;
31
+ }
32
+
33
+ // Intersection types
34
+ interface Timestamped {}
35
+ interface Authenticatable {}
36
+
37
+ function handleUser(Timestamped&Authenticatable $user): void {}
38
+ ```
39
+
40
+ ## Enums with Methods
41
+
42
+ ```php
43
+ <?php
44
+
45
+ declare(strict_types=1);
46
+
47
+ enum UserStatus: string
48
+ {
49
+ case ACTIVE = 'active';
50
+ case SUSPENDED = 'suspended';
51
+ case DELETED = 'deleted';
52
+
53
+ public function label(): string
54
+ {
55
+ return match($this) {
56
+ self::ACTIVE => 'Active User',
57
+ self::SUSPENDED => 'Suspended',
58
+ self::DELETED => 'Deleted User',
59
+ };
60
+ }
61
+
62
+ public function canLogin(): bool
63
+ {
64
+ return $this === self::ACTIVE;
65
+ }
66
+
67
+ public static function fromString(string $value): self
68
+ {
69
+ return self::from(strtolower($value));
70
+ }
71
+ }
72
+
73
+ enum HttpStatus: int
74
+ {
75
+ case OK = 200;
76
+ case CREATED = 201;
77
+ case BAD_REQUEST = 400;
78
+ case UNAUTHORIZED = 401;
79
+ case NOT_FOUND = 404;
80
+ case SERVER_ERROR = 500;
81
+
82
+ public function isSuccess(): bool
83
+ {
84
+ return $this->value >= 200 && $this->value < 300;
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## Readonly Properties & Classes
90
+
91
+ ```php
92
+ <?php
93
+
94
+ declare(strict_types=1);
95
+
96
+ // Readonly class (PHP 8.2+)
97
+ final readonly class Money
98
+ {
99
+ public function __construct(
100
+ public int $amount,
101
+ public string $currency,
102
+ ) {
103
+ if ($amount < 0) {
104
+ throw new \InvalidArgumentException('Amount cannot be negative');
105
+ }
106
+ }
107
+
108
+ public function add(Money $other): self
109
+ {
110
+ if ($this->currency !== $other->currency) {
111
+ throw new \InvalidArgumentException('Currency mismatch');
112
+ }
113
+ return new self($this->amount + $other->amount, $this->currency);
114
+ }
115
+ }
116
+
117
+ // Individual readonly properties
118
+ class Configuration
119
+ {
120
+ public function __construct(
121
+ public readonly string $apiKey,
122
+ public readonly string $apiSecret,
123
+ private string $cache = '',
124
+ ) {}
125
+ }
126
+ ```
127
+
128
+ ## Attributes (Metadata)
129
+
130
+ ```php
131
+ <?php
132
+
133
+ declare(strict_types=1);
134
+
135
+ #[\Attribute(\Attribute::TARGET_CLASS)]
136
+ final readonly class Route
137
+ {
138
+ public function __construct(
139
+ public string $path,
140
+ public string $method = 'GET',
141
+ public array $middleware = [],
142
+ ) {}
143
+ }
144
+
145
+ #[\Attribute(\Attribute::TARGET_PROPERTY)]
146
+ final readonly class Validate
147
+ {
148
+ public function __construct(
149
+ public ?string $rule = null,
150
+ public ?int $min = null,
151
+ public ?int $max = null,
152
+ ) {}
153
+ }
154
+
155
+ // Using attributes
156
+ #[Route('/api/users', method: 'POST', middleware: ['auth'])]
157
+ final class CreateUserController
158
+ {
159
+ public function __invoke(CreateUserRequest $request): JsonResponse
160
+ {
161
+ // ...
162
+ }
163
+ }
164
+
165
+ class UserDto
166
+ {
167
+ #[Validate(rule: 'email')]
168
+ public string $email;
169
+
170
+ #[Validate(min: 8, max: 100)]
171
+ public string $password;
172
+ }
173
+ ```
174
+
175
+ ## First-Class Callables
176
+
177
+ ```php
178
+ <?php
179
+
180
+ declare(strict_types=1);
181
+
182
+ class UserService
183
+ {
184
+ public function findById(int $id): ?User {}
185
+ public function create(array $data): User {}
186
+ }
187
+
188
+ $service = new UserService();
189
+
190
+ // PHP 8.1+ first-class callable syntax
191
+ $finder = $service->findById(...);
192
+ $user = $finder(42);
193
+
194
+ // Array operations
195
+ $numbers = [1, 2, 3, 4, 5];
196
+ $doubled = array_map(fn($n) => $n * 2, $numbers);
197
+
198
+ // Named arguments with callable
199
+ $result = array_filter(
200
+ array: $numbers,
201
+ callback: fn($n) => $n % 2 === 0,
202
+ );
203
+ ```
204
+
205
+ ## Match Expressions
206
+
207
+ ```php
208
+ <?php
209
+
210
+ declare(strict_types=1);
211
+
212
+ function getStatusColor(UserStatus $status): string
213
+ {
214
+ return match ($status) {
215
+ UserStatus::ACTIVE => 'green',
216
+ UserStatus::SUSPENDED => 'yellow',
217
+ UserStatus::DELETED => 'red',
218
+ };
219
+ }
220
+
221
+ function calculateShipping(int $weight, string $zone): float
222
+ {
223
+ return match (true) {
224
+ $weight < 1000 => 5.00,
225
+ $weight < 5000 && $zone === 'local' => 10.00,
226
+ $weight < 5000 => 15.00,
227
+ default => 25.00,
228
+ };
229
+ }
230
+
231
+ // Match with multiple conditions
232
+ function getHttpMessage(int $code): string
233
+ {
234
+ return match ($code) {
235
+ 200, 201, 204 => 'Success',
236
+ 400, 422 => 'Client Error',
237
+ 401, 403 => 'Unauthorized',
238
+ 500, 502, 503 => 'Server Error',
239
+ default => 'Unknown',
240
+ };
241
+ }
242
+ ```
243
+
244
+ ## Fibers (PHP 8.1+)
245
+
246
+ ```php
247
+ <?php
248
+
249
+ declare(strict_types=1);
250
+
251
+ // Basic fiber example
252
+ $fiber = new \Fiber(function (): void {
253
+ $value = \Fiber::suspend('fiber started');
254
+ echo "Received: {$value}\n";
255
+ \Fiber::suspend('second suspend');
256
+ echo "Fiber completed\n";
257
+ });
258
+
259
+ $result1 = $fiber->start();
260
+ echo "First result: {$result1}\n";
261
+
262
+ $result2 = $fiber->resume('data from main');
263
+ echo "Second result: {$result2}\n";
264
+
265
+ $fiber->resume('final data');
266
+
267
+ // Async-style with fibers
268
+ function async(callable $callback): \Fiber
269
+ {
270
+ return new \Fiber($callback);
271
+ }
272
+
273
+ function await(\Fiber $fiber): mixed
274
+ {
275
+ if (!$fiber->isStarted()) {
276
+ return $fiber->start();
277
+ }
278
+ return $fiber->resume();
279
+ }
280
+ ```
281
+
282
+ ## Never Type
283
+
284
+ ```php
285
+ <?php
286
+
287
+ declare(strict_types=1);
288
+
289
+ function redirect(string $url): never
290
+ {
291
+ header("Location: {$url}");
292
+ exit;
293
+ }
294
+
295
+ function abort(int $code, string $message): never
296
+ {
297
+ http_response_code($code);
298
+ echo json_encode(['error' => $message]);
299
+ exit;
300
+ }
301
+
302
+ class NotFoundException extends \Exception
303
+ {
304
+ public static function throw(string $resource): never
305
+ {
306
+ throw new self("Resource not found: {$resource}");
307
+ }
308
+ }
309
+ ```
310
+
311
+ ## Quick Reference
312
+
313
+ | Feature | PHP Version | Usage |
314
+ |---------|-------------|-------|
315
+ | Readonly properties | 8.1+ | `public readonly string $name` |
316
+ | Readonly classes | 8.2+ | `readonly class User {}` |
317
+ | Enums | 8.1+ | `enum Status: string {}` |
318
+ | First-class callables | 8.1+ | `$fn = $obj->method(...)` |
319
+ | Never type | 8.1+ | `function exit(): never` |
320
+ | Fibers | 8.1+ | `new \Fiber(fn() => ...)` |
321
+ | Pure intersection types | 8.1+ | `A&B $param` |
322
+ | DNF types | 8.2+ | `(A&B)\|C $param` |
323
+ | Constants in traits | 8.2+ | `trait T { const X = 1; }` |