ai-factory 2.0.0 → 2.2.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 +17 -4
- package/dist/cli/commands/extension.d.ts +4 -0
- package/dist/cli/commands/extension.d.ts.map +1 -0
- package/dist/cli/commands/extension.js +288 -0
- package/dist/cli/commands/extension.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +31 -35
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +86 -7
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/upgrade.d.ts.map +1 -1
- package/dist/cli/commands/upgrade.js +44 -41
- package/dist/cli/commands/upgrade.js.map +1 -1
- package/dist/cli/index.js +48 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/wizard/prompts.d.ts +2 -1
- package/dist/cli/wizard/prompts.d.ts.map +1 -1
- package/dist/cli/wizard/prompts.js +13 -20
- package/dist/cli/wizard/prompts.js.map +1 -1
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +9 -0
- package/dist/core/agents.js.map +1 -1
- package/dist/core/config.d.ts +8 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +5 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/extension-ops.d.ts +32 -0
- package/dist/core/extension-ops.d.ts.map +1 -0
- package/dist/core/extension-ops.js +83 -0
- package/dist/core/extension-ops.js.map +1 -0
- package/dist/core/extensions.d.ts +53 -0
- package/dist/core/extensions.d.ts.map +1 -0
- package/dist/core/extensions.js +141 -0
- package/dist/core/extensions.js.map +1 -0
- package/dist/core/injections.d.ts +10 -0
- package/dist/core/injections.d.ts.map +1 -0
- package/dist/core/injections.js +154 -0
- package/dist/core/injections.js.map +1 -0
- package/dist/core/installer.d.ts +7 -3
- package/dist/core/installer.d.ts.map +1 -1
- package/dist/core/installer.js +68 -26
- package/dist/core/installer.js.map +1 -1
- package/dist/core/mcp.d.ts +12 -0
- package/dist/core/mcp.d.ts.map +1 -1
- package/dist/core/mcp.js +112 -67
- package/dist/core/mcp.js.map +1 -1
- package/dist/core/transformer.d.ts +10 -1
- package/dist/core/transformer.d.ts.map +1 -1
- package/dist/core/transformer.js +19 -1
- package/dist/core/transformer.js.map +1 -1
- package/dist/core/transformers/antigravity.d.ts +1 -0
- package/dist/core/transformers/antigravity.d.ts.map +1 -1
- package/dist/core/transformers/antigravity.js +19 -4
- package/dist/core/transformers/antigravity.js.map +1 -1
- package/dist/core/transformers/codex.d.ts +7 -0
- package/dist/core/transformers/codex.d.ts.map +1 -0
- package/dist/core/transformers/codex.js +24 -0
- package/dist/core/transformers/codex.js.map +1 -0
- package/dist/core/transformers/default.d.ts +1 -0
- package/dist/core/transformers/default.d.ts.map +1 -1
- package/dist/core/transformers/default.js +6 -0
- package/dist/core/transformers/default.js.map +1 -1
- package/dist/core/transformers/kilocode.js +1 -1
- package/dist/core/transformers/kilocode.js.map +1 -1
- package/dist/core/transformers/qwen.d.ts +7 -0
- package/dist/core/transformers/qwen.d.ts.map +1 -0
- package/dist/core/transformers/qwen.js +25 -0
- package/dist/core/transformers/qwen.js.map +1 -0
- package/dist/utils/fs.d.ts +0 -2
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +1 -5
- package/dist/utils/fs.js.map +1 -1
- package/mcp/templates/playwright.json +4 -0
- package/package.json +16 -1
- package/skills/aif/SKILL.md +27 -52
- package/skills/aif-commit/SKILL.md +13 -1
- package/skills/aif-grounded/SKILL.md +90 -0
- package/skills/aif-implement/SKILL.md +39 -2
- package/skills/aif-implement/references/IMPLEMENTATION-GUIDE.md +16 -0
- package/skills/aif-loop/SKILL.md +2 -2
- package/dist/cli/wizard/detector.d.ts +0 -10
- package/dist/cli/wizard/detector.d.ts.map +0 -1
- package/dist/cli/wizard/detector.js +0 -231
- package/dist/cli/wizard/detector.js.map +0 -1
- package/skills/_templates/nextjs/nextjs-patterns/SKILL.md +0 -146
- package/skills/_templates/node-api/api-patterns/SKILL.md +0 -245
- package/skills/_templates/php/php-patterns/SKILL.md +0 -491
- package/skills/_templates/python/python-patterns/SKILL.md +0 -236
- package/skills/_templates/react/react-patterns/SKILL.md +0 -181
- package/skills/aif-deploy/SKILL.md +0 -138
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: php-patterns
|
|
3
|
-
description: PHP development patterns and best practices. Covers Laravel, Symfony, modern PHP 8+, PSR standards, Composer, and testing with PHPUnit/Pest.
|
|
4
|
-
argument-hint: "[topic: laravel|symfony|php8|psr|testing|security]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# PHP Patterns Guide
|
|
8
|
-
|
|
9
|
-
Modern PHP patterns and best practices for building robust applications.
|
|
10
|
-
|
|
11
|
-
## Topics
|
|
12
|
-
|
|
13
|
-
### Laravel (`/php-patterns laravel`)
|
|
14
|
-
|
|
15
|
-
**Project Structure:**
|
|
16
|
-
```
|
|
17
|
-
app/
|
|
18
|
-
├── Console/Commands/ # Artisan commands
|
|
19
|
-
├── Exceptions/ # Exception handlers
|
|
20
|
-
├── Http/
|
|
21
|
-
│ ├── Controllers/ # Request handlers
|
|
22
|
-
│ ├── Middleware/ # Request/response filters
|
|
23
|
-
│ ├── Requests/ # Form requests (validation)
|
|
24
|
-
│ └── Resources/ # API resources
|
|
25
|
-
├── Models/ # Eloquent models
|
|
26
|
-
├── Policies/ # Authorization policies
|
|
27
|
-
├── Providers/ # Service providers
|
|
28
|
-
└── Services/ # Business logic
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Eloquent Best Practices:**
|
|
32
|
-
```php
|
|
33
|
-
// ✅ Good: Eager loading to prevent N+1
|
|
34
|
-
$users = User::with(['posts', 'profile'])->get();
|
|
35
|
-
|
|
36
|
-
// ❌ Bad: N+1 query problem
|
|
37
|
-
$users = User::all();
|
|
38
|
-
foreach ($users as $user) {
|
|
39
|
-
echo $user->posts->count(); // Query per user!
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ✅ Good: Query scopes for reusable queries
|
|
43
|
-
class User extends Model
|
|
44
|
-
{
|
|
45
|
-
public function scopeActive(Builder $query): Builder
|
|
46
|
-
{
|
|
47
|
-
return $query->where('status', 'active');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public function scopeVerified(Builder $query): Builder
|
|
51
|
-
{
|
|
52
|
-
return $query->whereNotNull('email_verified_at');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Usage
|
|
57
|
-
$users = User::active()->verified()->get();
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**Form Requests:**
|
|
61
|
-
```php
|
|
62
|
-
class StoreUserRequest extends FormRequest
|
|
63
|
-
{
|
|
64
|
-
public function authorize(): bool
|
|
65
|
-
{
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public function rules(): array
|
|
70
|
-
{
|
|
71
|
-
return [
|
|
72
|
-
'email' => ['required', 'email', 'unique:users'],
|
|
73
|
-
'password' => ['required', 'min:12', 'confirmed'],
|
|
74
|
-
'name' => ['required', 'string', 'max:255'],
|
|
75
|
-
];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Controller stays clean
|
|
80
|
-
public function store(StoreUserRequest $request): JsonResponse
|
|
81
|
-
{
|
|
82
|
-
$user = User::create($request->validated());
|
|
83
|
-
return response()->json($user, 201);
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**API Resources:**
|
|
88
|
-
```php
|
|
89
|
-
class UserResource extends JsonResource
|
|
90
|
-
{
|
|
91
|
-
public function toArray(Request $request): array
|
|
92
|
-
{
|
|
93
|
-
return [
|
|
94
|
-
'id' => $this->id,
|
|
95
|
-
'name' => $this->name,
|
|
96
|
-
'email' => $this->email,
|
|
97
|
-
'created_at' => $this->created_at->toISOString(),
|
|
98
|
-
'posts' => PostResource::collection($this->whenLoaded('posts')),
|
|
99
|
-
];
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Symfony (`/php-patterns symfony`)
|
|
105
|
-
|
|
106
|
-
**Service Architecture:**
|
|
107
|
-
```php
|
|
108
|
-
// src/Service/UserService.php
|
|
109
|
-
#[AsService]
|
|
110
|
-
class UserService
|
|
111
|
-
{
|
|
112
|
-
public function __construct(
|
|
113
|
-
private UserRepository $userRepository,
|
|
114
|
-
private PasswordHasherInterface $passwordHasher,
|
|
115
|
-
private EventDispatcherInterface $dispatcher,
|
|
116
|
-
) {}
|
|
117
|
-
|
|
118
|
-
public function createUser(CreateUserDTO $dto): User
|
|
119
|
-
{
|
|
120
|
-
$user = new User();
|
|
121
|
-
$user->setEmail($dto->email);
|
|
122
|
-
$user->setPassword(
|
|
123
|
-
$this->passwordHasher->hashPassword($user, $dto->password)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
$this->userRepository->save($user, flush: true);
|
|
127
|
-
$this->dispatcher->dispatch(new UserCreatedEvent($user));
|
|
128
|
-
|
|
129
|
-
return $user;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**Repository Pattern:**
|
|
135
|
-
```php
|
|
136
|
-
// src/Repository/UserRepository.php
|
|
137
|
-
class UserRepository extends ServiceEntityRepository
|
|
138
|
-
{
|
|
139
|
-
public function __construct(ManagerRegistry $registry)
|
|
140
|
-
{
|
|
141
|
-
parent::__construct($registry, User::class);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public function findActiveByEmail(string $email): ?User
|
|
145
|
-
{
|
|
146
|
-
return $this->createQueryBuilder('u')
|
|
147
|
-
->andWhere('u.email = :email')
|
|
148
|
-
->andWhere('u.status = :status')
|
|
149
|
-
->setParameter('email', $email)
|
|
150
|
-
->setParameter('status', 'active')
|
|
151
|
-
->getQuery()
|
|
152
|
-
->getOneOrNullResult();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public function save(User $entity, bool $flush = false): void
|
|
156
|
-
{
|
|
157
|
-
$this->getEntityManager()->persist($entity);
|
|
158
|
-
if ($flush) {
|
|
159
|
-
$this->getEntityManager()->flush();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**DTOs with Validation:**
|
|
166
|
-
```php
|
|
167
|
-
// src/DTO/CreateUserDTO.php
|
|
168
|
-
class CreateUserDTO
|
|
169
|
-
{
|
|
170
|
-
public function __construct(
|
|
171
|
-
#[Assert\NotBlank]
|
|
172
|
-
#[Assert\Email]
|
|
173
|
-
public readonly string $email,
|
|
174
|
-
|
|
175
|
-
#[Assert\NotBlank]
|
|
176
|
-
#[Assert\Length(min: 12)]
|
|
177
|
-
public readonly string $password,
|
|
178
|
-
|
|
179
|
-
#[Assert\NotBlank]
|
|
180
|
-
#[Assert\Length(max: 255)]
|
|
181
|
-
public readonly string $name,
|
|
182
|
-
) {}
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Modern PHP 8+ (`/php-patterns php8`)
|
|
187
|
-
|
|
188
|
-
**Constructor Property Promotion:**
|
|
189
|
-
```php
|
|
190
|
-
// ✅ PHP 8+ concise
|
|
191
|
-
class User
|
|
192
|
-
{
|
|
193
|
-
public function __construct(
|
|
194
|
-
public readonly int $id,
|
|
195
|
-
public string $name,
|
|
196
|
-
public string $email,
|
|
197
|
-
private ?string $password = null,
|
|
198
|
-
) {}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ❌ Old verbose style
|
|
202
|
-
class User
|
|
203
|
-
{
|
|
204
|
-
public int $id;
|
|
205
|
-
public string $name;
|
|
206
|
-
|
|
207
|
-
public function __construct(int $id, string $name)
|
|
208
|
-
{
|
|
209
|
-
$this->id = $id;
|
|
210
|
-
$this->name = $name;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Named Arguments:**
|
|
216
|
-
```php
|
|
217
|
-
// Clear intent
|
|
218
|
-
$user = new User(
|
|
219
|
-
id: 1,
|
|
220
|
-
name: 'John',
|
|
221
|
-
email: 'john@example.com',
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Skip optional params
|
|
225
|
-
sendEmail(
|
|
226
|
-
to: $user->email,
|
|
227
|
-
subject: 'Welcome',
|
|
228
|
-
// body uses default
|
|
229
|
-
);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
**Match Expression:**
|
|
233
|
-
```php
|
|
234
|
-
// ✅ PHP 8+ match
|
|
235
|
-
$result = match($status) {
|
|
236
|
-
'pending' => 'Awaiting review',
|
|
237
|
-
'approved' => 'Ready to publish',
|
|
238
|
-
'rejected' => 'Please revise',
|
|
239
|
-
default => 'Unknown status',
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// ❌ Verbose switch
|
|
243
|
-
switch($status) {
|
|
244
|
-
case 'pending':
|
|
245
|
-
$result = 'Awaiting review';
|
|
246
|
-
break;
|
|
247
|
-
// ...
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
**Enums:**
|
|
252
|
-
```php
|
|
253
|
-
enum OrderStatus: string
|
|
254
|
-
{
|
|
255
|
-
case Pending = 'pending';
|
|
256
|
-
case Processing = 'processing';
|
|
257
|
-
case Shipped = 'shipped';
|
|
258
|
-
case Delivered = 'delivered';
|
|
259
|
-
case Cancelled = 'cancelled';
|
|
260
|
-
|
|
261
|
-
public function label(): string
|
|
262
|
-
{
|
|
263
|
-
return match($this) {
|
|
264
|
-
self::Pending => 'Pending',
|
|
265
|
-
self::Processing => 'Processing',
|
|
266
|
-
self::Shipped => 'Shipped',
|
|
267
|
-
self::Delivered => 'Delivered',
|
|
268
|
-
self::Cancelled => 'Cancelled',
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
public function canCancel(): bool
|
|
273
|
-
{
|
|
274
|
-
return in_array($this, [self::Pending, self::Processing]);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Usage
|
|
279
|
-
$order->status = OrderStatus::Pending;
|
|
280
|
-
if ($order->status->canCancel()) {
|
|
281
|
-
// ...
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
**Attributes:**
|
|
286
|
-
```php
|
|
287
|
-
#[Route('/api/users', methods: ['GET'])]
|
|
288
|
-
#[IsGranted('ROLE_ADMIN')]
|
|
289
|
-
public function list(): JsonResponse
|
|
290
|
-
{
|
|
291
|
-
// ...
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Custom attribute
|
|
295
|
-
#[Attribute(Attribute::TARGET_PROPERTY)]
|
|
296
|
-
class Encrypted
|
|
297
|
-
{
|
|
298
|
-
public function __construct(
|
|
299
|
-
public string $algorithm = 'aes-256-cbc'
|
|
300
|
-
) {}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
class User
|
|
304
|
-
{
|
|
305
|
-
#[Encrypted]
|
|
306
|
-
private string $ssn;
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### PSR Standards (`/php-patterns psr`)
|
|
311
|
-
|
|
312
|
-
**PSR-4 Autoloading:**
|
|
313
|
-
```json
|
|
314
|
-
// composer.json
|
|
315
|
-
{
|
|
316
|
-
"autoload": {
|
|
317
|
-
"psr-4": {
|
|
318
|
-
"App\\": "src/",
|
|
319
|
-
"Tests\\": "tests/"
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**PSR-12 Code Style:**
|
|
326
|
-
```php
|
|
327
|
-
<?php
|
|
328
|
-
|
|
329
|
-
declare(strict_types=1);
|
|
330
|
-
|
|
331
|
-
namespace App\Service;
|
|
332
|
-
|
|
333
|
-
use App\Repository\UserRepository;
|
|
334
|
-
use Psr\Log\LoggerInterface;
|
|
335
|
-
|
|
336
|
-
class UserService
|
|
337
|
-
{
|
|
338
|
-
public function __construct(
|
|
339
|
-
private UserRepository $repository,
|
|
340
|
-
private LoggerInterface $logger,
|
|
341
|
-
) {
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
public function findUser(int $id): ?User
|
|
345
|
-
{
|
|
346
|
-
if ($id <= 0) {
|
|
347
|
-
throw new InvalidArgumentException('ID must be positive');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return $this->repository->find($id);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Testing (`/php-patterns testing`)
|
|
356
|
-
|
|
357
|
-
**PHPUnit:**
|
|
358
|
-
```php
|
|
359
|
-
class UserServiceTest extends TestCase
|
|
360
|
-
{
|
|
361
|
-
private UserService $service;
|
|
362
|
-
private MockObject $repository;
|
|
363
|
-
|
|
364
|
-
protected function setUp(): void
|
|
365
|
-
{
|
|
366
|
-
$this->repository = $this->createMock(UserRepository::class);
|
|
367
|
-
$this->service = new UserService($this->repository);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
public function testCreateUserHashesPassword(): void
|
|
371
|
-
{
|
|
372
|
-
// Arrange
|
|
373
|
-
$dto = new CreateUserDTO(
|
|
374
|
-
email: 'test@example.com',
|
|
375
|
-
password: 'plainpassword',
|
|
376
|
-
name: 'Test User',
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
$this->repository
|
|
380
|
-
->expects($this->once())
|
|
381
|
-
->method('save')
|
|
382
|
-
->with($this->callback(fn(User $user) =>
|
|
383
|
-
password_verify('plainpassword', $user->getPassword())
|
|
384
|
-
));
|
|
385
|
-
|
|
386
|
-
// Act
|
|
387
|
-
$user = $this->service->createUser($dto);
|
|
388
|
-
|
|
389
|
-
// Assert
|
|
390
|
-
$this->assertEquals('test@example.com', $user->getEmail());
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* @dataProvider invalidEmailProvider
|
|
395
|
-
*/
|
|
396
|
-
public function testRejectsInvalidEmail(string $email): void
|
|
397
|
-
{
|
|
398
|
-
$this->expectException(ValidationException::class);
|
|
399
|
-
|
|
400
|
-
new CreateUserDTO($email, 'password123', 'Name');
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
public static function invalidEmailProvider(): array
|
|
404
|
-
{
|
|
405
|
-
return [
|
|
406
|
-
'empty' => [''],
|
|
407
|
-
'no at sign' => ['invalid'],
|
|
408
|
-
'no domain' => ['test@'],
|
|
409
|
-
];
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
**Pest (Modern alternative):**
|
|
415
|
-
```php
|
|
416
|
-
test('user can be created', function () {
|
|
417
|
-
$user = User::factory()->create();
|
|
418
|
-
|
|
419
|
-
expect($user)
|
|
420
|
-
->toBeInstanceOf(User::class)
|
|
421
|
-
->id->toBeInt()
|
|
422
|
-
->email->toContain('@');
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
test('password is hashed on create')
|
|
426
|
-
->expect(fn() => User::factory()->create(['password' => 'secret']))
|
|
427
|
-
->password->not->toBe('secret');
|
|
428
|
-
|
|
429
|
-
it('validates email format', function (string $email) {
|
|
430
|
-
expect(fn() => new CreateUserDTO($email, 'pass', 'name'))
|
|
431
|
-
->toThrow(ValidationException::class);
|
|
432
|
-
})->with(['', 'invalid', 'test@']);
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### Security (`/php-patterns security`)
|
|
436
|
-
|
|
437
|
-
**Password Hashing:**
|
|
438
|
-
```php
|
|
439
|
-
// ✅ Good: Use password_hash
|
|
440
|
-
$hash = password_hash($password, PASSWORD_ARGON2ID, [
|
|
441
|
-
'memory_cost' => 65536,
|
|
442
|
-
'time_cost' => 4,
|
|
443
|
-
'threads' => 3,
|
|
444
|
-
]);
|
|
445
|
-
|
|
446
|
-
// Verify
|
|
447
|
-
if (password_verify($inputPassword, $storedHash)) {
|
|
448
|
-
// Valid
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ❌ Never use: md5, sha1, sha256 for passwords
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
**SQL Injection Prevention:**
|
|
455
|
-
```php
|
|
456
|
-
// ✅ PDO prepared statements
|
|
457
|
-
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
|
|
458
|
-
$stmt->execute(['email' => $email]);
|
|
459
|
-
|
|
460
|
-
// ✅ Eloquent (auto-escaped)
|
|
461
|
-
User::where('email', $email)->first();
|
|
462
|
-
|
|
463
|
-
// ✅ Query Builder
|
|
464
|
-
DB::table('users')->where('email', '=', $email)->first();
|
|
465
|
-
|
|
466
|
-
// ❌ NEVER: String interpolation
|
|
467
|
-
$pdo->query("SELECT * FROM users WHERE email = '$email'");
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
**XSS Prevention:**
|
|
471
|
-
```php
|
|
472
|
-
// ✅ Blade auto-escapes
|
|
473
|
-
{{ $userInput }}
|
|
474
|
-
|
|
475
|
-
// ❌ Raw output - only when certain it's safe
|
|
476
|
-
{!! $trustedHtml !!}
|
|
477
|
-
|
|
478
|
-
// ✅ Manual escaping
|
|
479
|
-
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
## Best Practices Summary
|
|
483
|
-
|
|
484
|
-
1. **Use strict types** - `declare(strict_types=1);`
|
|
485
|
-
2. **Type everything** - Parameters, returns, properties
|
|
486
|
-
3. **Prefer readonly** - Immutable by default
|
|
487
|
-
4. **Use enums** - Instead of string constants
|
|
488
|
-
5. **Dependency injection** - Constructor injection preferred
|
|
489
|
-
6. **Follow PSR-12** - Code style consistency
|
|
490
|
-
7. **Write tests** - PHPUnit or Pest
|
|
491
|
-
8. **Use static analysis** - PHPStan level 8+
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: python-patterns
|
|
3
|
-
description: Python development patterns and best practices. Covers typing, async, testing, project structure, and common frameworks.
|
|
4
|
-
argument-hint: "[topic: typing|async|testing|structure|fastapi|django]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Python Patterns Guide
|
|
8
|
-
|
|
9
|
-
Modern Python patterns and best practices for building robust applications.
|
|
10
|
-
|
|
11
|
-
## Topics
|
|
12
|
-
|
|
13
|
-
### Type Hints (`/python-patterns typing`)
|
|
14
|
-
|
|
15
|
-
**Basic Types:**
|
|
16
|
-
```python
|
|
17
|
-
from typing import Optional, List, Dict, Union, Callable, TypeVar, Generic
|
|
18
|
-
|
|
19
|
-
def greet(name: str) -> str:
|
|
20
|
-
return f"Hello, {name}"
|
|
21
|
-
|
|
22
|
-
def process_items(items: List[str]) -> Dict[str, int]:
|
|
23
|
-
return {item: len(item) for item in items}
|
|
24
|
-
|
|
25
|
-
def find_user(user_id: int) -> Optional[User]:
|
|
26
|
-
return db.get(user_id)
|
|
27
|
-
|
|
28
|
-
# Union types (Python 3.10+)
|
|
29
|
-
def process(value: int | str) -> None:
|
|
30
|
-
pass
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Generics:**
|
|
34
|
-
```python
|
|
35
|
-
T = TypeVar('T')
|
|
36
|
-
|
|
37
|
-
class Repository(Generic[T]):
|
|
38
|
-
def get(self, id: int) -> Optional[T]:
|
|
39
|
-
...
|
|
40
|
-
|
|
41
|
-
def save(self, entity: T) -> T:
|
|
42
|
-
...
|
|
43
|
-
|
|
44
|
-
# Usage
|
|
45
|
-
user_repo: Repository[User] = Repository()
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Dataclasses & Pydantic:**
|
|
49
|
-
```python
|
|
50
|
-
from dataclasses import dataclass
|
|
51
|
-
from pydantic import BaseModel, Field
|
|
52
|
-
|
|
53
|
-
# Dataclass for simple data containers
|
|
54
|
-
@dataclass
|
|
55
|
-
class Point:
|
|
56
|
-
x: float
|
|
57
|
-
y: float
|
|
58
|
-
|
|
59
|
-
# Pydantic for validation
|
|
60
|
-
class UserCreate(BaseModel):
|
|
61
|
-
email: str = Field(..., pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
|
|
62
|
-
password: str = Field(..., min_length=8)
|
|
63
|
-
name: str = Field(..., max_length=100)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Async (`/python-patterns async`)
|
|
67
|
-
|
|
68
|
-
**Async Basics:**
|
|
69
|
-
```python
|
|
70
|
-
import asyncio
|
|
71
|
-
from typing import List
|
|
72
|
-
|
|
73
|
-
async def fetch_data(url: str) -> dict:
|
|
74
|
-
async with aiohttp.ClientSession() as session:
|
|
75
|
-
async with session.get(url) as response:
|
|
76
|
-
return await response.json()
|
|
77
|
-
|
|
78
|
-
# Run multiple async operations concurrently
|
|
79
|
-
async def fetch_all(urls: List[str]) -> List[dict]:
|
|
80
|
-
tasks = [fetch_data(url) for url in urls]
|
|
81
|
-
return await asyncio.gather(*tasks)
|
|
82
|
-
|
|
83
|
-
# Context manager for async resources
|
|
84
|
-
class AsyncDatabase:
|
|
85
|
-
async def __aenter__(self):
|
|
86
|
-
await self.connect()
|
|
87
|
-
return self
|
|
88
|
-
|
|
89
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
90
|
-
await self.disconnect()
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Async Patterns:**
|
|
94
|
-
```python
|
|
95
|
-
# Semaphore for limiting concurrency
|
|
96
|
-
async def fetch_with_limit(urls: List[str], limit: int = 10):
|
|
97
|
-
semaphore = asyncio.Semaphore(limit)
|
|
98
|
-
|
|
99
|
-
async def fetch_one(url: str):
|
|
100
|
-
async with semaphore:
|
|
101
|
-
return await fetch_data(url)
|
|
102
|
-
|
|
103
|
-
return await asyncio.gather(*[fetch_one(url) for url in urls])
|
|
104
|
-
|
|
105
|
-
# Timeout handling
|
|
106
|
-
async def fetch_with_timeout(url: str, timeout: float = 5.0):
|
|
107
|
-
try:
|
|
108
|
-
return await asyncio.wait_for(fetch_data(url), timeout=timeout)
|
|
109
|
-
except asyncio.TimeoutError:
|
|
110
|
-
raise TimeoutError(f"Request to {url} timed out")
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Testing (`/python-patterns testing`)
|
|
114
|
-
|
|
115
|
-
**Pytest Patterns:**
|
|
116
|
-
```python
|
|
117
|
-
import pytest
|
|
118
|
-
from unittest.mock import Mock, patch, AsyncMock
|
|
119
|
-
|
|
120
|
-
# Fixtures
|
|
121
|
-
@pytest.fixture
|
|
122
|
-
def user():
|
|
123
|
-
return User(id=1, name="Test User", email="test@example.com")
|
|
124
|
-
|
|
125
|
-
@pytest.fixture
|
|
126
|
-
def db_session():
|
|
127
|
-
session = create_test_session()
|
|
128
|
-
yield session
|
|
129
|
-
session.rollback()
|
|
130
|
-
session.close()
|
|
131
|
-
|
|
132
|
-
# Parametrized tests
|
|
133
|
-
@pytest.mark.parametrize("input,expected", [
|
|
134
|
-
("hello", "HELLO"),
|
|
135
|
-
("world", "WORLD"),
|
|
136
|
-
("", ""),
|
|
137
|
-
])
|
|
138
|
-
def test_uppercase(input: str, expected: str):
|
|
139
|
-
assert input.upper() == expected
|
|
140
|
-
|
|
141
|
-
# Mocking
|
|
142
|
-
def test_fetch_user(mocker):
|
|
143
|
-
mock_db = mocker.patch('app.db.get_user')
|
|
144
|
-
mock_db.return_value = User(id=1, name="Test")
|
|
145
|
-
|
|
146
|
-
result = fetch_user(1)
|
|
147
|
-
|
|
148
|
-
mock_db.assert_called_once_with(1)
|
|
149
|
-
assert result.name == "Test"
|
|
150
|
-
|
|
151
|
-
# Async tests
|
|
152
|
-
@pytest.mark.asyncio
|
|
153
|
-
async def test_async_fetch():
|
|
154
|
-
with patch('app.fetch_data', new_callable=AsyncMock) as mock:
|
|
155
|
-
mock.return_value = {"data": "test"}
|
|
156
|
-
result = await fetch_data("http://example.com")
|
|
157
|
-
assert result == {"data": "test"}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Project Structure (`/python-patterns structure`)
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
project/
|
|
164
|
-
├── pyproject.toml # Project config (Poetry/PDM)
|
|
165
|
-
├── src/
|
|
166
|
-
│ └── myapp/
|
|
167
|
-
│ ├── __init__.py
|
|
168
|
-
│ ├── main.py # Entry point
|
|
169
|
-
│ ├── config.py # Settings
|
|
170
|
-
│ ├── models/ # Data models
|
|
171
|
-
│ │ ├── __init__.py
|
|
172
|
-
│ │ └── user.py
|
|
173
|
-
│ ├── services/ # Business logic
|
|
174
|
-
│ │ ├── __init__.py
|
|
175
|
-
│ │ └── user_service.py
|
|
176
|
-
│ ├── repositories/ # Data access
|
|
177
|
-
│ │ ├── __init__.py
|
|
178
|
-
│ │ └── user_repo.py
|
|
179
|
-
│ ├── api/ # API routes
|
|
180
|
-
│ │ ├── __init__.py
|
|
181
|
-
│ │ └── routes/
|
|
182
|
-
│ └── utils/ # Helpers
|
|
183
|
-
├── tests/
|
|
184
|
-
│ ├── conftest.py
|
|
185
|
-
│ ├── unit/
|
|
186
|
-
│ └── integration/
|
|
187
|
-
└── scripts/ # CLI scripts
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### FastAPI (`/python-patterns fastapi`)
|
|
191
|
-
|
|
192
|
-
```python
|
|
193
|
-
from fastapi import FastAPI, Depends, HTTPException, status
|
|
194
|
-
from fastapi.security import OAuth2PasswordBearer
|
|
195
|
-
|
|
196
|
-
app = FastAPI()
|
|
197
|
-
|
|
198
|
-
# Dependency injection
|
|
199
|
-
async def get_db():
|
|
200
|
-
db = SessionLocal()
|
|
201
|
-
try:
|
|
202
|
-
yield db
|
|
203
|
-
finally:
|
|
204
|
-
db.close()
|
|
205
|
-
|
|
206
|
-
# Route with dependencies
|
|
207
|
-
@app.get("/users/{user_id}", response_model=UserResponse)
|
|
208
|
-
async def get_user(
|
|
209
|
-
user_id: int,
|
|
210
|
-
db: Session = Depends(get_db),
|
|
211
|
-
current_user: User = Depends(get_current_user),
|
|
212
|
-
):
|
|
213
|
-
user = db.query(User).filter(User.id == user_id).first()
|
|
214
|
-
if not user:
|
|
215
|
-
raise HTTPException(status_code=404, detail="User not found")
|
|
216
|
-
return user
|
|
217
|
-
|
|
218
|
-
# Background tasks
|
|
219
|
-
@app.post("/email")
|
|
220
|
-
async def send_email(
|
|
221
|
-
email: EmailSchema,
|
|
222
|
-
background_tasks: BackgroundTasks,
|
|
223
|
-
):
|
|
224
|
-
background_tasks.add_task(send_email_task, email)
|
|
225
|
-
return {"message": "Email queued"}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Best Practices
|
|
229
|
-
|
|
230
|
-
1. **Use type hints** - Better IDE support and documentation
|
|
231
|
-
2. **Prefer composition** - Over inheritance
|
|
232
|
-
3. **Use context managers** - For resource management
|
|
233
|
-
4. **Write tests** - Pytest with fixtures
|
|
234
|
-
5. **Use virtual environments** - Poetry or PDM
|
|
235
|
-
6. **Format code** - Black + isort + ruff
|
|
236
|
-
7. **Document with docstrings** - Google or NumPy style
|