thinkphp について
REST API やモジュール型アプリケーションを開発する PHP エンジニア向けの ThinkPHP 8.x バックエンド開発ガイドです。プロジェクト構成やコーディング規約に加え、レスポンス、検証、認証、例外処理の方針を示します。
主な機能
- 01ThinkPHP 8.x のプロジェクト構成
- 02PSR-4 の命名規則
- 03RESTful API のルーティング指針
- 04統一レスポンスと例外処理
- 05JWT 認証の規約
- 06バリデーターとモデルの指針
活用シーン
- 01ThinkPHP プロジェクトの規約策定
- 02モジュール型バックエンドの構成
- 03REST API エンドポイントの実装
- 04JWT 認証と入力検証の設計
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ youlaitech/ youlai-skills --skill thinkphpインストール後、Claude Code で/thinkphpを入力して読み込まれたか確認します。
詳細
ThinkPHP バックエンド開発規約
適用条件
- ThinkPHP プロジェクトを開発する
- REST API を実装する
- モデルを使用したデータアクセスを行う
- JWT 認証を実装する
- 権限制御を実装する
Part 1: 技術スタック
| 層 | 選定技術 | 説明 |
|---|---|---|
| 実行環境 | PHP 8.1+ | ファイバー、列挙型、読み取り専用プロパティ |
| フレームワーク | ThinkPHP 8.x | MVC アーキテクチャ、ORM 内蔵 |
| データベース | MySQL 8.x | InnoDB エンジン |
| キャッシュ | Redis 7.x | 分散キャッシュ |
| 認証 | firebase/php-jwt | JWT の発行/検証 |
| API ドキュメント | Swagger UI (swagger.json) | 静的ドキュメント |
Part 2: ディレクトリ構成
基本ディレクトリは ThinkPHP 8 公式ディレクトリ構成 に準拠します。
app/はこれをベースに業務モジュール(auth、systemなど)ごとに分割し、各モジュールに controller/model/service/validate を含めます。
app/
├── BaseController.php # 基础控制器
├── ExceptionHandle.php # 全局异常处理
├── common.php, middleware.php # 公共函数 / 中间件定义
│
├── auth/ # 认证模块
│ ├── controller/{AuthController, WxMaAuthController}
│ └── service/{AuthService, WxMaAuthService}
│
├── system/ # 系统管理
│ ├── controller/{UserController, RoleController, MenuController, ...}
│ ├── model/{User, Role, Menu, ...}
│ ├── service/{UserService, RoleService, RolePermService, DataPermissionService, ...}
│ ├── validate/{UserValidate, RoleValidate}
│ └── enums/{ActionType, LogModule}
│
├── codegen/ # 代码生成器
├── file/ # 文件管理
│
├── common/ # 公共代码
│ ├── constants/ # RedisConstants, RedisKey
│ ├── enums/ # DataScopeEnum
│ ├── exception/ # BusinessException
│ ├── middleware/ # Auth, Perm, DataScope, Cors, Log, RateLimit, ConvertCase
│ ├── model/BaseModel.php
│ ├── traits/{AuthTrait, ParamsTrait}
│ ├── util/{CaseConverter, IdStringify, PageUtil, VerifyCodeHelper}
│ └── web/ # Result, PageResult, ResultCode, IResultCode
│
├── controller/BaseController.php # 基础控制器
│
extend/ # 扩展类库
├── jwt/{JwtTokenManager, TokenManager, ...}
├── redis/{RedisClient, KeyFormatter}
├── sse/{SseEmitter, SseService, ...}
└── http/HttpClient.php
config/ route/ public/ sql/
設計原則:
common/はすべてのモジュールで共有する共通コンポーネントですextend/はサードパーティ/フレームワークの拡張(JWT/Redis/SSE)で、業務には依存しませんapp/{module}/はモジュールごとに構成し、各モジュールに controller/model/service/validate を含めます
Part 3: 命名規約
ThinkPHP 8.0 開発規約 と PSR-4 に準拠します。
3.1 ファイルとクラス
| 規則 | 例 |
|---|---|
| ディレクトリ名は小文字+アンダースコアを使用 | controller/, user_service/ |
| クラス名はキャメルケース(先頭大文字) | UserController, UserValidate |
| クラス名とファイル名を一致させる | UserController → UserController.php |
| 名前空間をディレクトリパスと一致させる | app\system\controller → app/system/controller/ |
3.2 定数と設定
| 種類 | 規約 | 例 |
|---|---|---|
| 定数 | 大文字とアンダースコア | APP_PATH, HAS_ONE |
| 設定パラメーター | 小文字とアンダースコア | url_route_on |
| 環境変数 | 大文字とアンダースコア | APP_DEBUG, DB_HOST |
3.3 データテーブルとフィールド
| 規則 | 例 |
|---|---|
| データテーブル名は小文字+アンダースコアを使用 | sys_user, sys_role_menu |
| フィールド名は小文字+アンダースコアを使用 | user_name, create_time |
| キャメルケースと中国語による命名は禁止 | ❌ userName, ❌ 用户表 |
3.4 関数と変数
| 種類 | 規約 | 例 |
|---|---|---|
| メソッド | キャメルケース(先頭小文字) | getUserName() |
| グローバル関数 | 小文字とアンダースコア | get_client_ip() |
| プロパティ | キャメルケース(先頭小文字) | $tableName |
Part 4: RESTful API 規約
4.1 標準 CRUD パス
| 操作 | メソッド | パス |
|---|---|---|
| ページネーション付き一覧 | GET | /api/v1/users/page |
| 詳細 | GET | /api/v1/users/:id |
| 新規作成 | POST | /api/v1/users |
| 更新 | PUT | /api/v1/users |
| 削除 | DELETE | /api/v1/users/:id |
| 一括削除 | DELETE | /api/v1/users/batch |
| ドロップダウン選択肢 | GET | /api/v1/users/options |
4.2 Controller テンプレート
declare(strict_types=1);
namespace app\system\controller;
use app\BaseController;
use app\system\model\User;
use app\system\validate\UserValidate;
use app\common\exception\BusinessException;
use app\common\web\ResultCode;
final class UserController extends BaseController
{
protected bool $requireAuth = true;
/** 用户分页列表 */
public function page()
{
$params = $this->request->get();
$result = User::where(function ($q) use ($params) {
if (!empty($params['keywords']))
$q->whereLike('username|nickname', $params['keywords']);
if (isset($params['status']))
$q->where('status', $params['status']);
})->order('create_time', 'desc')
->paginate(['page' => $params['pageNum'] ?? 1, 'list_rows' => $params['pageSize'] ?? 20]);
return $this->successPaginate($result->items(), $result->total());
}
/** 新增用户 */
public function create()
{
$params = $this->request->post();
$this->validate($params, UserValidate::class, 'create');
if (User::where('username', $params['username'])->find())
throw new BusinessException(ResultCode::USER_ERROR, '用户名已存在');
$user = new User();
$user->username = $params['username'];
$user->password = password_hash($params['password'], PASSWORD_BCRYPT);
$user->save();
return $this->success(['id' => $user->id]);
}
}
Part 5: レスポンス形式と例外処理
5.1 統一レスポンス
class Result
{
public static function success(mixed $data = null, string $msg = '成功'): array
{
return ['code' => '00000', 'msg' => $msg, 'data' => $data];
}
public static function page(array $list, int $total): array
{
return ['code' => '00000', 'msg' => '成功', 'data' => ['list' => $list, 'total' => $total]];
}
public static function failedWith(ResultCode $code, string $msg = ''): array
{
return ['code' => $code->value, 'msg' => $msg ?: $code->getMsg(), 'data' => null];
}
}
5.2 基本コントローラーのレスポンスメソッド
abstract class BaseController
{
protected function success(mixed $data = null, string $msg = ''): Json
{
return json(Result::success($data, $msg ?: ResultCode::SUCCESS->getMsg()));
}
protected function successPaginate(array $list, int $total): Json
{
return json(Result::page($list, $total));
}
}
5.3 業務例外
final class BusinessException extends RuntimeException
{
private ResultCode $resultCode;
public function __construct(ResultCode $resultCode, string $message = '')
{
$this->resultCode = $resultCode;
parent::__construct($message ?: $resultCode->getMsg());
}
public function getResultCode(): ResultCode { return $this->resultCode; }
}
5.4 グローバル例外処理
class ExceptionHandle extends Handle
{
public function render($request, Throwable $e): Response
{
if ($e instanceof BusinessException)
return json(Result::failedWith($e->getResultCode(), $e->getMessage()));
if ($e instanceof ValidateException)
return json(Result::failedWith(ResultCode::PARAM_ERROR, $e->getError()));
return parent::render($request, $e); // 生产环境隐藏详情
}
}
// config/app.php — 注册异常处理器
return ['exception_handle' => \app\ExceptionHandle::class];
Part 6: モデル規約
class BaseModel extends Model
{
protected $autoWriteTimestamp = true;
protected $createTime = 'create_time';
protected $updateTime = 'update_time';
use \think\model\concern\SoftDelete;
protected $deleteTime = 'delete_time';
protected $defaultSoftDelete = 0;
}
final class User extends BaseModel
{
protected $name = 'sys_user';
protected $pk = 'id';
protected $hidden = ['password', 'delete_time'];
public function dept()
{
return $this->belongsTo(Dept::class, 'dept_id');
}
public function roles()
{
return $this->belongsToMany(Role::class, UserRole::class, 'role_id', 'user_id');
}
}
Part 7: ルーティング規約
use think\facade\Route;
// 认证接口(无需登录)
Route::group('api/v1/auth', function () {
Route::post('login', 'auth.controller.AuthController/login');
Route::post('logout', 'auth.controller.AuthController/logout');
Route::post('refresh-token', 'auth.controller.AuthController/refreshToken');
})->allowCrossDomain();
// 需要登录的接口
Route::group('api/v1', function () {
Route::group('users', function () {
Route::get('page', 'system.controller.UserController/page');
Route::get(':id', 'system.controller.UserController/detail');
Route::post('', 'system.controller.UserController/create');
Route::put('', 'system.controller.UserController/update');
Route::delete(':id', 'system.controller.UserController/delete');
Route::delete('batch', 'system.controller.UserController/batchDelete');
});
})->middleware(\app\middleware\Auth::class)->allowCrossDomain();
Part 8: 認証規約
8.1 JWT サービス
final class JwtService
{
public static function generateAccessToken(array $payload): string
{
$payload['exp'] = time() + Config::get('jwt.access_ttl', 7200);
$payload['iat'] = time();
$payload['iss'] = Config::get('jwt.issuer', 'youlai-think');
return JWT::encode($payload, Config::get('jwt.secret'), 'HS256');
}
public static function parseToken(string $token): ?array
{
try {
return (array) JWT::decode($token, new Key(Config::get('jwt.secret'), 'HS256'));
} catch (\Exception) { return null; }
}
}
8.2 認証ミドルウェア
final class Auth
{
public function handle(Request $request, Closure $next): Response
{
$token = str_replace('Bearer ', '', $request->header('Authorization', ''));
if (empty($token)) return json(Result::failedWith(ResultCode::ACCESS_TOKEN_INVALID));
$payload = JwtService::parseToken($token);
if (!$payload) return json(Result::failedWith(ResultCode::ACCESS_TOKEN_INVALID));
$request->userId = $payload['sub'];
$request->username = $payload['username'] ?? '';
return $next($request);
}
}
Part 9: バリデーター規約
final class UserValidate extends Validate
{
protected $rule = [
'username' => 'require|max:50',
'password' => 'require|min:6|max:20',
'nickname' => 'require|max:50',
'mobile' => 'mobile',
'status' => 'in:0,1',
];
protected $scene = [
'create' => ['username', 'password', 'nickname', 'mobile', 'status'],
'update' => ['id', 'nickname', 'mobile', 'status'],
];
}
Part 10: コメント規約
- すべての public メソッドに PHPDoc
/** */コメントを付ける - メソッドのコメントには
@param/@return/@throwsを含める declare(strict_types=1)をファイルの先頭行に記述する(PHP 8 の厳密な型チェック)
/**
* 用户管理控制器。
*/
final class UserController extends BaseController
{
/**
* 创建新用户。
*
* @return Json {"code":"00000","data":{"id":1}}
* @throws BusinessException 用户名已存在
*/
public function create()
{
// ...
}
}
Part 11: コード品質チェックリスト
- ディレクトリ名は小文字+アンダースコア、クラス名はキャメルケースを使用する
- データテーブル名とフィールド名は小文字+アンダースコアを使用する
- RESTful API のパス規約に準拠する
- 統一レスポンス形式(
Result/ResultCode)を使用する - グローバル例外処理(
ExceptionHandle)を実装する - 業務例外には
BusinessException+ResultCodeを使用する - モデルは
BaseModelを継承し、論理削除を使用する -
UserValidateバリデーターでパラメーターを検証する - ルートを明示的にマッピングする
-
declare(strict_types=1)をファイルの先頭行に記述する
よくあるアンチパターン
| アンチパターン | 正しい方法 |
|---|---|
return json(['code' => 0]) が各所に散在する | Result::success() で統一的にラップする |
| Controller 内で直接 SQL クエリーを実行する | Model / Service 層に委譲する |
if (!$user) { exit('用户不存在'); } | BusinessException をスローし、グローバル例外処理に任せる |
catch (\Exception $e) { } で例外を空のまま捕捉する | ログを記録するか、BusinessException を再スローする |
| テーブル名にキャメルケースを使用する | sys_user(小文字+アンダースコア)を使用し、SysUser は禁止 |
コメント
Markdown 対応