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,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
|
+
**維護者**: 開發團隊
|