universal-dev-standards 6.14.0-beta.3 → 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 (39) 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/pipeline-security-gates.ai.yaml +5 -1
  4. package/bundled/core/ai-response-navigation.md +128 -12
  5. package/bundled/extensions/frameworks/fat-free-patterns.md +937 -0
  6. package/bundled/extensions/languages/csharp-style.md +464 -0
  7. package/bundled/extensions/languages/php/fat-free-patterns.md +915 -0
  8. package/bundled/extensions/languages/php/php-style.md +693 -0
  9. package/bundled/extensions/languages/php-style.md +700 -0
  10. package/bundled/extensions/locales/zh-cn.md +717 -0
  11. package/bundled/extensions/locales/zh-tw.md +717 -0
  12. package/bundled/locales/COVERAGE.md +5 -4
  13. package/bundled/locales/zh-CN/CHANGELOG.md +27 -3
  14. package/bundled/locales/zh-CN/README.md +2 -2
  15. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  16. package/bundled/locales/zh-CN/core/ai-response-navigation.md +110 -12
  17. package/bundled/locales/zh-CN/skills/README.md +1 -0
  18. package/bundled/locales/zh-CN/skills/comprehension-ladder/SKILL.md +289 -0
  19. package/bundled/locales/zh-CN/skills/comprehension-ladder/eval-cases.md +261 -0
  20. package/bundled/locales/zh-TW/CHANGELOG.md +27 -3
  21. package/bundled/locales/zh-TW/README.md +2 -2
  22. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  23. package/bundled/locales/zh-TW/core/ai-response-navigation.md +110 -12
  24. package/bundled/locales/zh-TW/skills/README.md +1 -0
  25. package/bundled/locales/zh-TW/skills/comprehension-ladder/SKILL.md +289 -0
  26. package/bundled/locales/zh-TW/skills/comprehension-ladder/eval-cases.md +261 -0
  27. package/bundled/skills/README.md +1 -0
  28. package/bundled/skills/comprehension-ladder/SKILL.md +283 -0
  29. package/bundled/skills/comprehension-ladder/eval-cases.md +255 -0
  30. package/package.json +2 -2
  31. package/src/commands/init.js +29 -9
  32. package/src/commands/update.js +3 -2
  33. package/src/installers/standards-installer.js +16 -23
  34. package/src/reconciler/plan-executor.js +10 -11
  35. package/src/uninstallers/hook-uninstaller.js +5 -3
  36. package/src/utils/copier.js +57 -0
  37. package/src/utils/git-hooks.js +8 -4
  38. package/src/utils/locale.js +19 -0
  39. package/standards-registry.json +21 -7
@@ -0,0 +1,915 @@
1
+ # Fat-Free Framework Patterns
2
+ # Fat-Free 框架模式指南
3
+
4
+ **Version**: 1.0.0
5
+ **Last Updated**: 2025-12-22
6
+ **Applicability**: Projects using Fat-Free Framework (F3)
7
+ **適用範圍**: 使用 Fat-Free 框架(F3)的專案
8
+
9
+ ---
10
+
11
+ ## Purpose | 目的
12
+
13
+ This guide defines patterns and best practices for projects using the Fat-Free Framework (F3), ensuring consistent architecture and maintainable code.
14
+
15
+ 本指南定義使用 Fat-Free 框架(F3)的專案模式與最佳實踐,確保一致的架構與可維護的程式碼。
16
+
17
+ ---
18
+
19
+ ## Project Structure | 專案結構
20
+
21
+ ### Recommended Directory Layout | 建議目錄結構
22
+
23
+ ```
24
+ project/
25
+ ├── app/
26
+ │ ├── Controllers/ # 控制器
27
+ │ │ ├── BaseController.php
28
+ │ │ ├── Api/
29
+ │ │ │ └── UserController.php
30
+ │ │ └── Web/
31
+ │ │ └── HomeController.php
32
+ │ ├── Models/ # 資料模型
33
+ │ │ └── User.php
34
+ │ ├── Services/ # 業務邏輯服務
35
+ │ │ └── UserService.php
36
+ │ ├── Repositories/ # 資料存取層
37
+ │ │ └── UserRepository.php
38
+ │ └── Middleware/ # 中介層
39
+ │ └── AuthMiddleware.php
40
+ ├── config/
41
+ │ ├── config.ini # 主設定檔
42
+ │ ├── routes.ini # 路由設定
43
+ │ └── environments/
44
+ │ ├── development.ini
45
+ │ ├── staging.ini
46
+ │ └── production.ini
47
+ ├── public/
48
+ │ ├── index.php # 入口點
49
+ │ └── assets/
50
+ ├── templates/ # 視圖範本
51
+ │ ├── layouts/
52
+ │ │ └── main.html
53
+ │ └── pages/
54
+ │ └── home.html
55
+ ├── tests/
56
+ │ ├── Unit/
57
+ │ └── Integration/
58
+ ├── vendor/
59
+ ├── composer.json
60
+ └── .htaccess
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Routing Patterns | 路由模式
66
+
67
+ ### Route Definition | 路由定義
68
+
69
+ #### Using routes.ini | 使用 routes.ini
70
+
71
+ ```ini
72
+ ; config/routes.ini
73
+
74
+ ; Web Routes | 網頁路由
75
+ [routes]
76
+ GET /=Web\HomeController->index
77
+ GET /about=Web\HomeController->about
78
+
79
+ ; API Routes | API 路由
80
+ GET /api/users=Api\UserController->list
81
+ GET /api/users/@id=Api\UserController->show
82
+ POST /api/users=Api\UserController->create
83
+ PUT /api/users/@id=Api\UserController->update
84
+ DELETE /api/users/@id=Api\UserController->delete
85
+
86
+ ; Route with middleware | 帶中介層的路由
87
+ GET /admin/*=AdminController->*,beforeroute:AuthMiddleware->check
88
+ ```
89
+
90
+ #### Using PHP Code | 使用 PHP 程式碼
91
+
92
+ ```php
93
+ <?php
94
+
95
+ declare(strict_types=1);
96
+
97
+ // config/routes.php
98
+
99
+ $f3 = \Base::instance();
100
+
101
+ // 基本路由
102
+ $f3->route('GET /', 'Web\HomeController->index');
103
+
104
+ // RESTful API 路由
105
+ $f3->route('GET /api/users', 'Api\UserController->list');
106
+ $f3->route('GET /api/users/@id', 'Api\UserController->show');
107
+ $f3->route('POST /api/users', 'Api\UserController->create');
108
+ $f3->route('PUT /api/users/@id', 'Api\UserController->update');
109
+ $f3->route('DELETE /api/users/@id', 'Api\UserController->delete');
110
+
111
+ // 路由群組(使用前綴)
112
+ $f3->route('GET /api/v2/users', 'Api\V2\UserController->list');
113
+ $f3->route('GET /api/v2/users/@id', 'Api\V2\UserController->show');
114
+ ```
115
+
116
+ ### Route Parameters | 路由參數
117
+
118
+ ```php
119
+ <?php
120
+
121
+ declare(strict_types=1);
122
+
123
+ namespace App\Controllers\Api;
124
+
125
+ use Base;
126
+
127
+ class UserController
128
+ {
129
+ private Base $f3;
130
+
131
+ public function __construct()
132
+ {
133
+ $this->f3 = Base::instance();
134
+ }
135
+
136
+ /**
137
+ * 顯示單一使用者
138
+ *
139
+ * @param Base $f3 F3 實例
140
+ * @param array $params 路由參數
141
+ */
142
+ public function show(Base $f3, array $params): void
143
+ {
144
+ $userId = (int) $params['id'];
145
+
146
+ // 驗證參數
147
+ if ($userId <= 0) {
148
+ $this->jsonError('Invalid user ID', 400);
149
+ return;
150
+ }
151
+
152
+ $user = $this->userService->getUserById($userId);
153
+
154
+ if ($user === null) {
155
+ $this->jsonError('User not found', 404);
156
+ return;
157
+ }
158
+
159
+ $this->jsonSuccess($user);
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Controller Patterns | 控制器模式
167
+
168
+ ### Base Controller | 基礎控制器
169
+
170
+ ```php
171
+ <?php
172
+
173
+ declare(strict_types=1);
174
+
175
+ namespace App\Controllers;
176
+
177
+ use Base;
178
+ use Template;
179
+
180
+ /**
181
+ * 基礎控制器
182
+ *
183
+ * 提供所有控制器共用的功能。
184
+ */
185
+ abstract class BaseController
186
+ {
187
+ protected Base $f3;
188
+ protected Template $template;
189
+
190
+ public function __construct()
191
+ {
192
+ $this->f3 = Base::instance();
193
+ $this->template = Template::instance();
194
+ }
195
+
196
+ /**
197
+ * 渲染視圖
198
+ */
199
+ protected function render(string $view, array $data = []): void
200
+ {
201
+ foreach ($data as $key => $value) {
202
+ $this->f3->set($key, $value);
203
+ }
204
+ echo $this->template->render($view);
205
+ }
206
+
207
+ /**
208
+ * 回傳 JSON 成功回應
209
+ */
210
+ protected function jsonSuccess(mixed $data, int $statusCode = 200): void
211
+ {
212
+ $this->f3->status($statusCode);
213
+ header('Content-Type: application/json; charset=utf-8');
214
+ echo json_encode([
215
+ 'success' => true,
216
+ 'data' => $data,
217
+ ], JSON_UNESCAPED_UNICODE);
218
+ }
219
+
220
+ /**
221
+ * 回傳 JSON 錯誤回應
222
+ */
223
+ protected function jsonError(string $message, int $statusCode = 400): void
224
+ {
225
+ $this->f3->status($statusCode);
226
+ header('Content-Type: application/json; charset=utf-8');
227
+ echo json_encode([
228
+ 'success' => false,
229
+ 'error' => [
230
+ 'message' => $message,
231
+ 'code' => $statusCode,
232
+ ],
233
+ ], JSON_UNESCAPED_UNICODE);
234
+ }
235
+
236
+ /**
237
+ * 取得請求 JSON 內容
238
+ */
239
+ protected function getJsonBody(): array
240
+ {
241
+ $body = $this->f3->get('BODY');
242
+ $data = json_decode($body, true);
243
+
244
+ if (json_last_error() !== JSON_ERROR_NONE) {
245
+ return [];
246
+ }
247
+
248
+ return $data ?? [];
249
+ }
250
+ }
251
+ ```
252
+
253
+ ### API Controller | API 控制器
254
+
255
+ ```php
256
+ <?php
257
+
258
+ declare(strict_types=1);
259
+
260
+ namespace App\Controllers\Api;
261
+
262
+ use App\Controllers\BaseController;
263
+ use App\Services\UserService;
264
+ use Base;
265
+
266
+ /**
267
+ * 使用者 API 控制器
268
+ */
269
+ class UserController extends BaseController
270
+ {
271
+ private UserService $userService;
272
+
273
+ public function __construct(UserService $userService)
274
+ {
275
+ parent::__construct();
276
+ $this->userService = $userService;
277
+ }
278
+
279
+ /**
280
+ * 列出所有使用者
281
+ */
282
+ public function list(Base $f3): void
283
+ {
284
+ $page = (int) $f3->get('GET.page') ?: 1;
285
+ $limit = (int) $f3->get('GET.limit') ?: 20;
286
+
287
+ $users = $this->userService->getPaginatedUsers($page, $limit);
288
+
289
+ $this->jsonSuccess([
290
+ 'users' => $users['data'],
291
+ 'pagination' => [
292
+ 'current_page' => $page,
293
+ 'per_page' => $limit,
294
+ 'total' => $users['total'],
295
+ ],
296
+ ]);
297
+ }
298
+
299
+ /**
300
+ * 建立使用者
301
+ */
302
+ public function create(Base $f3): void
303
+ {
304
+ $data = $this->getJsonBody();
305
+
306
+ // 驗證必要欄位
307
+ $required = ['name', 'email', 'password'];
308
+ foreach ($required as $field) {
309
+ if (empty($data[$field])) {
310
+ $this->jsonError("Missing required field: {$field}", 400);
311
+ return;
312
+ }
313
+ }
314
+
315
+ try {
316
+ $user = $this->userService->createUser($data);
317
+ $this->jsonSuccess($user, 201);
318
+ } catch (\Exception $e) {
319
+ $this->jsonError($e->getMessage(), 500);
320
+ }
321
+ }
322
+ }
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Model Patterns | 模型模式
328
+
329
+ ### Using Cortex ORM | 使用 Cortex ORM
330
+
331
+ ```php
332
+ <?php
333
+
334
+ declare(strict_types=1);
335
+
336
+ namespace App\Models;
337
+
338
+ use DB\Cortex;
339
+
340
+ /**
341
+ * 使用者模型
342
+ *
343
+ * @property int $id
344
+ * @property string $name
345
+ * @property string $email
346
+ * @property string $password
347
+ * @property bool $is_active
348
+ * @property string $created_at
349
+ * @property string $updated_at
350
+ */
351
+ class User extends Cortex
352
+ {
353
+ protected $db = 'DB';
354
+ protected $table = 'users';
355
+ protected $primary = 'id';
356
+
357
+ protected $fieldConf = [
358
+ 'name' => [
359
+ 'type' => 'VARCHAR(255)',
360
+ 'nullable' => false,
361
+ ],
362
+ 'email' => [
363
+ 'type' => 'VARCHAR(255)',
364
+ 'nullable' => false,
365
+ ],
366
+ 'password' => [
367
+ 'type' => 'VARCHAR(255)',
368
+ 'nullable' => false,
369
+ ],
370
+ 'is_active' => [
371
+ 'type' => 'BOOLEAN',
372
+ 'default' => true,
373
+ ],
374
+ 'created_at' => [
375
+ 'type' => 'DATETIME',
376
+ ],
377
+ 'updated_at' => [
378
+ 'type' => 'DATETIME',
379
+ ],
380
+ ];
381
+
382
+ /**
383
+ * 儲存前的處理
384
+ */
385
+ public function beforeinsert(): void
386
+ {
387
+ $this->created_at = date('Y-m-d H:i:s');
388
+ $this->updated_at = date('Y-m-d H:i:s');
389
+ }
390
+
391
+ /**
392
+ * 更新前的處理
393
+ */
394
+ public function beforeupdate(): void
395
+ {
396
+ $this->updated_at = date('Y-m-d H:i:s');
397
+ }
398
+
399
+ /**
400
+ * 檢查使用者是否啟用
401
+ */
402
+ public function isActive(): bool
403
+ {
404
+ return (bool) $this->is_active;
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### Using SQL Mapper | 使用 SQL Mapper
410
+
411
+ ```php
412
+ <?php
413
+
414
+ declare(strict_types=1);
415
+
416
+ namespace App\Models;
417
+
418
+ use DB\SQL\Mapper;
419
+
420
+ /**
421
+ * 使用者模型(SQL Mapper 版本)
422
+ */
423
+ class User extends Mapper
424
+ {
425
+ public function __construct()
426
+ {
427
+ $db = \Base::instance()->get('DB');
428
+ parent::__construct($db, 'users');
429
+ }
430
+
431
+ /**
432
+ * 根據 Email 查詢使用者
433
+ */
434
+ public function findByEmail(string $email): ?self
435
+ {
436
+ $this->load(['email = ?', $email]);
437
+
438
+ if ($this->dry()) {
439
+ return null;
440
+ }
441
+
442
+ return $this;
443
+ }
444
+
445
+ /**
446
+ * 取得所有啟用的使用者
447
+ */
448
+ public function findAllActive(): array
449
+ {
450
+ return $this->find(['is_active = ?', true]);
451
+ }
452
+ }
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Configuration Management | 設定檔管理
458
+
459
+ ### Main Configuration | 主設定檔
460
+
461
+ ```ini
462
+ ; config/config.ini
463
+
464
+ [globals]
465
+ ; 應用程式設定
466
+ APP.NAME=My Application
467
+ APP.VERSION=1.0.0
468
+ APP.DEBUG=false
469
+ APP.TIMEZONE=Asia/Taipei
470
+
471
+ ; 快取設定
472
+ CACHE=redis=127.0.0.1:6379
473
+
474
+ ; 日誌設定
475
+ LOGS=logs/
476
+
477
+ ; 上傳設定
478
+ UPLOADS=uploads/
479
+ ```
480
+
481
+ ### Environment Configuration | 環境設定
482
+
483
+ ```ini
484
+ ; config/environments/development.ini
485
+
486
+ [globals]
487
+ APP.DEBUG=true
488
+ APP.ENV=development
489
+
490
+ ; 資料庫設定
491
+ DB.HOST=localhost
492
+ DB.PORT=3306
493
+ DB.NAME=myapp_dev
494
+ DB.USER=root
495
+ DB.PASS=
496
+
497
+ ; 日誌等級
498
+ LOG.LEVEL=debug
499
+ ```
500
+
501
+ ```ini
502
+ ; config/environments/production.ini
503
+
504
+ [globals]
505
+ APP.DEBUG=false
506
+ APP.ENV=production
507
+
508
+ ; 資料庫設定(使用環境變數)
509
+ DB.HOST=${DB_HOST}
510
+ DB.PORT=${DB_PORT}
511
+ DB.NAME=${DB_NAME}
512
+ DB.USER=${DB_USER}
513
+ DB.PASS=${DB_PASS}
514
+
515
+ ; 日誌等級
516
+ LOG.LEVEL=error
517
+ ```
518
+
519
+ ### Loading Configuration | 載入設定
520
+
521
+ ```php
522
+ <?php
523
+
524
+ declare(strict_types=1);
525
+
526
+ // public/index.php
527
+
528
+ $f3 = Base::instance();
529
+
530
+ // 載入基本設定
531
+ $f3->config('config/config.ini');
532
+
533
+ // 根據環境載入設定
534
+ $env = getenv('APP_ENV') ?: 'development';
535
+ $envConfigFile = "config/environments/{$env}.ini";
536
+
537
+ if (file_exists($envConfigFile)) {
538
+ $f3->config($envConfigFile);
539
+ }
540
+
541
+ // 載入路由設定
542
+ $f3->config('config/routes.ini');
543
+
544
+ // 設定時區
545
+ date_default_timezone_set($f3->get('APP.TIMEZONE') ?: 'UTC');
546
+
547
+ // 設定資料庫連線
548
+ $f3->set('DB', new \DB\SQL(
549
+ sprintf(
550
+ 'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
551
+ $f3->get('DB.HOST'),
552
+ $f3->get('DB.PORT'),
553
+ $f3->get('DB.NAME')
554
+ ),
555
+ $f3->get('DB.USER'),
556
+ $f3->get('DB.PASS')
557
+ ));
558
+
559
+ $f3->run();
560
+ ```
561
+
562
+ ---
563
+
564
+ ## Middleware Patterns | 中介層模式
565
+
566
+ ### Authentication Middleware | 認證中介層
567
+
568
+ ```php
569
+ <?php
570
+
571
+ declare(strict_types=1);
572
+
573
+ namespace App\Middleware;
574
+
575
+ use Base;
576
+
577
+ /**
578
+ * 認證中介層
579
+ */
580
+ class AuthMiddleware
581
+ {
582
+ private Base $f3;
583
+
584
+ public function __construct()
585
+ {
586
+ $this->f3 = Base::instance();
587
+ }
588
+
589
+ /**
590
+ * 檢查認證狀態
591
+ *
592
+ * 此方法會在路由處理前執行。
593
+ */
594
+ public function check(Base $f3): void
595
+ {
596
+ $token = $this->getTokenFromHeader();
597
+
598
+ if ($token === null) {
599
+ $this->unauthorized('Missing authentication token');
600
+ return;
601
+ }
602
+
603
+ $user = $this->validateToken($token);
604
+
605
+ if ($user === null) {
606
+ $this->unauthorized('Invalid or expired token');
607
+ return;
608
+ }
609
+
610
+ // 將使用者資訊存入 F3 hive
611
+ $f3->set('AUTH.user', $user);
612
+ }
613
+
614
+ /**
615
+ * 從請求標頭取得 Token
616
+ */
617
+ private function getTokenFromHeader(): ?string
618
+ {
619
+ $header = $this->f3->get('HEADERS.Authorization');
620
+
621
+ if ($header === null) {
622
+ return null;
623
+ }
624
+
625
+ if (preg_match('/Bearer\s+(.+)/', $header, $matches)) {
626
+ return $matches[1];
627
+ }
628
+
629
+ return null;
630
+ }
631
+
632
+ /**
633
+ * 驗證 Token
634
+ */
635
+ private function validateToken(string $token): ?array
636
+ {
637
+ // 實作 Token 驗證邏輯
638
+ // ...
639
+ return null;
640
+ }
641
+
642
+ /**
643
+ * 回傳未授權錯誤
644
+ */
645
+ private function unauthorized(string $message): void
646
+ {
647
+ $this->f3->status(401);
648
+ header('Content-Type: application/json; charset=utf-8');
649
+ echo json_encode([
650
+ 'success' => false,
651
+ 'error' => [
652
+ 'message' => $message,
653
+ 'code' => 401,
654
+ ],
655
+ ], JSON_UNESCAPED_UNICODE);
656
+
657
+ // 終止路由執行
658
+ $this->f3->set('HALT', true);
659
+ }
660
+ }
661
+ ```
662
+
663
+ ### CORS Middleware | CORS 中介層
664
+
665
+ ```php
666
+ <?php
667
+
668
+ declare(strict_types=1);
669
+
670
+ namespace App\Middleware;
671
+
672
+ use Base;
673
+
674
+ /**
675
+ * CORS 中介層
676
+ */
677
+ class CorsMiddleware
678
+ {
679
+ private const ALLOWED_ORIGINS = [
680
+ 'http://localhost:3000',
681
+ 'https://example.com',
682
+ ];
683
+
684
+ private const ALLOWED_METHODS = [
685
+ 'GET',
686
+ 'POST',
687
+ 'PUT',
688
+ 'DELETE',
689
+ 'OPTIONS',
690
+ ];
691
+
692
+ private const ALLOWED_HEADERS = [
693
+ 'Content-Type',
694
+ 'Authorization',
695
+ 'X-Requested-With',
696
+ ];
697
+
698
+ /**
699
+ * 處理 CORS
700
+ */
701
+ public function handle(Base $f3): void
702
+ {
703
+ $origin = $f3->get('HEADERS.Origin');
704
+
705
+ if ($origin !== null && in_array($origin, self::ALLOWED_ORIGINS, true)) {
706
+ header("Access-Control-Allow-Origin: {$origin}");
707
+ header('Access-Control-Allow-Methods: ' . implode(', ', self::ALLOWED_METHODS));
708
+ header('Access-Control-Allow-Headers: ' . implode(', ', self::ALLOWED_HEADERS));
709
+ header('Access-Control-Max-Age: 86400');
710
+ }
711
+
712
+ // 處理 OPTIONS 預檢請求
713
+ if ($f3->get('VERB') === 'OPTIONS') {
714
+ $f3->status(204);
715
+ $f3->set('HALT', true);
716
+ }
717
+ }
718
+ }
719
+ ```
720
+
721
+ ---
722
+
723
+ ## Service Layer Patterns | 服務層模式
724
+
725
+ ### Service Class | 服務類別
726
+
727
+ ```php
728
+ <?php
729
+
730
+ declare(strict_types=1);
731
+
732
+ namespace App\Services;
733
+
734
+ use App\Models\User;
735
+ use App\Repositories\UserRepositoryInterface;
736
+ use Psr\Log\LoggerInterface;
737
+
738
+ /**
739
+ * 使用者服務
740
+ *
741
+ * 處理使用者相關的業務邏輯。
742
+ */
743
+ class UserService
744
+ {
745
+ private UserRepositoryInterface $userRepository;
746
+ private LoggerInterface $logger;
747
+
748
+ public function __construct(
749
+ UserRepositoryInterface $userRepository,
750
+ LoggerInterface $logger
751
+ ) {
752
+ $this->userRepository = $userRepository;
753
+ $this->logger = $logger;
754
+ }
755
+
756
+ /**
757
+ * 根據 ID 取得使用者
758
+ */
759
+ public function getUserById(int $userId): ?User
760
+ {
761
+ $user = $this->userRepository->findById($userId);
762
+
763
+ if ($user === null) {
764
+ $this->logger->info('User not found', ['user_id' => $userId]);
765
+ }
766
+
767
+ return $user;
768
+ }
769
+
770
+ /**
771
+ * 建立使用者
772
+ *
773
+ * @throws \InvalidArgumentException 當 email 已存在時
774
+ */
775
+ public function createUser(array $data): User
776
+ {
777
+ // 檢查 email 是否已存在
778
+ $existingUser = $this->userRepository->findByEmail($data['email']);
779
+ if ($existingUser !== null) {
780
+ throw new \InvalidArgumentException('Email already exists');
781
+ }
782
+
783
+ // 雜湊密碼
784
+ $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
785
+
786
+ $user = $this->userRepository->create($data);
787
+
788
+ $this->logger->info('User created', ['user_id' => $user->id]);
789
+
790
+ return $user;
791
+ }
792
+
793
+ /**
794
+ * 取得分頁使用者列表
795
+ */
796
+ public function getPaginatedUsers(int $page, int $limit): array
797
+ {
798
+ $offset = ($page - 1) * $limit;
799
+ $users = $this->userRepository->findAll($limit, $offset);
800
+ $total = $this->userRepository->count();
801
+
802
+ return [
803
+ 'data' => $users,
804
+ 'total' => $total,
805
+ ];
806
+ }
807
+ }
808
+ ```
809
+
810
+ ---
811
+
812
+ ## Error Handling | 錯誤處理
813
+
814
+ ### Custom Error Handler | 自定義錯誤處理
815
+
816
+ ```php
817
+ <?php
818
+
819
+ declare(strict_types=1);
820
+
821
+ // 設定錯誤處理
822
+ $f3->set('ONERROR', function (Base $f3): void {
823
+ $error = $f3->get('ERROR');
824
+
825
+ // 記錄錯誤
826
+ $logger = $f3->get('logger');
827
+ $logger->error('Application error', [
828
+ 'code' => $error['code'],
829
+ 'message' => $error['text'],
830
+ 'trace' => $error['trace'],
831
+ ]);
832
+
833
+ // API 請求回傳 JSON
834
+ if (str_starts_with($f3->get('PATH'), '/api/')) {
835
+ header('Content-Type: application/json; charset=utf-8');
836
+ echo json_encode([
837
+ 'success' => false,
838
+ 'error' => [
839
+ 'message' => $f3->get('APP.DEBUG')
840
+ ? $error['text']
841
+ : 'Internal server error',
842
+ 'code' => $error['code'],
843
+ ],
844
+ ], JSON_UNESCAPED_UNICODE);
845
+ return;
846
+ }
847
+
848
+ // 網頁請求顯示錯誤頁面
849
+ $template = Template::instance();
850
+ $f3->set('error', $error);
851
+ echo $template->render('pages/error.html');
852
+ });
853
+ ```
854
+
855
+ ---
856
+
857
+ ## Related Standards | 相關標準
858
+
859
+ - [PHP Style Guide](php-style.md) - PHP 程式碼風格指南
860
+ - [Anti-Hallucination Standard](../../../core/anti-hallucination.md) - AI 協作防幻覺標準
861
+ - [Code Check-in Standards](../../../core/checkin-standards.md) - 程式碼簽入檢查點標準
862
+ - [Testing Standards](../../../core/testing-standards.md) - 測試標準
863
+
864
+ ---
865
+
866
+ ## Quick Reference Card | 快速參考卡
867
+
868
+ ```
869
+ ┌─────────────────────────────────────────────────────────┐
870
+ │ Fat-Free Framework Quick Reference │
871
+ ├─────────────────────────────────────────────────────────┤
872
+ │ Route Definition │
873
+ │ GET /users/@id → UserController->show │
874
+ │ POST /users → UserController->create │
875
+ ├─────────────────────────────────────────────────────────┤
876
+ │ Controller Response │
877
+ │ $this->jsonSuccess($data, 200); │
878
+ │ $this->jsonError('message', 400); │
879
+ ├─────────────────────────────────────────────────────────┤
880
+ │ Route Parameters │
881
+ │ $params['id'] → 路由參數 │
882
+ │ $f3->get('GET.key') → Query 參數 │
883
+ │ $f3->get('POST.key')→ Form 參數 │
884
+ ├─────────────────────────────────────────────────────────┤
885
+ │ Configuration │
886
+ │ $f3->get('APP.NAME')→ 取得設定值 │
887
+ │ $f3->set('key', $v) → 設定值 │
888
+ ├─────────────────────────────────────────────────────────┤
889
+ │ Database │
890
+ │ $mapper->load() → 載入單筆 │
891
+ │ $mapper->find() → 查詢多筆 │
892
+ │ $mapper->save() → 儲存 │
893
+ └─────────────────────────────────────────────────────────┘
894
+ ```
895
+
896
+ ---
897
+
898
+ ## Version History | 版本歷史
899
+
900
+ | Version | Date | Changes |
901
+ |---------|------|---------|
902
+ | 1.0.0 | 2025-12-22 | Initial Fat-Free Framework patterns guide |
903
+
904
+ ---
905
+
906
+ ## License | 授權
907
+
908
+ This standard is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
909
+
910
+ 本標準以 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 授權發布。
911
+
912
+ ---
913
+
914
+ **Maintainer**: Development Team
915
+ **維護者**: 開發團隊