cc-codeconductor 0.4.3 → 0.5.0
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 +1 -1
- package/dist/index.js +2642 -226
- package/package.json +1 -1
- package/presets/agy/skills/evaluation/SKILL.md +6 -0
- package/presets/agy/skills/openspec/SKILL.md +32 -0
- package/presets/agy/workflows/cc-openspec.md +62 -0
- package/presets/agy/workflows/cc-scorecard.md +17 -0
- package/presets/claude/commands/cc/openspec.md +144 -0
- package/presets/claude/commands/cc/review.md +13 -2
- package/presets/claude/commands/cc/scorecard.md +65 -0
- package/presets/claude/skills/evaluation/SKILL.md +42 -0
- package/presets/claude/skills/openspec/SKILL.md +54 -0
- package/presets/codex/AGENTS.md +57 -0
- package/presets/cursor/.cursorignore +15 -0
- package/presets/cursor/AGENTS.md +504 -0
- package/presets/cursor/agents/architect.md +211 -0
- package/presets/cursor/agents/complexity-auditor.md +76 -0
- package/presets/cursor/agents/contract-builder.md +75 -0
- package/presets/cursor/agents/docs.md +180 -0
- package/presets/cursor/agents/goal-planner.md +71 -0
- package/presets/cursor/agents/implementer.md +161 -0
- package/presets/cursor/agents/orchestrator.md +377 -0
- package/presets/cursor/agents/repo-explorer.md +100 -0
- package/presets/cursor/agents/reviewer.md +237 -0
- package/presets/cursor/agents/security-reviewer.md +113 -0
- package/presets/cursor/agents/task-coach.md +145 -0
- package/presets/cursor/agents/tester.md +241 -0
- package/presets/cursor/commands/cc/api-contract.md +58 -0
- package/presets/cursor/commands/cc/db-migration.md +58 -0
- package/presets/cursor/commands/cc/feature.md +115 -0
- package/presets/cursor/commands/cc/fix.md +121 -0
- package/presets/cursor/commands/cc/openspec.md +144 -0
- package/presets/cursor/commands/cc/pagespeed.md +103 -0
- package/presets/cursor/commands/cc/refactor.md +148 -0
- package/presets/cursor/commands/cc/review.md +137 -0
- package/presets/cursor/commands/cc/scorecard.md +65 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
- package/presets/cursor/commands/cc/test-plan.md +138 -0
- package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
- package/presets/cursor/rules/context-budget.mdc +12 -0
- package/presets/cursor/rules/orchestration.mdc +12 -0
- package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
- package/presets/cursor/skills/android/SKILL.md +122 -0
- package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
- package/presets/cursor/skills/astro/SKILL.md +322 -0
- package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
- package/presets/cursor/skills/code-review/SKILL.md +208 -0
- package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
- package/presets/cursor/skills/django-orm/SKILL.md +463 -0
- package/presets/cursor/skills/django-testing/SKILL.md +417 -0
- package/presets/cursor/skills/django-uv/SKILL.md +409 -0
- package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
- package/presets/cursor/skills/evaluation/SKILL.md +8 -0
- package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
- package/presets/cursor/skills/find-skills/SKILL.md +144 -0
- package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
- package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
- package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
- package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
- package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
- package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
- package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
- package/presets/cursor/skills/openspec/SKILL.md +52 -0
- package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
- package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
- package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
- package/presets/cursor/skills/php-pro/SKILL.md +210 -0
- package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/cursor/skills/python/SKILL.md +613 -0
- package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
- package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
- package/presets/cursor/skills/security/SKILL.md +384 -0
- package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
- package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
- package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
- package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
- package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
- package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
- package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
- package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
- package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
- package/presets/opencode/agents/architect.md +1 -2
- package/presets/opencode/agents/complexity-auditor.md +1 -0
- package/presets/opencode/agents/contract-builder.md +93 -0
- package/presets/opencode/agents/docs.md +1 -2
- package/presets/opencode/agents/goal-planner.md +82 -0
- package/presets/opencode/agents/implementer.md +9 -2
- package/presets/opencode/agents/orchestrator.md +50 -12
- package/presets/opencode/agents/repo-explorer.md +0 -1
- package/presets/opencode/agents/reviewer.md +23 -2
- package/presets/opencode/agents/security-reviewer.md +129 -0
- package/presets/opencode/agents/task-coach.md +1 -2
- package/presets/opencode/agents/tester.md +1 -2
- package/presets/opencode/commands/cc-openspec.md +61 -0
- package/presets/opencode/commands/cc-scorecard.md +16 -0
- package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
- package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
- package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
- package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
- package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
- package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
- package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
- package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
- package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
- package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
- package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
- package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
- package/presets/opencode/skills/evaluation/SKILL.md +6 -0
- package/presets/opencode/skills/openspec/SKILL.md +50 -0
- package/presets/templates/BACKLOG.md +33 -0
- package/presets/templates/execution-profile.yml +6 -0
- package/presets/templates/model-comparison.md +11 -0
- package/presets/templates/regression-checklist.yml +10 -0
- package/src/presets/manifests/agy.yml +2 -2
- package/src/presets/manifests/claude.yml +2 -2
- package/src/presets/manifests/codex.yml +2 -2
- package/src/presets/manifests/cursor.yml +19 -3
- package/src/presets/manifests/gemini.yml +2 -2
- package/src/presets/manifests/opencode.yml +2 -2
- package/src/presets/models/agy.yml +21 -0
- package/src/presets/models/claude.yml +18 -0
- package/src/presets/models/codex.yml +18 -0
- package/src/presets/models/cursor.yml +39 -9
- package/src/presets/models/gemini.yml +18 -0
- package/src/presets/models/opencode.yml +18 -0
|
@@ -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; }` |
|