SuperAPI AI Directory

thinkphp

General & other

View repository

Updated 10-16 01:48

About this skill

ThinkPHP Backend Development Standards

References:ThinkPHP 8.0 Development Standards | PSR-4 Autoloading

Trigger Conditions

  • Develop ThinkPHP projects
  • Implement REST APIs
  • Use model data access
  • Implement JWT authentication
  • Implement permission control

Part 1: Technology Stack

Layer Selection Description
Runtime Environment PHP 8.1+ Fibers, Enums, Readonly Properties
Framework ThinkPHP 8.x MVC Architecture, Built-in ORM
Database MySQL 8.x InnoDB Engine
Cache Redis 7.x Distributed Cache
Authentication firebase/php-jwt JWT Signing/Verification
API Documentation Swagger UI (swagger.json) Static Documentation

Part 2: Directory Structure

The base directory is based on ThinkPHP 8 Official Directory Structure ;app/ On this basis, it is divided by business modules (auth、system , etc.), and each module contains 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/

Design Principles:

  • common/ is a public component shared by all modules
  • extend/ is a third-party/framework extension (JWT/Redis/SSE) and does not depend on business logic
  • app/{module}/ Organized by module, each module contains controller/model/service/validate

Part 3: Naming Conventions

Follow ThinkPHP 8.0 Development Standards + PSR-4。

3.1 Files and Classes

Rule Example
Directories use lowercase + underscores controller/, user_service/
Class names use camel case (first letter uppercase) UserController, UserValidate
Class name matches file name UserController → UserController.php
Namespace matches directory path app\system\controller → app/system/controller/

3.2 Constants and Configuration

Type Convention Example
Constant Uppercase letters and underscores APP_PATH, HAS_ONE
Configuration Parameters Lowercase letters and underscores url_route_on
Environment Variables Uppercase letters and underscores APP_DEBUG, DB_HOST

3.3 Data Tables and Fields

Rule Example
Data tables use lowercase + underscores sys_user, sys_role_menu
Fields use lowercase + underscores user_name, create_time
Camel case and Chinese naming are prohibited ❌ userName, ❌ 用户表

3.4 Functions and Variables

Type Convention Example
Method Camel case (first letter lowercase) getUserName()
Global Functions Lowercase letters and underscores get_client_ip()
Property Camel case (first letter lowercase) $tableName

Part 4: RESTful API Standards

4.1 Standard CRUD Paths

Operation Method Path
Paginated List GET /api/v1/users/page
Details GET /api/v1/users/:id
Create POST /api/v1/users
Update PUT /api/v1/users
Delete DELETE /api/v1/users/:id
Batch Delete DELETE /api/v1/users/batch
Dropdown Options GET /api/v1/users/options

4.2 Controller Template

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: Response Format and Exception Handling

5.1 Unified Response

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 Base Controller Response Methods

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 Business Exceptions

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 Global Exception Handling

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: Model Standards

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: Routing Standards

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: Authentication Specification

8.1 JWT Service

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 Authentication Middleware

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: Validator Specification

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: Comment Specification

  • All public methods use PHPDoc /** */ comments
  • Method comments include @param/@return/@throws
  • declare(strict_types=1) on the first line of the file (PHP 8 enforces type checking)
/**
 * 用户管理控制器。
 */
final class UserController extends BaseController
{
    /**
     * 创建新用户。
     *
     * @return Json {"code":"00000","data":{"id":1}}
     * @throws BusinessException 用户名已存在
     */
    public function create()
    {
        // ...
    }
}

Part 11: Code Quality Checklist

  • Directories use lowercase + underscores, class names use camel case
  • Data tables and fields use lowercase + underscores
  • Follow RESTful API path conventions
  • Use a unified response format (Result / ResultCode)
  • Implement global exception handling (ExceptionHandle)
  • Business exceptions use BusinessException + ResultCode
  • Models inherit from BaseModel, use soft deletes
  • Use the UserValidate validator to validate parameters
  • Explicit route mapping
  • declare(strict_types=1) on the first line of the file

Common Anti-Patterns

Anti-Pattern Correct Practice
return json(['code' => 0]) Scattered everywhere Use Result::success() unified encapsulation
Direct SQL queries in the Controller Delegate to the Model / Service layer
if (!$user) { exit('用户不存在'); } Throw BusinessException, go through global exception handling
catch (\Exception $e) { } empty catch Log or rethrow BusinessException
Table names use camel case sys_user(lowercase + underscores), prohibit SysUser