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.
Files changed (53) hide show
  1. package/bundled/ai/standards/ai-response-navigation.ai.yaml +43 -3
  2. package/bundled/ai/standards/checkin-standards.ai.yaml +25 -6
  3. package/bundled/ai/standards/open-work-tracking.ai.yaml +4 -1
  4. package/bundled/ai/standards/pipeline-security-gates.ai.yaml +5 -1
  5. package/bundled/core/ai-response-navigation.md +128 -12
  6. package/bundled/core/open-work-tracking.md +1 -1
  7. package/bundled/extensions/frameworks/fat-free-patterns.md +937 -0
  8. package/bundled/extensions/languages/csharp-style.md +464 -0
  9. package/bundled/extensions/languages/php/fat-free-patterns.md +915 -0
  10. package/bundled/extensions/languages/php/php-style.md +693 -0
  11. package/bundled/extensions/languages/php-style.md +700 -0
  12. package/bundled/extensions/locales/zh-cn.md +717 -0
  13. package/bundled/extensions/locales/zh-tw.md +717 -0
  14. package/bundled/locales/COVERAGE.md +5 -4
  15. package/bundled/locales/zh-CN/CHANGELOG.md +44 -3
  16. package/bundled/locales/zh-CN/README.md +2 -2
  17. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  18. package/bundled/locales/zh-CN/core/ai-response-navigation.md +110 -12
  19. package/bundled/locales/zh-CN/skills/README.md +1 -0
  20. package/bundled/locales/zh-CN/skills/comprehension-ladder/SKILL.md +289 -0
  21. package/bundled/locales/zh-CN/skills/comprehension-ladder/eval-cases.md +261 -0
  22. package/bundled/locales/zh-TW/CHANGELOG.md +44 -3
  23. package/bundled/locales/zh-TW/README.md +2 -2
  24. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  25. package/bundled/locales/zh-TW/core/ai-response-navigation.md +110 -12
  26. package/bundled/locales/zh-TW/core/open-work-tracking.md +3 -3
  27. package/bundled/locales/zh-TW/skills/README.md +1 -0
  28. package/bundled/locales/zh-TW/skills/comprehension-ladder/SKILL.md +289 -0
  29. package/bundled/locales/zh-TW/skills/comprehension-ladder/eval-cases.md +261 -0
  30. package/bundled/skills/README.md +1 -0
  31. package/bundled/skills/comprehension-ladder/SKILL.md +283 -0
  32. package/bundled/skills/comprehension-ladder/eval-cases.md +255 -0
  33. package/package.json +2 -2
  34. package/src/commands/check.js +9 -0
  35. package/src/commands/init.js +100 -27
  36. package/src/commands/uninstall.js +144 -30
  37. package/src/commands/update.js +62 -3
  38. package/src/core/install-records.js +191 -0
  39. package/src/i18n/messages.js +39 -6
  40. package/src/installers/hooks-installer.js +61 -30
  41. package/src/installers/integration-installer.js +5 -1
  42. package/src/installers/standards-installer.js +16 -23
  43. package/src/reconciler/plan-executor.js +10 -11
  44. package/src/uninstallers/hook-uninstaller.js +219 -33
  45. package/src/uninstallers/integration-uninstaller.js +35 -5
  46. package/src/utils/copier.js +57 -0
  47. package/src/utils/git-hooks.js +139 -7
  48. package/src/utils/hasher.js +36 -0
  49. package/src/utils/integration-generator.js +16 -6
  50. package/src/utils/legacy-hook-migration.js +112 -0
  51. package/src/utils/locale.js +19 -0
  52. package/src/utils/open-work-tracking.mjs +124 -23
  53. package/standards-registry.json +21 -7
@@ -0,0 +1,937 @@
1
+ # Fat-Free Framework Development Patterns
2
+ # Fat-Free Framework 開發模式
3
+
4
+ **Version**: 1.0.0
5
+ **Last Updated**: 2025-12-23
6
+ **Applicability**: All Fat-Free Framework (F3) v3.8+ projects
7
+ **適用範圍**: 所有 Fat-Free Framework (F3) v3.8+ 專案
8
+
9
+ ---
10
+
11
+ ## Purpose | 目的
12
+
13
+ This guide defines development patterns and best practices for Fat-Free Framework (F3) projects to ensure consistent, maintainable, and secure applications.
14
+
15
+ 本指南定義 Fat-Free Framework (F3) 專案的開發模式與最佳實踐,確保應用程式的一致性、可維護性與安全性。
16
+
17
+ ---
18
+
19
+ ## Project Structure | 專案結構
20
+
21
+ ### Recommended Directory Layout | 建議目錄結構
22
+
23
+ ```
24
+ project-root/
25
+ ├── app/
26
+ │ ├── Controllers/ # 控制器
27
+ │ │ ├── BaseController.php
28
+ │ │ ├── Api/
29
+ │ │ │ └── UserController.php
30
+ │ │ └── Web/
31
+ │ │ └── HomeController.php
32
+ │ ├── Models/ # 資料模型
33
+ │ │ ├── User.php
34
+ │ │ └── Traits/
35
+ │ │ └── HasTimestamps.php
36
+ │ ├── Services/ # 業務邏輯層
37
+ │ │ └── UserService.php
38
+ │ ├── Repositories/ # 資料存取層
39
+ │ │ └── UserRepository.php
40
+ │ └── Middleware/ # 中介軟體
41
+ │ ├── AuthMiddleware.php
42
+ │ └── CorsMiddleware.php
43
+ ├── config/
44
+ │ ├── config.ini # 主設定檔
45
+ │ ├── config.dev.ini # 開發環境
46
+ │ ├── config.staging.ini # 測試環境
47
+ │ └── config.prod.ini # 生產環境
48
+ ├── public/
49
+ │ ├── index.php # 入口點
50
+ │ └── assets/
51
+ ├── storage/
52
+ │ ├── cache/
53
+ │ ├── logs/
54
+ │ └── tmp/
55
+ ├── templates/ # 視圖模板
56
+ │ ├── layouts/
57
+ │ │ └── main.html
58
+ │ └── pages/
59
+ │ └── home.html
60
+ ├── tests/
61
+ │ ├── Unit/
62
+ │ └── Integration/
63
+ ├── vendor/
64
+ ├── composer.json
65
+ └── .env
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Routing | 路由定義
71
+
72
+ ### RESTful Routes | RESTful 路由
73
+
74
+ ```php
75
+ // ✅ 正確:RESTful 路由定義
76
+ // config/routes.ini 或 app/routes.php
77
+
78
+ // API Routes | API 路由
79
+ $f3->route('GET /api/users', 'App\Controllers\Api\UserController->index');
80
+ $f3->route('GET /api/users/@id', 'App\Controllers\Api\UserController->show');
81
+ $f3->route('POST /api/users', 'App\Controllers\Api\UserController->store');
82
+ $f3->route('PUT /api/users/@id', 'App\Controllers\Api\UserController->update');
83
+ $f3->route('DELETE /api/users/@id', 'App\Controllers\Api\UserController->destroy');
84
+
85
+ // Web Routes | 網頁路由
86
+ $f3->route('GET /', 'App\Controllers\Web\HomeController->index');
87
+ $f3->route('GET /login', 'App\Controllers\Web\AuthController->showLogin');
88
+ $f3->route('POST /login', 'App\Controllers\Web\AuthController->login');
89
+ ```
90
+
91
+ ### Route Groups | 路由群組
92
+
93
+ ```php
94
+ // ✅ 正確:使用路由群組
95
+ class RouteGroup
96
+ {
97
+ public static function register(Base $f3): void
98
+ {
99
+ // API v1 群組
100
+ self::apiV1Routes($f3);
101
+
102
+ // Admin 群組
103
+ self::adminRoutes($f3);
104
+ }
105
+
106
+ private static function apiV1Routes(Base $f3): void
107
+ {
108
+ $prefix = '/api/v1';
109
+
110
+ $f3->route("GET {$prefix}/users", 'Api\V1\UserController->index');
111
+ $f3->route("GET {$prefix}/users/@id", 'Api\V1\UserController->show');
112
+ $f3->route("POST {$prefix}/users", 'Api\V1\UserController->store');
113
+ }
114
+
115
+ private static function adminRoutes(Base $f3): void
116
+ {
117
+ $prefix = '/admin';
118
+
119
+ // 套用認證中介軟體
120
+ $f3->route("GET {$prefix}/dashboard [auth]", 'Admin\DashboardController->index');
121
+ $f3->route("GET {$prefix}/users [auth,admin]", 'Admin\UserController->index');
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Route Parameters | 路由參數
127
+
128
+ ```php
129
+ // ✅ 正確:路由參數處理
130
+ $f3->route('GET /users/@id', function(Base $f3, array $params) {
131
+ $userId = (int) $params['id'];
132
+ // ...
133
+ });
134
+
135
+ // ✅ 正確:可選參數
136
+ $f3->route('GET /posts/@category/@page', 'PostController->list');
137
+ $f3->route('GET /posts/@category', 'PostController->list'); // page 可選
138
+
139
+ // ✅ 正確:正則表達式約束
140
+ $f3->route('GET /users/@id:[0-9]+', 'UserController->show'); // 僅數字
141
+ $f3->route('GET /posts/@slug:[a-z0-9-]+', 'PostController->show'); // slug 格式
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Controllers | 控制器
147
+
148
+ ### Base Controller | 基礎控制器
149
+
150
+ ```php
151
+ <?php
152
+
153
+ declare(strict_types=1);
154
+
155
+ namespace App\Controllers;
156
+
157
+ use Base;
158
+ use Template;
159
+
160
+ abstract class BaseController
161
+ {
162
+ protected Base $f3;
163
+ protected Template $template;
164
+
165
+ public function __construct()
166
+ {
167
+ $this->f3 = Base::instance();
168
+ $this->template = Template::instance();
169
+ }
170
+
171
+ /**
172
+ * 渲染視圖
173
+ */
174
+ protected function render(string $view, array $data = []): string
175
+ {
176
+ foreach ($data as $key => $value) {
177
+ $this->f3->set($key, $value);
178
+ }
179
+
180
+ return $this->template->render($view);
181
+ }
182
+
183
+ /**
184
+ * 回傳 JSON 回應
185
+ */
186
+ protected function json(mixed $data, int $statusCode = 200): void
187
+ {
188
+ $this->f3->set('RESPONSE.status', $statusCode);
189
+ header('Content-Type: application/json; charset=utf-8');
190
+ echo json_encode($data, JSON_UNESCAPED_UNICODE);
191
+ }
192
+
193
+ /**
194
+ * 重新導向
195
+ */
196
+ protected function redirect(string $path): void
197
+ {
198
+ $this->f3->reroute($path);
199
+ }
200
+
201
+ /**
202
+ * 取得 POST/PUT 資料
203
+ */
204
+ protected function getBody(): array
205
+ {
206
+ $contentType = $this->f3->get('HEADERS.Content-Type') ?? '';
207
+
208
+ if (str_contains($contentType, 'application/json')) {
209
+ return json_decode($this->f3->get('BODY'), true) ?? [];
210
+ }
211
+
212
+ return $this->f3->get('POST') ?? [];
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### API Controller | API 控制器
218
+
219
+ ```php
220
+ <?php
221
+
222
+ declare(strict_types=1);
223
+
224
+ namespace App\Controllers\Api;
225
+
226
+ use App\Controllers\BaseController;
227
+ use App\Services\UserService;
228
+ use Base;
229
+
230
+ class UserController extends BaseController
231
+ {
232
+ private UserService $userService;
233
+
234
+ public function __construct()
235
+ {
236
+ parent::__construct();
237
+ $this->userService = new UserService();
238
+ }
239
+
240
+ /**
241
+ * GET /api/users
242
+ */
243
+ public function index(Base $f3): void
244
+ {
245
+ $page = (int) ($f3->get('GET.page') ?? 1);
246
+ $perPage = (int) ($f3->get('GET.per_page') ?? 20);
247
+
248
+ $result = $this->userService->paginate($page, $perPage);
249
+
250
+ $this->json([
251
+ 'data' => $result['data'],
252
+ 'meta' => [
253
+ 'current_page' => $page,
254
+ 'per_page' => $perPage,
255
+ 'total' => $result['total'],
256
+ ],
257
+ ]);
258
+ }
259
+
260
+ /**
261
+ * GET /api/users/@id
262
+ */
263
+ public function show(Base $f3, array $params): void
264
+ {
265
+ $userId = (int) $params['id'];
266
+
267
+ try {
268
+ $user = $this->userService->findById($userId);
269
+ $this->json(['data' => $user]);
270
+ } catch (UserNotFoundException $e) {
271
+ $this->json(['error' => 'User not found'], 404);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * POST /api/users
277
+ */
278
+ public function store(Base $f3): void
279
+ {
280
+ $data = $this->getBody();
281
+
282
+ // 驗證輸入
283
+ $errors = $this->validate($data, [
284
+ 'email' => 'required|email',
285
+ 'name' => 'required|min:2',
286
+ 'password' => 'required|min:8',
287
+ ]);
288
+
289
+ if (!empty($errors)) {
290
+ $this->json(['errors' => $errors], 422);
291
+ return;
292
+ }
293
+
294
+ $user = $this->userService->create($data);
295
+ $this->json(['data' => $user], 201);
296
+ }
297
+ }
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Models | 資料模型
303
+
304
+ ### Using DB Mapper | 使用 DB Mapper
305
+
306
+ ```php
307
+ <?php
308
+
309
+ declare(strict_types=1);
310
+
311
+ namespace App\Models;
312
+
313
+ use DB\SQL\Mapper;
314
+ use DateTime;
315
+
316
+ class User extends Mapper
317
+ {
318
+ public function __construct()
319
+ {
320
+ parent::__construct(
321
+ \Base::instance()->get('DB'),
322
+ 'users'
323
+ );
324
+ }
325
+
326
+ /**
327
+ * 根據 email 查詢使用者
328
+ */
329
+ public function findByEmail(string $email): ?self
330
+ {
331
+ $this->load(['email = ?', $email]);
332
+
333
+ return $this->dry() ? null : $this;
334
+ }
335
+
336
+ /**
337
+ * 驗證密碼
338
+ */
339
+ public function verifyPassword(string $password): bool
340
+ {
341
+ return password_verify($password, $this->password);
342
+ }
343
+
344
+ /**
345
+ * 設定密碼(自動雜湊)
346
+ */
347
+ public function setPassword(string $password): void
348
+ {
349
+ $this->password = password_hash($password, PASSWORD_ARGON2ID);
350
+ }
351
+
352
+ /**
353
+ * 轉換為陣列
354
+ */
355
+ public function toArray(): array
356
+ {
357
+ return [
358
+ 'id' => $this->id,
359
+ 'email' => $this->email,
360
+ 'name' => $this->name,
361
+ 'created_at' => $this->created_at,
362
+ 'updated_at' => $this->updated_at,
363
+ ];
364
+ }
365
+
366
+ /**
367
+ * 儲存前的 hook
368
+ */
369
+ public function beforeSave(): void
370
+ {
371
+ $now = (new DateTime())->format('Y-m-d H:i:s');
372
+
373
+ if ($this->dry()) {
374
+ $this->created_at = $now;
375
+ }
376
+
377
+ $this->updated_at = $now;
378
+ }
379
+ }
380
+ ```
381
+
382
+ ### Query Best Practices | 查詢最佳實踐
383
+
384
+ ```php
385
+ // ✅ 正確:使用參數化查詢
386
+ $user = new User();
387
+ $user->load(['email = ? AND status = ?', $email, 'active']);
388
+
389
+ // ✅ 正確:使用具名參數
390
+ $user->load([
391
+ 'email = :email AND status = :status',
392
+ ':email' => $email,
393
+ ':status' => 'active',
394
+ ]);
395
+
396
+ // ✅ 正確:複雜查詢
397
+ $users = $user->find(
398
+ ['department_id = ? AND role IN ?', $deptId, ['admin', 'manager']],
399
+ [
400
+ 'order' => 'created_at DESC',
401
+ 'limit' => 20,
402
+ 'offset' => 0,
403
+ ]
404
+ );
405
+
406
+ // ❌ 危險:字串拼接(SQL Injection)
407
+ $user->load("email = '$email'"); // 禁止!
408
+ ```
409
+
410
+ ---
411
+
412
+ ## Middleware | 中介軟體
413
+
414
+ ### Authentication Middleware | 認證中介軟體
415
+
416
+ ```php
417
+ <?php
418
+
419
+ declare(strict_types=1);
420
+
421
+ namespace App\Middleware;
422
+
423
+ use Base;
424
+
425
+ class AuthMiddleware
426
+ {
427
+ /**
428
+ * 驗證 JWT Token
429
+ */
430
+ public function beforeRoute(Base $f3): void
431
+ {
432
+ $authHeader = $f3->get('HEADERS.Authorization') ?? '';
433
+
434
+ if (!str_starts_with($authHeader, 'Bearer ')) {
435
+ $this->unauthorized($f3, 'Missing authorization header');
436
+ return;
437
+ }
438
+
439
+ $token = substr($authHeader, 7);
440
+
441
+ try {
442
+ $payload = $this->validateToken($token);
443
+ $f3->set('AUTH.user_id', $payload['user_id']);
444
+ $f3->set('AUTH.role', $payload['role']);
445
+ } catch (InvalidTokenException $e) {
446
+ $this->unauthorized($f3, 'Invalid or expired token');
447
+ }
448
+ }
449
+
450
+ private function unauthorized(Base $f3, string $message): void
451
+ {
452
+ $f3->set('RESPONSE.status', 401);
453
+ header('Content-Type: application/json');
454
+ echo json_encode(['error' => $message]);
455
+ exit;
456
+ }
457
+
458
+ private function validateToken(string $token): array
459
+ {
460
+ // JWT 驗證邏輯
461
+ // ...
462
+ }
463
+ }
464
+ ```
465
+
466
+ ### CORS Middleware | CORS 中介軟體
467
+
468
+ ```php
469
+ <?php
470
+
471
+ declare(strict_types=1);
472
+
473
+ namespace App\Middleware;
474
+
475
+ use Base;
476
+
477
+ class CorsMiddleware
478
+ {
479
+ private array $allowedOrigins = [
480
+ 'https://example.com',
481
+ 'https://app.example.com',
482
+ ];
483
+
484
+ public function beforeRoute(Base $f3): void
485
+ {
486
+ $origin = $f3->get('HEADERS.Origin') ?? '';
487
+
488
+ if (in_array($origin, $this->allowedOrigins, true)) {
489
+ header("Access-Control-Allow-Origin: {$origin}");
490
+ header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
491
+ header('Access-Control-Allow-Headers: Content-Type, Authorization');
492
+ header('Access-Control-Max-Age: 86400');
493
+ }
494
+
495
+ // Preflight request
496
+ if ($f3->get('VERB') === 'OPTIONS') {
497
+ $f3->set('RESPONSE.status', 204);
498
+ exit;
499
+ }
500
+ }
501
+ }
502
+ ```
503
+
504
+ ### Registering Middleware | 註冊中介軟體
505
+
506
+ ```php
507
+ // public/index.php 或 bootstrap.php
508
+
509
+ $f3 = Base::instance();
510
+
511
+ // 全域中介軟體
512
+ $f3->set('MIDDLEWARE', [
513
+ 'before' => [
514
+ new \App\Middleware\CorsMiddleware(),
515
+ ],
516
+ ]);
517
+
518
+ // 路由專用中介軟體
519
+ $f3->route('GET /api/users [auth]', 'UserController->index');
520
+ $f3->route('GET /admin/* [auth,admin]', 'AdminController->*');
521
+
522
+ // 註冊中介軟體別名
523
+ $f3->set('middleware.auth', new \App\Middleware\AuthMiddleware());
524
+ $f3->set('middleware.admin', new \App\Middleware\AdminMiddleware());
525
+ ```
526
+
527
+ ---
528
+
529
+ ## Configuration | 設定管理
530
+
531
+ ### Main Configuration | 主設定檔
532
+
533
+ ```ini
534
+ ; config/config.ini
535
+
536
+ [globals]
537
+ ; 應用程式設定
538
+ APP_NAME = "My Application"
539
+ APP_ENV = production
540
+ APP_DEBUG = false
541
+ APP_URL = "https://example.com"
542
+
543
+ ; 時區
544
+ TZ = Asia/Taipei
545
+
546
+ ; 資料庫設定
547
+ DB.dsn = "mysql:host=localhost;dbname=myapp;charset=utf8mb4"
548
+ DB.user = "root"
549
+ DB.password = ""
550
+
551
+ ; 快取設定
552
+ CACHE = "redis=127.0.0.1:6379"
553
+
554
+ ; Session 設定
555
+ SESSION.name = "my_app_session"
556
+ SESSION.expire = 3600
557
+
558
+ ; 日誌設定
559
+ LOG.level = "error"
560
+ LOG.path = "storage/logs/"
561
+ ```
562
+
563
+ ### Environment-Specific Configuration | 環境專用設定
564
+
565
+ ```ini
566
+ ; config/config.dev.ini
567
+
568
+ [globals]
569
+ APP_ENV = development
570
+ APP_DEBUG = true
571
+
572
+ DB.dsn = "mysql:host=localhost;dbname=myapp_dev;charset=utf8mb4"
573
+ DB.user = "dev_user"
574
+ DB.password = "dev_password"
575
+
576
+ CACHE = "folder=storage/cache/"
577
+
578
+ LOG.level = "debug"
579
+ ```
580
+
581
+ ```ini
582
+ ; config/config.prod.ini
583
+
584
+ [globals]
585
+ APP_ENV = production
586
+ APP_DEBUG = false
587
+
588
+ ; 從環境變數讀取敏感資訊
589
+ DB.dsn = "${DATABASE_URL}"
590
+ DB.user = "${DB_USERNAME}"
591
+ DB.password = "${DB_PASSWORD}"
592
+
593
+ CACHE = "redis=${REDIS_URL}"
594
+
595
+ LOG.level = "error"
596
+ ```
597
+
598
+ ### Loading Configuration | 載入設定
599
+
600
+ ```php
601
+ <?php
602
+ // public/index.php
603
+
604
+ $f3 = Base::instance();
605
+
606
+ // 載入主設定
607
+ $f3->config('config/config.ini');
608
+
609
+ // 根據環境載入對應設定
610
+ $env = getenv('APP_ENV') ?: 'production';
611
+ $envConfig = "config/config.{$env}.ini";
612
+
613
+ if (file_exists($envConfig)) {
614
+ $f3->config($envConfig);
615
+ }
616
+
617
+ // 從 .env 載入敏感資訊(開發環境)
618
+ if (file_exists('.env') && $env === 'development') {
619
+ $f3->config('.env');
620
+ }
621
+ ```
622
+
623
+ ### Environment Variables | 環境變數處理
624
+
625
+ ```php
626
+ // ✅ 正確:安全地讀取環境變數
627
+ class Config
628
+ {
629
+ public static function get(string $key, mixed $default = null): mixed
630
+ {
631
+ $f3 = Base::instance();
632
+
633
+ // 優先從 F3 設定讀取
634
+ $value = $f3->get($key);
635
+
636
+ if ($value !== null) {
637
+ return $value;
638
+ }
639
+
640
+ // 次之從環境變數讀取
641
+ $envValue = getenv(str_replace('.', '_', strtoupper($key)));
642
+
643
+ return $envValue !== false ? $envValue : $default;
644
+ }
645
+
646
+ public static function isDebug(): bool
647
+ {
648
+ return (bool) self::get('APP_DEBUG', false);
649
+ }
650
+
651
+ public static function isProduction(): bool
652
+ {
653
+ return self::get('APP_ENV') === 'production';
654
+ }
655
+ }
656
+ ```
657
+
658
+ ---
659
+
660
+ ## Error Handling | 錯誤處理
661
+
662
+ ### Custom Error Handler | 自訂錯誤處理器
663
+
664
+ ```php
665
+ <?php
666
+
667
+ $f3 = Base::instance();
668
+
669
+ // 設定錯誤處理器
670
+ $f3->set('ONERROR', function(Base $f3) {
671
+ $error = $f3->get('ERROR');
672
+
673
+ // 記錄錯誤
674
+ error_log(sprintf(
675
+ "[%s] %s: %s in %s:%d\nTrace: %s",
676
+ date('Y-m-d H:i:s'),
677
+ $error['code'],
678
+ $error['text'],
679
+ $error['trace'][0]['file'] ?? 'unknown',
680
+ $error['trace'][0]['line'] ?? 0,
681
+ json_encode($error['trace'])
682
+ ));
683
+
684
+ // API 回應
685
+ if (str_starts_with($f3->get('PATH'), '/api/')) {
686
+ header('Content-Type: application/json');
687
+ echo json_encode([
688
+ 'error' => [
689
+ 'code' => $error['code'],
690
+ 'message' => $f3->get('APP_DEBUG')
691
+ ? $error['text']
692
+ : 'An error occurred',
693
+ ],
694
+ ]);
695
+ return;
696
+ }
697
+
698
+ // 網頁回應
699
+ $template = Template::instance();
700
+ echo $template->render('errors/' . $error['code'] . '.html');
701
+ });
702
+ ```
703
+
704
+ ### Exception Handling | 例外處理
705
+
706
+ ```php
707
+ // ✅ 正確:在控制器中處理例外
708
+ class UserController extends BaseController
709
+ {
710
+ public function show(Base $f3, array $params): void
711
+ {
712
+ try {
713
+ $user = $this->userService->findById((int) $params['id']);
714
+ $this->json(['data' => $user->toArray()]);
715
+
716
+ } catch (UserNotFoundException $e) {
717
+ $this->json(['error' => 'User not found'], 404);
718
+
719
+ } catch (DatabaseException $e) {
720
+ $this->f3->error(500, 'Database error');
721
+
722
+ } catch (Throwable $e) {
723
+ // 記錄未預期的錯誤
724
+ error_log($e->getMessage() . "\n" . $e->getTraceAsString());
725
+ $this->f3->error(500, 'Internal server error');
726
+ }
727
+ }
728
+ }
729
+ ```
730
+
731
+ ---
732
+
733
+ ## Testing | 測試
734
+
735
+ ### Unit Test Example | 單元測試範例
736
+
737
+ ```php
738
+ <?php
739
+
740
+ declare(strict_types=1);
741
+
742
+ namespace Tests\Unit;
743
+
744
+ use App\Services\UserService;
745
+ use App\Repositories\UserRepository;
746
+ use PHPUnit\Framework\TestCase;
747
+
748
+ class UserServiceTest extends TestCase
749
+ {
750
+ private UserService $userService;
751
+ private UserRepository $userRepository;
752
+
753
+ protected function setUp(): void
754
+ {
755
+ $this->userRepository = $this->createMock(UserRepository::class);
756
+ $this->userService = new UserService($this->userRepository);
757
+ }
758
+
759
+ public function testFindByIdReturnsUser(): void
760
+ {
761
+ // Arrange
762
+ $expectedUser = ['id' => 1, 'email' => 'test@example.com'];
763
+ $this->userRepository
764
+ ->method('findById')
765
+ ->with(1)
766
+ ->willReturn($expectedUser);
767
+
768
+ // Act
769
+ $result = $this->userService->findById(1);
770
+
771
+ // Assert
772
+ $this->assertEquals($expectedUser, $result);
773
+ }
774
+
775
+ public function testFindByIdThrowsWhenNotFound(): void
776
+ {
777
+ // Arrange
778
+ $this->userRepository
779
+ ->method('findById')
780
+ ->with(999)
781
+ ->willReturn(null);
782
+
783
+ // Assert
784
+ $this->expectException(UserNotFoundException::class);
785
+
786
+ // Act
787
+ $this->userService->findById(999);
788
+ }
789
+ }
790
+ ```
791
+
792
+ ### Integration Test Example | 整合測試範例
793
+
794
+ ```php
795
+ <?php
796
+
797
+ declare(strict_types=1);
798
+
799
+ namespace Tests\Integration;
800
+
801
+ use Base;
802
+ use PHPUnit\Framework\TestCase;
803
+
804
+ class UserApiTest extends TestCase
805
+ {
806
+ private Base $f3;
807
+
808
+ protected function setUp(): void
809
+ {
810
+ $this->f3 = Base::instance();
811
+ $this->f3->config('config/config.test.ini');
812
+
813
+ // 設定測試資料庫
814
+ $this->setupTestDatabase();
815
+ }
816
+
817
+ protected function tearDown(): void
818
+ {
819
+ $this->cleanupTestDatabase();
820
+ }
821
+
822
+ public function testGetUsersReturnsJsonList(): void
823
+ {
824
+ // Arrange
825
+ $this->insertTestUsers(3);
826
+
827
+ // Act
828
+ $this->f3->mock('GET /api/users');
829
+
830
+ // Assert
831
+ $response = json_decode($this->f3->get('RESPONSE'), true);
832
+ $this->assertCount(3, $response['data']);
833
+ }
834
+
835
+ public function testCreateUserReturns201(): void
836
+ {
837
+ // Arrange
838
+ $userData = [
839
+ 'email' => 'new@example.com',
840
+ 'name' => 'New User',
841
+ 'password' => 'SecureP@ss123',
842
+ ];
843
+
844
+ // Act
845
+ $this->f3->mock('POST /api/users', $userData);
846
+
847
+ // Assert
848
+ $this->assertEquals(201, $this->f3->get('RESPONSE.status'));
849
+ }
850
+ }
851
+ ```
852
+
853
+ ---
854
+
855
+ ## Security Checklist | 安全檢查清單
856
+
857
+ ### Before Deployment | 部署前檢查
858
+
859
+ - [ ] **APP_DEBUG = false** in production
860
+ - [ ] Database credentials in environment variables (not in code)
861
+ - [ ] All user inputs validated and sanitized
862
+ - [ ] Prepared statements used for all database queries
863
+ - [ ] XSS prevention (htmlspecialchars) applied to all output
864
+ - [ ] CSRF tokens implemented for forms
865
+ - [ ] Rate limiting configured for API endpoints
866
+ - [ ] HTTPS enforced
867
+ - [ ] Secure session configuration
868
+ - [ ] Error details hidden from users in production
869
+
870
+ ---
871
+
872
+ ## Related Standards | 相關標準
873
+
874
+ - [PHP Coding Style Guide](../languages/php-style.md) - PHP 編碼風格指南
875
+ - [Anti-Hallucination Standard](../../core/anti-hallucination.md) - AI 協作防幻覺標準
876
+ - [Code Check-in Standards](../../core/checkin-standards.md) - 程式碼簽入檢查點標準
877
+ - [Testing Standards](../../core/testing-standards.md) - 測試標準
878
+
879
+ ---
880
+
881
+ ## Quick Reference Card | 快速參考卡
882
+
883
+ ```
884
+ ┌─────────────────────────────────────────────────────────┐
885
+ │ Fat-Free Framework Patterns │
886
+ ├─────────────────────────────────────────────────────────┤
887
+ │ Project Structure │
888
+ ├─────────────────────────────────────────────────────────┤
889
+ │ app/Controllers/ │ 控制器 │
890
+ │ app/Models/ │ 資料模型 (DB Mapper) │
891
+ │ app/Services/ │ 業務邏輯 │
892
+ │ app/Middleware/ │ 中介軟體 │
893
+ │ config/ │ 設定檔 │
894
+ │ templates/ │ 視圖模板 │
895
+ ├─────────────────────────────────────────────────────────┤
896
+ │ Routing │
897
+ ├─────────────────────────────────────────────────────────┤
898
+ │ GET /users │ $f3->route('GET /users', ...) │
899
+ │ Route params │ /users/@id:[0-9]+ │
900
+ │ Middleware │ [auth,admin] │
901
+ ├─────────────────────────────────────────────────────────┤
902
+ │ Configuration │
903
+ ├─────────────────────────────────────────────────────────┤
904
+ │ Main config │ config/config.ini │
905
+ │ Dev config │ config/config.dev.ini │
906
+ │ Prod config │ config/config.prod.ini │
907
+ │ Env vars │ ${DATABASE_URL} │
908
+ ├─────────────────────────────────────────────────────────┤
909
+ │ Security Essentials │
910
+ ├─────────────────────────────────────────────────────────┤
911
+ │ ✅ Prepared statements for all queries │
912
+ │ ✅ Input validation before processing │
913
+ │ ✅ APP_DEBUG = false in production │
914
+ │ ✅ Sensitive config in environment variables │
915
+ └─────────────────────────────────────────────────────────┘
916
+ ```
917
+
918
+ ---
919
+
920
+ ## Version History | 版本歷史
921
+
922
+ | Version | Date | Changes |
923
+ |---------|------|---------|
924
+ | 1.0.0 | 2025-12-23 | Initial Fat-Free Framework v3.8+ patterns |
925
+
926
+ ---
927
+
928
+ ## License | 授權
929
+
930
+ This standard is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
931
+
932
+ 本標準以 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 授權發布。
933
+
934
+ ---
935
+
936
+ **Maintainer**: Development Team
937
+ **維護者**: 開發團隊