universal-dev-standards 6.14.0-beta.2 → 6.14.0-beta.4
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/bundled/ai/standards/ai-response-navigation.ai.yaml +43 -3
- package/bundled/ai/standards/checkin-standards.ai.yaml +25 -6
- package/bundled/ai/standards/open-work-tracking.ai.yaml +4 -1
- package/bundled/ai/standards/pipeline-security-gates.ai.yaml +5 -1
- package/bundled/core/ai-response-navigation.md +128 -12
- package/bundled/core/open-work-tracking.md +1 -1
- package/bundled/extensions/frameworks/fat-free-patterns.md +937 -0
- package/bundled/extensions/languages/csharp-style.md +464 -0
- package/bundled/extensions/languages/php/fat-free-patterns.md +915 -0
- package/bundled/extensions/languages/php/php-style.md +693 -0
- package/bundled/extensions/languages/php-style.md +700 -0
- package/bundled/extensions/locales/zh-cn.md +717 -0
- package/bundled/extensions/locales/zh-tw.md +717 -0
- package/bundled/locales/COVERAGE.md +5 -4
- package/bundled/locales/zh-CN/CHANGELOG.md +44 -3
- package/bundled/locales/zh-CN/README.md +2 -2
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/ai-response-navigation.md +110 -12
- package/bundled/locales/zh-CN/skills/README.md +1 -0
- package/bundled/locales/zh-CN/skills/comprehension-ladder/SKILL.md +289 -0
- package/bundled/locales/zh-CN/skills/comprehension-ladder/eval-cases.md +261 -0
- package/bundled/locales/zh-TW/CHANGELOG.md +44 -3
- package/bundled/locales/zh-TW/README.md +2 -2
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/ai-response-navigation.md +110 -12
- package/bundled/locales/zh-TW/core/open-work-tracking.md +3 -3
- package/bundled/locales/zh-TW/skills/README.md +1 -0
- package/bundled/locales/zh-TW/skills/comprehension-ladder/SKILL.md +289 -0
- package/bundled/locales/zh-TW/skills/comprehension-ladder/eval-cases.md +261 -0
- package/bundled/skills/README.md +1 -0
- package/bundled/skills/comprehension-ladder/SKILL.md +283 -0
- package/bundled/skills/comprehension-ladder/eval-cases.md +255 -0
- package/package.json +2 -2
- package/src/commands/check.js +9 -0
- package/src/commands/init.js +100 -27
- package/src/commands/uninstall.js +144 -30
- package/src/commands/update.js +62 -3
- package/src/core/install-records.js +191 -0
- package/src/i18n/messages.js +39 -6
- package/src/installers/hooks-installer.js +61 -30
- package/src/installers/integration-installer.js +5 -1
- package/src/installers/standards-installer.js +16 -23
- package/src/reconciler/plan-executor.js +10 -11
- package/src/uninstallers/hook-uninstaller.js +219 -33
- package/src/uninstallers/integration-uninstaller.js +35 -5
- package/src/utils/copier.js +57 -0
- package/src/utils/git-hooks.js +139 -7
- package/src/utils/hasher.js +36 -0
- package/src/utils/integration-generator.js +16 -6
- package/src/utils/legacy-hook-migration.js +112 -0
- package/src/utils/locale.js +19 -0
- package/src/utils/open-work-tracking.mjs +124 -23
- package/standards-registry.json +21 -7
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
# PHP Coding Style Guide
|
|
2
|
+
# PHP 程式碼風格指南
|
|
3
|
+
|
|
4
|
+
**Version**: 1.0.0
|
|
5
|
+
**Last Updated**: 2025-12-23
|
|
6
|
+
**Applicability**: All PHP 8.1+ projects
|
|
7
|
+
**適用範圍**: 所有 PHP 8.1+ 專案
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Purpose | 目的
|
|
12
|
+
|
|
13
|
+
This guide defines PHP coding conventions based on PSR-12 to ensure consistent, readable, secure, and maintainable code across the team.
|
|
14
|
+
|
|
15
|
+
本指南基於 PSR-12 定義 PHP 編碼慣例,確保團隊程式碼的一致性、可讀性、安全性與可維護性。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## PSR-12 Coding Style | PSR-12 編碼風格
|
|
20
|
+
|
|
21
|
+
### File Structure | 檔案結構
|
|
22
|
+
|
|
23
|
+
```php
|
|
24
|
+
<?php
|
|
25
|
+
// ✅ 正確:PHP 8.1+ 檔案結構
|
|
26
|
+
|
|
27
|
+
declare(strict_types=1);
|
|
28
|
+
|
|
29
|
+
namespace App\Services;
|
|
30
|
+
|
|
31
|
+
use App\Contracts\UserRepositoryInterface;
|
|
32
|
+
use App\Models\User;
|
|
33
|
+
use Psr\Log\LoggerInterface;
|
|
34
|
+
|
|
35
|
+
class UserService
|
|
36
|
+
{
|
|
37
|
+
// ...
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Key Rules | 關鍵規則
|
|
42
|
+
|
|
43
|
+
| Rule | Standard | 規則說明 |
|
|
44
|
+
|------|----------|---------|
|
|
45
|
+
| Indentation | 4 spaces (NO tabs) | 4 個空格(禁止 Tab) |
|
|
46
|
+
| Line Length | Max 120 characters | 最大 120 字元 |
|
|
47
|
+
| Line Ending | LF (Unix style) | Unix 換行符 |
|
|
48
|
+
| PHP Tags | `<?php` only (no short tags) | 僅使用完整標籤 |
|
|
49
|
+
| Encoding | UTF-8 without BOM | UTF-8 無 BOM |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Naming Conventions | 命名慣例
|
|
54
|
+
|
|
55
|
+
### Summary Table | 總覽表
|
|
56
|
+
|
|
57
|
+
| Element | Style | Example |
|
|
58
|
+
|---------|-------|---------|
|
|
59
|
+
| Namespace | PascalCase | `App\Services` |
|
|
60
|
+
| Class | PascalCase | `UserService` |
|
|
61
|
+
| Interface | PascalCase + Interface | `UserRepositoryInterface` |
|
|
62
|
+
| Trait | PascalCase + Trait | `HasTimestamps` |
|
|
63
|
+
| Enum | PascalCase | `UserStatus` |
|
|
64
|
+
| Method | camelCase | `getUserById` |
|
|
65
|
+
| Property | camelCase | `$userName` |
|
|
66
|
+
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
67
|
+
| Variable | camelCase | `$currentUser` |
|
|
68
|
+
| Function | snake_case (legacy) / camelCase | `array_map` / `processData` |
|
|
69
|
+
|
|
70
|
+
### Detailed Rules | 詳細規則
|
|
71
|
+
|
|
72
|
+
#### Classes & Interfaces | 類別與介面
|
|
73
|
+
|
|
74
|
+
```php
|
|
75
|
+
// ✅ 正確
|
|
76
|
+
class UserService {}
|
|
77
|
+
interface UserRepositoryInterface {}
|
|
78
|
+
abstract class BaseController {}
|
|
79
|
+
trait Loggable {}
|
|
80
|
+
enum UserStatus: string {}
|
|
81
|
+
|
|
82
|
+
// ❌ 錯誤
|
|
83
|
+
class userService {} // 應使用 PascalCase
|
|
84
|
+
interface IUserRepository {} // 不使用 I 前綴(這是 C# 慣例)
|
|
85
|
+
class user_service {} // 不使用 snake_case
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Properties & Variables | 屬性與變數
|
|
89
|
+
|
|
90
|
+
```php
|
|
91
|
+
// ✅ 正確
|
|
92
|
+
private readonly UserRepository $userRepository;
|
|
93
|
+
private int $retryCount = 0;
|
|
94
|
+
private bool $isInitialized = false;
|
|
95
|
+
|
|
96
|
+
// ❌ 錯誤
|
|
97
|
+
private readonly UserRepository $_userRepository; // 不使用底線前綴
|
|
98
|
+
private int $retry_count = 0; // 不使用 snake_case
|
|
99
|
+
private bool $m_isInitialized = false; // 不使用匈牙利命名法
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Constants | 常數
|
|
103
|
+
|
|
104
|
+
```php
|
|
105
|
+
// ✅ 正確
|
|
106
|
+
public const int MAX_RETRY_COUNT = 3;
|
|
107
|
+
public const string DEFAULT_TIMEZONE = 'Asia/Taipei';
|
|
108
|
+
private const float CACHE_TTL_HOURS = 24.0;
|
|
109
|
+
|
|
110
|
+
// ❌ 錯誤
|
|
111
|
+
public const int maxRetryCount = 3; // 應使用 UPPER_SNAKE_CASE
|
|
112
|
+
public const int MaxRetryCount = 3; // 應使用 UPPER_SNAKE_CASE
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## PHP 8.1+ Features | PHP 8.1+ 特性
|
|
118
|
+
|
|
119
|
+
### Enums | 列舉
|
|
120
|
+
|
|
121
|
+
```php
|
|
122
|
+
// ✅ 正確:使用 Backed Enum
|
|
123
|
+
enum UserStatus: string
|
|
124
|
+
{
|
|
125
|
+
case Active = 'active';
|
|
126
|
+
case Inactive = 'inactive';
|
|
127
|
+
case Suspended = 'suspended';
|
|
128
|
+
|
|
129
|
+
public function label(): string
|
|
130
|
+
{
|
|
131
|
+
return match($this) {
|
|
132
|
+
self::Active => '啟用',
|
|
133
|
+
self::Inactive => '停用',
|
|
134
|
+
self::Suspended => '暫停',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 使用方式
|
|
140
|
+
$status = UserStatus::Active;
|
|
141
|
+
$statusValue = $status->value; // 'active'
|
|
142
|
+
$statusLabel = $status->label(); // '啟用'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Readonly Properties | 唯讀屬性
|
|
146
|
+
|
|
147
|
+
```php
|
|
148
|
+
// ✅ 正確:使用 readonly
|
|
149
|
+
class User
|
|
150
|
+
{
|
|
151
|
+
public function __construct(
|
|
152
|
+
public readonly int $id,
|
|
153
|
+
public readonly string $email,
|
|
154
|
+
private readonly DateTimeImmutable $createdAt,
|
|
155
|
+
) {}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ❌ 錯誤:可變的不可變資料
|
|
159
|
+
class User
|
|
160
|
+
{
|
|
161
|
+
public int $id; // 應該是 readonly
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Constructor Property Promotion | 建構子屬性提升
|
|
166
|
+
|
|
167
|
+
```php
|
|
168
|
+
// ✅ 正確:使用屬性提升
|
|
169
|
+
class UserService
|
|
170
|
+
{
|
|
171
|
+
public function __construct(
|
|
172
|
+
private readonly UserRepositoryInterface $userRepository,
|
|
173
|
+
private readonly LoggerInterface $logger,
|
|
174
|
+
) {}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ❌ 冗長寫法(避免)
|
|
178
|
+
class UserService
|
|
179
|
+
{
|
|
180
|
+
private UserRepositoryInterface $userRepository;
|
|
181
|
+
private LoggerInterface $logger;
|
|
182
|
+
|
|
183
|
+
public function __construct(
|
|
184
|
+
UserRepositoryInterface $userRepository,
|
|
185
|
+
LoggerInterface $logger
|
|
186
|
+
) {
|
|
187
|
+
$this->userRepository = $userRepository;
|
|
188
|
+
$this->logger = $logger;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Named Arguments | 具名參數
|
|
194
|
+
|
|
195
|
+
```php
|
|
196
|
+
// ✅ 正確:使用具名參數提升可讀性
|
|
197
|
+
$user = new User(
|
|
198
|
+
id: 1,
|
|
199
|
+
email: 'user@example.com',
|
|
200
|
+
name: 'John Doe',
|
|
201
|
+
isActive: true,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// ✅ 正確:跳過可選參數
|
|
205
|
+
$this->sendEmail(
|
|
206
|
+
to: $user->email,
|
|
207
|
+
subject: 'Welcome',
|
|
208
|
+
priority: EmailPriority::High, // 跳過 cc, bcc 等可選參數
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Code Structure | 程式碼結構
|
|
215
|
+
|
|
216
|
+
### Method Length | 方法長度
|
|
217
|
+
|
|
218
|
+
- **Maximum**: 50 lines (excluding blank lines and comments)
|
|
219
|
+
- **Recommended**: 20-30 lines
|
|
220
|
+
- **最大**: 50 行(不含空行與註解)
|
|
221
|
+
- **建議**: 20-30 行
|
|
222
|
+
|
|
223
|
+
```php
|
|
224
|
+
// ✅ 正確:拆分成多個小方法
|
|
225
|
+
public function processOrder(Order $order): OrderResult
|
|
226
|
+
{
|
|
227
|
+
$this->validateOrder($order);
|
|
228
|
+
$inventory = $this->checkInventory($order->items);
|
|
229
|
+
$payment = $this->processPayment($order);
|
|
230
|
+
|
|
231
|
+
return $this->createOrderResult($order, $inventory, $payment);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ❌ 錯誤:方法過長,應該拆分
|
|
235
|
+
public function processOrder(Order $order): OrderResult
|
|
236
|
+
{
|
|
237
|
+
// ... 超過 50 行的程式碼 ...
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Nesting Depth | 巢狀深度
|
|
242
|
+
|
|
243
|
+
- **Maximum**: 3 levels
|
|
244
|
+
- **Recommended**: 2 levels
|
|
245
|
+
- **最大**: 3 層
|
|
246
|
+
- **建議**: 2 層
|
|
247
|
+
|
|
248
|
+
```php
|
|
249
|
+
// ✅ 正確:使用 early return 減少巢狀
|
|
250
|
+
public function getActiveUser(int $userId): ?User
|
|
251
|
+
{
|
|
252
|
+
$user = $this->userRepository->findById($userId);
|
|
253
|
+
|
|
254
|
+
if ($user === null) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!$user->isActive()) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return $user;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ❌ 錯誤:巢狀過深
|
|
266
|
+
public function getActiveUser(int $userId): ?User
|
|
267
|
+
{
|
|
268
|
+
$user = $this->userRepository->findById($userId);
|
|
269
|
+
if ($user !== null) {
|
|
270
|
+
if ($user->isActive()) {
|
|
271
|
+
if ($user->isVerified()) {
|
|
272
|
+
if ($user->hasPermission()) { // 第 4 層巢狀
|
|
273
|
+
return $user;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Class Member Order | 類別成員順序
|
|
283
|
+
|
|
284
|
+
```php
|
|
285
|
+
class UserService
|
|
286
|
+
{
|
|
287
|
+
// 1. Constants | 常數
|
|
288
|
+
private const int MAX_RETRY_COUNT = 3;
|
|
289
|
+
|
|
290
|
+
// 2. Static properties | 靜態屬性
|
|
291
|
+
private static int $instanceCount = 0;
|
|
292
|
+
|
|
293
|
+
// 3. Instance properties | 實例屬性
|
|
294
|
+
private readonly UserRepositoryInterface $userRepository;
|
|
295
|
+
private readonly LoggerInterface $logger;
|
|
296
|
+
|
|
297
|
+
// 4. Constructor | 建構子
|
|
298
|
+
public function __construct(
|
|
299
|
+
UserRepositoryInterface $userRepository,
|
|
300
|
+
LoggerInterface $logger,
|
|
301
|
+
) {
|
|
302
|
+
$this->userRepository = $userRepository;
|
|
303
|
+
$this->logger = $logger;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 5. Public methods | 公開方法
|
|
307
|
+
public function getUser(int $userId): ?User
|
|
308
|
+
{
|
|
309
|
+
// ...
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 6. Protected methods | 受保護方法
|
|
313
|
+
protected function validateUserId(int $userId): void
|
|
314
|
+
{
|
|
315
|
+
// ...
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 7. Private methods | 私有方法
|
|
319
|
+
private function logAccess(int $userId): void
|
|
320
|
+
{
|
|
321
|
+
// ...
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Documentation | 文件註解
|
|
329
|
+
|
|
330
|
+
### PHPDoc Standards | PHPDoc 標準
|
|
331
|
+
|
|
332
|
+
```php
|
|
333
|
+
/**
|
|
334
|
+
* 根據使用者 ID 取得使用者資訊
|
|
335
|
+
*
|
|
336
|
+
* @param int $userId 使用者的唯一識別碼
|
|
337
|
+
* @return User|null 使用者實體,若不存在則回傳 null
|
|
338
|
+
* @throws InvalidArgumentException 當 userId 為負數時拋出
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```php
|
|
342
|
+
* $user = $userService->getUserById(123);
|
|
343
|
+
* if ($user !== null) {
|
|
344
|
+
* echo $user->getName();
|
|
345
|
+
* }
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
public function getUserById(int $userId): ?User
|
|
349
|
+
{
|
|
350
|
+
if ($userId <= 0) {
|
|
351
|
+
throw new InvalidArgumentException('User ID must be positive');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return $this->userRepository->findById($userId);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### When to Use PHPDoc | 何時使用 PHPDoc
|
|
359
|
+
|
|
360
|
+
| Scenario | Required? | 情境 |
|
|
361
|
+
|----------|-----------|------|
|
|
362
|
+
| Public API methods | ✅ Required | 公開 API 方法必須 |
|
|
363
|
+
| Complex logic | ✅ Required | 複雜邏輯必須 |
|
|
364
|
+
| Type hints are sufficient | ❌ Optional | 型別提示已足夠時可選 |
|
|
365
|
+
| Private simple methods | ❌ Optional | 簡單私有方法可選 |
|
|
366
|
+
|
|
367
|
+
```php
|
|
368
|
+
// ✅ 不需要 PHPDoc:型別提示已足夠
|
|
369
|
+
public function isActive(): bool
|
|
370
|
+
{
|
|
371
|
+
return $this->status === UserStatus::Active;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ✅ 需要 PHPDoc:複雜邏輯需要說明
|
|
375
|
+
/**
|
|
376
|
+
* 計算使用者的信用評分
|
|
377
|
+
*
|
|
378
|
+
* 評分規則:
|
|
379
|
+
* - 基礎分 500 分
|
|
380
|
+
* - 帳齡每月 +5 分(最高 +100)
|
|
381
|
+
* - 逾期記錄每筆 -50 分
|
|
382
|
+
*/
|
|
383
|
+
public function calculateCreditScore(User $user): int
|
|
384
|
+
{
|
|
385
|
+
// ...
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Security Best Practices | 安全性最佳實踐
|
|
392
|
+
|
|
393
|
+
### SQL Injection Prevention | SQL Injection 防護
|
|
394
|
+
|
|
395
|
+
```php
|
|
396
|
+
// ✅ 正確:使用 Prepared Statements
|
|
397
|
+
public function findByEmail(string $email): ?User
|
|
398
|
+
{
|
|
399
|
+
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = :email');
|
|
400
|
+
$stmt->execute(['email' => $email]);
|
|
401
|
+
|
|
402
|
+
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
403
|
+
return $row ? User::fromArray($row) : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ❌ 危險:SQL Injection 漏洞
|
|
407
|
+
public function findByEmail(string $email): ?User
|
|
408
|
+
{
|
|
409
|
+
// 絕對禁止!
|
|
410
|
+
$sql = "SELECT * FROM users WHERE email = '$email'";
|
|
411
|
+
$result = $this->pdo->query($sql);
|
|
412
|
+
// ...
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### XSS Prevention | XSS 防護
|
|
417
|
+
|
|
418
|
+
```php
|
|
419
|
+
// ✅ 正確:輸出時進行編碼
|
|
420
|
+
public function renderUserName(string $name): string
|
|
421
|
+
{
|
|
422
|
+
return htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ✅ 正確:在模板中使用
|
|
426
|
+
// Blade: {{ $user->name }} 自動轉義
|
|
427
|
+
// Twig: {{ user.name }} 自動轉義
|
|
428
|
+
// 原生 PHP:
|
|
429
|
+
echo htmlspecialchars($user->name, ENT_QUOTES, 'UTF-8');
|
|
430
|
+
|
|
431
|
+
// ❌ 危險:直接輸出使用者輸入
|
|
432
|
+
echo $user->name; // XSS 漏洞
|
|
433
|
+
echo $_GET['search']; // XSS 漏洞
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Input Validation | 輸入驗證
|
|
437
|
+
|
|
438
|
+
```php
|
|
439
|
+
// ✅ 正確:驗證並清理輸入
|
|
440
|
+
public function updateEmail(int $userId, string $email): void
|
|
441
|
+
{
|
|
442
|
+
// 驗證 email 格式
|
|
443
|
+
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
444
|
+
throw new InvalidArgumentException('Invalid email format');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 正規化 email(轉小寫)
|
|
448
|
+
$email = strtolower(trim($email));
|
|
449
|
+
|
|
450
|
+
$this->userRepository->updateEmail($userId, $email);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ✅ 正確:驗證整數範圍
|
|
454
|
+
public function setPage(mixed $page): int
|
|
455
|
+
{
|
|
456
|
+
$page = filter_var($page, FILTER_VALIDATE_INT, [
|
|
457
|
+
'options' => ['min_range' => 1, 'max_range' => 1000]
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
if ($page === false) {
|
|
461
|
+
throw new InvalidArgumentException('Page must be between 1 and 1000');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return $page;
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Password Handling | 密碼處理
|
|
469
|
+
|
|
470
|
+
```php
|
|
471
|
+
// ✅ 正確:使用 password_hash
|
|
472
|
+
public function createUser(string $email, string $password): User
|
|
473
|
+
{
|
|
474
|
+
$hashedPassword = password_hash($password, PASSWORD_ARGON2ID);
|
|
475
|
+
|
|
476
|
+
return $this->userRepository->create([
|
|
477
|
+
'email' => $email,
|
|
478
|
+
'password' => $hashedPassword,
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ✅ 正確:使用 password_verify
|
|
483
|
+
public function verifyPassword(User $user, string $password): bool
|
|
484
|
+
{
|
|
485
|
+
return password_verify($password, $user->getPasswordHash());
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ❌ 危險:不安全的雜湊演算法
|
|
489
|
+
$hash = md5($password); // 禁止!
|
|
490
|
+
$hash = sha1($password); // 禁止!
|
|
491
|
+
$hash = hash('sha256', $password); // 禁止用於密碼!
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Error Handling | 錯誤處理
|
|
497
|
+
|
|
498
|
+
### Exception Usage | 例外使用
|
|
499
|
+
|
|
500
|
+
```php
|
|
501
|
+
// ✅ 正確:使用具體的例外類型
|
|
502
|
+
public function getUser(int $userId): User
|
|
503
|
+
{
|
|
504
|
+
$user = $this->userRepository->findById($userId);
|
|
505
|
+
|
|
506
|
+
if ($user === null) {
|
|
507
|
+
throw new UserNotFoundException("User not found: {$userId}");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return $user;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ✅ 正確:自訂例外類別
|
|
514
|
+
class UserNotFoundException extends RuntimeException
|
|
515
|
+
{
|
|
516
|
+
public function __construct(
|
|
517
|
+
string $message,
|
|
518
|
+
public readonly int $userId = 0,
|
|
519
|
+
?Throwable $previous = null,
|
|
520
|
+
) {
|
|
521
|
+
parent::__construct($message, 0, $previous);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ❌ 錯誤:使用通用 Exception
|
|
526
|
+
throw new Exception('User not found'); // 應使用具體類型
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Try-Catch Best Practices | Try-Catch 最佳實踐
|
|
530
|
+
|
|
531
|
+
```php
|
|
532
|
+
// ✅ 正確:捕獲具體例外
|
|
533
|
+
try {
|
|
534
|
+
$user = $this->userService->getUser($userId);
|
|
535
|
+
} catch (UserNotFoundException $e) {
|
|
536
|
+
$this->logger->warning('User not found', ['userId' => $userId]);
|
|
537
|
+
return null;
|
|
538
|
+
} catch (DatabaseException $e) {
|
|
539
|
+
$this->logger->error('Database error', ['exception' => $e]);
|
|
540
|
+
throw $e;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ❌ 錯誤:空的 catch 區塊
|
|
544
|
+
try {
|
|
545
|
+
$user = $this->userService->getUser($userId);
|
|
546
|
+
} catch (Exception $e) {
|
|
547
|
+
// 吞掉例外,不處理 - 禁止!
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ❌ 錯誤:捕獲過於廣泛
|
|
551
|
+
try {
|
|
552
|
+
$user = $this->userService->getUser($userId);
|
|
553
|
+
} catch (Throwable $e) {
|
|
554
|
+
// 過於廣泛,可能隱藏重要錯誤
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Prohibited Practices | 禁止行為
|
|
561
|
+
|
|
562
|
+
### 1. Pinyin Naming | 拼音命名
|
|
563
|
+
|
|
564
|
+
```php
|
|
565
|
+
// ❌ 絕對禁止
|
|
566
|
+
class YongHuFuWu {} // 應為 UserService
|
|
567
|
+
function yanZhengQuanXian() {} // 應為 validatePermission
|
|
568
|
+
$baiMingDan = []; // 應為 $whitelist
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 2. Mixed HTML/PHP | 混合 HTML/PHP
|
|
572
|
+
|
|
573
|
+
```php
|
|
574
|
+
// ❌ 禁止:在類別中混合 HTML
|
|
575
|
+
class UserController
|
|
576
|
+
{
|
|
577
|
+
public function show(int $id): void
|
|
578
|
+
{
|
|
579
|
+
$user = $this->getUser($id);
|
|
580
|
+
echo "<h1>{$user->name}</h1>"; // 禁止!
|
|
581
|
+
echo "<p>{$user->email}</p>"; // 禁止!
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ✅ 正確:使用模板引擎
|
|
586
|
+
class UserController
|
|
587
|
+
{
|
|
588
|
+
public function show(int $id): Response
|
|
589
|
+
{
|
|
590
|
+
$user = $this->userService->getUser($id);
|
|
591
|
+
return $this->view->render('user/show', ['user' => $user]);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### 3. Global Variables | 全域變數
|
|
597
|
+
|
|
598
|
+
```php
|
|
599
|
+
// ❌ 禁止
|
|
600
|
+
global $db;
|
|
601
|
+
$GLOBALS['config'] = [];
|
|
602
|
+
|
|
603
|
+
// ✅ 正確:使用依賴注入
|
|
604
|
+
public function __construct(
|
|
605
|
+
private readonly PDO $db,
|
|
606
|
+
private readonly Config $config,
|
|
607
|
+
) {}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### 4. Suppressing Errors | 抑制錯誤
|
|
611
|
+
|
|
612
|
+
```php
|
|
613
|
+
// ❌ 禁止:使用 @ 抑制錯誤
|
|
614
|
+
$result = @file_get_contents($path);
|
|
615
|
+
|
|
616
|
+
// ✅ 正確:正確處理錯誤
|
|
617
|
+
if (!file_exists($path)) {
|
|
618
|
+
throw new FileNotFoundException("File not found: {$path}");
|
|
619
|
+
}
|
|
620
|
+
$result = file_get_contents($path);
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### 5. eval() and Dynamic Code | eval() 與動態程式碼
|
|
624
|
+
|
|
625
|
+
```php
|
|
626
|
+
// ❌ 絕對禁止
|
|
627
|
+
eval($userInput);
|
|
628
|
+
$func = $_GET['function'];
|
|
629
|
+
$func(); // 危險!
|
|
630
|
+
|
|
631
|
+
// ❌ 禁止:動態引入
|
|
632
|
+
include $_GET['page'] . '.php'; // 路徑遍歷攻擊
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Related Standards | 相關標準
|
|
638
|
+
|
|
639
|
+
- [Anti-Hallucination Standard](../../core/anti-hallucination.md) - AI 協作防幻覺標準
|
|
640
|
+
- [Code Check-in Standards](../../core/checkin-standards.md) - 程式碼簽入檢查點標準
|
|
641
|
+
- [Commit Message Guide](../../core/commit-message-guide.md) - Commit 訊息規範
|
|
642
|
+
- [Fat-Free Framework Patterns](../frameworks/fat-free-patterns.md) - F3 開發模式
|
|
643
|
+
- [Traditional Chinese Language Guide](../locales/zh-tw.md) - 繁體中文語言規範
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Quick Reference Card | 快速參考卡
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
┌─────────────────────────────────────────────────────────┐
|
|
651
|
+
│ PHP Naming Conventions │
|
|
652
|
+
├─────────────────────────────────────────────────────────┤
|
|
653
|
+
│ Class/Interface/Enum │ PascalCase │ UserService │
|
|
654
|
+
│ Method │ camelCase │ getUserById │
|
|
655
|
+
│ Property/Variable │ $camelCase │ $userId │
|
|
656
|
+
│ Constant │ UPPER_SNAKE │ MAX_COUNT │
|
|
657
|
+
├─────────────────────────────────────────────────────────┤
|
|
658
|
+
│ Limits │
|
|
659
|
+
├─────────────────────────────────────────────────────────┤
|
|
660
|
+
│ Method Length │ ≤ 50 lines │
|
|
661
|
+
│ Nesting Depth │ ≤ 3 levels │
|
|
662
|
+
│ Line Length │ ≤ 120 characters │
|
|
663
|
+
├─────────────────────────────────────────────────────────┤
|
|
664
|
+
│ Security Checklist │
|
|
665
|
+
├─────────────────────────────────────────────────────────┤
|
|
666
|
+
│ ✅ Prepared Statements │ 防止 SQL Injection │
|
|
667
|
+
│ ✅ htmlspecialchars │ 防止 XSS │
|
|
668
|
+
│ ✅ password_hash │ 安全密碼雜湊 │
|
|
669
|
+
│ ✅ Input validation │ 驗證所有使用者輸入 │
|
|
670
|
+
├─────────────────────────────────────────────────────────┤
|
|
671
|
+
│ Prohibited │
|
|
672
|
+
├─────────────────────────────────────────────────────────┤
|
|
673
|
+
│ ❌ Pinyin naming │ yongHuFuWu │
|
|
674
|
+
│ ❌ Mixed HTML/PHP │ echo "<h1>$name</h1>" │
|
|
675
|
+
│ ❌ Global variables │ global $db │
|
|
676
|
+
│ ❌ Error suppression │ @file_get_contents() │
|
|
677
|
+
│ ❌ eval() │ eval($userInput) │
|
|
678
|
+
└─────────────────────────────────────────────────────────┘
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## Version History | 版本歷史
|
|
684
|
+
|
|
685
|
+
| Version | Date | Changes |
|
|
686
|
+
|---------|------|---------|
|
|
687
|
+
| 1.0.0 | 2025-12-23 | Initial PHP 8.1+ style guide |
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## License | 授權
|
|
692
|
+
|
|
693
|
+
This standard is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
694
|
+
|
|
695
|
+
本標準以 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 授權發布。
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
**Maintainer**: Development Team
|
|
700
|
+
**維護者**: 開發團隊
|