# Coding Conventions **Analysis Date:** 2026-04-21 ## Naming Patterns **Files:** - Controllers: PascalCase, directory structure mirrors URL path. Example: `application/admin/controller/user/User.php` -> `/admin/user/user` - Models: PascalCase in parallel directory structure. Example: `application/admin/model/User.php` - Validates: PascalCase. Example: `application/admin/validate/User.php` - Libraries: PascalCase. Example: `application/admin/library/Auth.php`, `application/common/library/Upload.php` - Traits: PascalCase. Example: `application/admin/library/traits/Backend.php` - Behaviors: PascalCase. Example: `application/admin/behavior/AdminLog.php` - Language files: lowercase `zh-cn.php`, mirroring controller directory structure - View templates: lowercase `.html`, matching action names. Example: `application/admin/view/user/user/index.html` - JS files: lowercase, mirroring controller path. Example: `public/assets/js/backend/user/user.js` - CSS: lowercase `.css` with `.min.css` variants - Less: lowercase `.less` (source files in `public/assets/less/`) - Common helper functions: snake_case. Example: `build_select()`, `cdnurl()`, `datetime()`, `letter_avatar()` **Functions/Methods:** - Controller action methods: lowercase with underscores. Examples: `index()`, `add()`, `edit()`, `del()`, `recyclebin()`, `get_field_list()`, `get_controller_list()` - Controller protected methods: camelCase. Examples: `buildparams()`, `selectpage()`, `getDataLimitAdminIds()`, `loadlang()`, `assignconfig()` - Model accessors/mutators: ThinkPHP convention `get{FieldName}Attr()` / `set{FieldName}Attr()`. Example: `getPrevtimeTextAttr()`, `setJointimeAttr()`, `getAvatarAttr()`, `setBirthdayAttr()` - Model list methods: PascalCase `getStatusList()`, `getGenderList()` - Model static methods: camelCase. Example: `User::money()`, `User::score()`, `User::nextlevel()` - Library methods: camelCase. Example: `Auth::login()`, `Auth::getUserinfo()`, `Upload::init()` - Global functions: snake_case, wrapped in `if (!function_exists('...'))`. Example: `__()`, `format_bytes()`, `check_cors_request()`, `xss_clean()` - Addon methods: camelCase following ThinkPHP convention **Variables:** - Controller properties: camelCase. Examples: `$noNeedLogin`, `$model`, `$searchFields`, `$relationSearch`, `$dataLimit` - Library private properties: underscore prefix + camelCase. Examples: `$_error`, `$_logined`, `$_user`, `$_token` (in `application/common/library/Auth.php`) - Library protected properties: camelCase, no prefix. Examples: `$keeptime`, `$requestUri`, `$allowFields` - Local variables: camelCase. Examples: `$tableList`, `$fieldlist`, `$insert_data`, `$changedata` - Database fields: lowercase with underscores. Examples: `createtime`, `updatetime`, `group_id`, `loginip` - Request input: via `$this->request->post('row/a')` (array), `$this->request->request('keyField')` (single) **Types/Namespaces:** - Namespace convention: lowercase `app\admin\controller`, `app\common\model`, `app\api\library` - Class names: PascalCase. Example: `class User extends Backend` - Addon namespace: `addons\{name}\` mapped via PSR-4 in `composer.json` - Fast tools namespace: `fast\` maps to `extend/fast/` directory ## Code Style **Formatting:** - 4-space indentation throughout (no tabs) - Opening brace on same line for classes/functions: `class User extends Backend\n{` - Opening brace on next line for control structures in some files, same line in others (inconsistent) - Closing PHP tag `?>` omitted at end of files - Files start with `= 7.4.0 (per `composer.json`) - Union types in catch blocks: `catch (ValidateException|PDOException|Exception $e)` - Null coalescing: `$value ?? ''` - Match expressions: Not used - Arrow functions: Not used - Typed properties: Not used **Linting/Formatting Tools:** - No project-level `.php-cs-fixer.php`, `phpcs.xml`, `.editorconfig`, or `biome.json` - No ESLint or stylelint configured - PhpStorm `.idea/` directory present with PHP 7.4 language level configured - Static analysis tools (PHPStan, PHPCS, MessDetector) configured in IDE but not activated (`transferred=true`) ## Import Organization **Order in PHP files:** 1. `namespace app\admin\controller;` 2. `use` statements for app classes: `use app\common\controller\Backend;` 3. `use` statements for ThinkPHP classes: `use think\Db;`, `use think\Config;`, `use think\Exception;` 4. `use` statements for vendor/third-party classes: `use fast\Tree;` 5. No grouping separators between use statement categories **Example from `application/admin/controller/user/User.php`:** ```php namespace app\admin\controller\user; use app\common\controller\Backend; use app\common\library\Auth; ``` **Example from `application/common/controller/Backend.php`:** ```php namespace app\common\controller; use app\admin\library\Auth; use think\Config; use think\Controller; use think\Hook; use think\Lang; use think\Loader; use think\Model; use think\Session; use fast\Tree; use think\Validate; ``` **Path Aliases:** - No PSR-4 path aliases configured beyond `addons\\` -> `addons/` in `composer.json` - ThinkPHP autoload handles `app\` namespace mapping automatically - `fast\` namespace handled by ThinkPHP custom autoloader -> `extend/fast/` ## ThinkPHP Conventions **Model Table Naming:** - `$name` property defines the table name without prefix. Example: `protected $name = 'user';` maps to `{prefix}user` - Table prefix configured in `application/config.php` database section - All models extend `think\Model` **Timestamp Convention:** - Auto timestamp type: `protected $autoWriteTimestamp = 'int';` (Unix integer timestamps) - Create field name: `protected $createTime = 'createtime';` - Update field name: `protected $updateTime = 'updatetime';` **Controller Initialization:** - `_initialize()` method called by ThinkPHP before each action - Always calls `parent::_initialize()` first - Sets `$this->model` instance in `_initialize()` - Assigns view data: `$this->view->assign("statusList", ...)` **Magic Model Methods:** - Dynamic finders via `@method` PHPDoc annotations - `getBy{Field}()` auto-generated by ThinkPHP for any column - Example: `@method static mixed getByUsername($str)` in `application/common/model/User.php` - Usage: `\app\common\model\User::getByMobile($mobile)` **AJAX Detection Pattern:** ```php if ($this->request->isAjax()) { // Return JSON response return json(['total' => $list->total(), 'rows' => $list->items()]); } return $this->view->fetch(); ``` ## FastAdmin Patterns **Backend Base Class (`app\common\controller\Backend`):** - All admin controllers extend this class - Provides via trait `app\admin\library\traits\Backend`: - `index()` - List with pagination and filtering - `add()` - Create record - `edit($ids)` - Update record - `del($ids)` - Soft delete - `destroy($ids)` - Hard delete (from recycle bin) - `restore($ids)` - Restore from recycle bin - `multi($ids)` - Batch update - `recyclebin()` - Recycle bin list - `import()` - Excel/CSV import - `selectpage()` - SelectPage dropdown data - Key configurable properties: - `$noNeedLogin = []` - Methods skipping authentication entirely - `$noNeedRight = []` - Methods skipping permission check (still need login) - `$model = null` - Associated model instance - `$searchFields = 'id'` - Fields for quick search - `$relationSearch = false` - Whether to use table alias in queries - `$dataLimit = false` - Data scope: `auth`/`personal`/`false` - `$dataLimitField = 'admin_id'` - Field for data restriction - `$dataLimitFieldAutoFill = true` - Auto-fill restriction field - `$modelValidate = false` - Enable model-level validation - `$modelSceneValidate = false` - Enable scene-based validation - `$multiFields = 'status'` - Fields allowed in batch operations - `$selectpageFields = '*'` - Fields shown in SelectPage - `$excludeFields = ""` - Fields to exclude from form submission - `$importHeadType = 'comment'` - Import header type: `comment`/`name` - `$layout = 'default'` - Template layout name **Standard CRUD Controller Pattern:** ```php /** * 会员管理 * * @icon fa fa-user */ class User extends Backend { protected $relationSearch = true; protected $searchFields = 'id,username,nickname'; /** * @var \app\admin\model\User */ protected $model = null; public function _initialize() { parent::_initialize(); $this->model = new \app\admin\model\User; } } ``` **CRUD Generation:** - Via `php think crud` command - Flags: `--table`, `--controller`, `--model`, `--fields`, `--force`, `--delete`, `--menu` - Extended flags: `--setcheckboxsuffix`, `--enumradiosuffix`, `--imagefield`, `--filefield`, etc. **Auth Patterns:** - Admin auth: `app\admin\library\Auth` - backend admin user authentication - User auth: `app\common\library\Auth` - frontend/member user authentication - Both use singleton: `Auth::instance()` - Permission check: `$this->auth->check($path)` where `$path = 'controller/action'` - Login check: `$this->auth->isLogin()` - Super admin check: `$this->auth->isSuperAdmin()` **API Controller Pattern (`app\common\controller\Api`):** - API controllers extend this (NOT `think\Controller`) - Does NOT extend `think\Controller` - standalone class with `__construct()` - Response format: `{'code': 1|0, 'msg': '', 'time': , 'data': ...}` - Success: `$this->success($msg, $data)` (code=1, HTTP 200) - Error: `$this->error($msg, $data, $httpCode)` (code=0) - HTTP status codes: 401 for unauth, 403 for forbidden - API annotations for doc generation: `@ApiMethod(POST)`, `@ApiParams(name="...", type="string", required=true, description="...")` - Default request filter: `trim,strip_tags,htmlspecialchars` **Frontend Controller Pattern (`app\common\controller\Frontend`):** - Index module controllers extend this - Similar to Backend but for public-facing pages - Uses `app\common\library\Auth` for member auth - Layout can be empty string for no layout: `protected $layout = '';` ## Language File Structure **Directory Layout:** ``` application/{module}/lang/zh-cn.php # Module-level translations application/{module}/lang/zh-cn/controller.php # Controller translations application/{module}/lang/zh-cn/sub/controller.php # Nested controller translations application/{module}/lang/{locale}/controller.php # Other locales ``` **Observed locales:** - `zh-cn` (Simplified Chinese) - primary language for all modules - `en` - only exists for `application/index/lang/en/index.php` **File Format:** ```php return [ 'Id' => 'ID', 'Group_id' => '组别ID', 'Username' => '用户名', 'Leave password blank if dont want to change' => '不修改密码请留空', ]; ``` **Usage Pattern:** - `__('Key')` - Simple translation - `__('Key %s', $value)` - Parameterized translation - Keys use PascalCase for field names (matching DB column names) - Keys use sentence case for messages - Language auto-loaded by controller in `loadlang()` method - Language detection: `$this->request->langset()` with regex validation, defaults to `zh-cn` ## Template Syntax **Template Engine:** ThinkPHP built-in template engine **File Extension:** `.html` **Template Tags Used:** | Tag | Example | |-----|---------| | Output | `{$variable}`, `{$var\|htmlentities}`, `{$var\|default='default'}` | | Function | `{:__('Dashboard')}`, `{:build_heading()}`, `{:build_toolbar('refresh,edit,del')}` | | Condition | `{if condition="$auth->check('dashboard')"}`, `{if !IS_DIALOG}`, `{/if}` | | If-else shorthand | `{:defined('IS_DIALOG') && IS_DIALOG ? 'is-dialog' : ''}` | | Loop | `{foreach $breadcrumb as $vo}`, `{/foreach}` | | Include | `{include file="common/meta" /}` | | Layout placeholder | `{__CONTENT__}` | | ThinkPHP config | `{$Think.config.fastadmin.breadcrumb}` | | Inline PHP | `{:$auth->check('user/user/multi')?'':'hide'}` | **Layout Structure:** ``` application/{module}/view/layout/default.html # Layout wrapper with {__CONTENT__} application/{module}/view/{controller}/index.html # Page content application/{module}/view/common/meta.html # Shared section application/{module}/view/common/script.html # Shared JS loading application/{module}/view/common/header.html # Header fragment application/{module}/view/common/menu.html # Sidebar menu application/{module}/view/common/control.html # Control bar fragment ``` **CSS Framework:** Bootstrap 3 with AdminLTE theme **Common Classes:** - Layout: `.panel`, `.panel-default`, `.panel-intro`, `.panel-heading`, `.panel-body` - Table: `.table`, `.table-striped`, `.table-bordered`, `.table-hover`, `.table-nowrap` - Buttons: `.btn`, `.btn-primary`, `.btn-success`, `.btn-danger`, `.btn-info`, `.btn-xs` - Form: `.form-control`, `.selectpicker`, `.form-group`, `.control-label` - FastAdmin custom: `.btn-dialog`, `.btn-addtabs`, `.btn-ajax`, `.btn-click`, `.searchit` ## JS AMD Module Pattern (RequireJS) **Module Definition Pattern:** ```javascript define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { var Controller = { index: function () { Table.api.init({ extend: { index_url: 'user/user/index', add_url: 'user/user/add', edit_url: 'user/user/edit', del_url: 'user/user/del', multi_url: 'user/user/multi', table: 'user', } }); var table = $("#table"); table.bootstrapTable({ url: $.fn.bootstrapTable.defaults.extend.index_url, pk: 'id', sortName: 'user.id', columns: [[ {checkbox: true}, {field: 'id', title: __('Id'), sortable: true}, {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate} ]] }); Table.api.bindevent(table); }, add: function () { Controller.api.bindevent(); }, edit: function () { Controller.api.bindevent(); }, api: { bindevent: function () { Form.api.bindevent($("form[role=form]")); } } }; return Controller; }); ``` **RequireJS Configuration Files:** - `public/assets/js/require-backend.js` - Backend module config (minified as `require-backend.min.js`) - `public/assets/js/require-frontend.js` - Frontend module config (minified) - `public/assets/js/require-table.js` - Bootstrap-table wrapper module - `public/assets/js/require-form.js` - Form handling module - `public/assets/js/require-upload.js` - Upload module **Core JS Modules:** - `Fast` / `Fast.api` - Core API: `ajax()`, `open()`, `cdnurl()`, `fixurl()`, `selectedids()` - `Backend` - Admin: sidebar badges, tab management, dialog/_ajax handlers - `Frontend` - Frontend: captcha sending, touch swipe for sidebar - `Table` - Bootstrap-table wrapper: `api.init()`, `api.bindevent()`, formatters, events - `Form` - Form validation and submission: `api.bindevent()` - `Template` - art-template JS template engine - `Moment` - Date/time formatting (with `moment/locale/zh-cn`) **Global Objects (set on `window`):** - `window.Backend` - Backend namespace - `window.Frontend` - Frontend namespace - `window.Config` - Server-rendered config object - `window.Toastr` - Toastr notification library - `window.Layer` - Layer popup library (layui-based) - `window.Table` - (via require) Table module - `window.Form` - (via require) Form module - `window.Template` - Template engine - `window.Moment` - Moment.js **Controller JS File Convention:** - Location: `public/assets/js/backend/{controller_path}.js` - Standard methods: `Controller.index()`, `Controller.add()`, `Controller.edit()` - Sub-controller: `public/assets/js/backend/auth/admin.js` - Each controller JS is loaded dynamically based on `Config.jsname` **Button/Action Patterns in JS:** - `.btn-dialog` / `.dialogit` - Opens URL in Layer dialog - `.btn-addtabs` / `.addtabsit` - Opens URL in new tab - `.btn-ajax` / `.ajaxit` - Sends AJAX request - `.btn-click` / `.clickit` - Custom click handler - `.searchit` - Triggers table search with field/value **Table Formatter Patterns:** - `Table.api.formatter.image` / `.images` - Image display - `Table.api.formatter.datetime` - Date formatting - `Table.api.formatter.status` - Status badge - `Table.api.formatter.normal` - Generic label with color - `Table.api.formatter.search` - Clickable search link - `Table.api.formatter.operate` - Action buttons (edit/del) - `Table.api.formatter.toggle` - Toggle switch - `Table.api.formatter.flag` / `.label` - Multi-value flags ## CSS Class Naming **Convention:** Bootstrap 3 + AdminLTE + FastAdmin extensions **CSS Source:** Less files compiled to CSS - `public/assets/less/backend.less` -> `public/assets/css/backend.css` - `public/assets/less/frontend.less` -> `public/assets/css/frontend.css` - `public/assets/less/bootstrap.less` -> `public/assets/css/bootstrap.css` - `public/assets/css/fastadmin.css` - FastAdmin base overrides - `public/assets/css/index.css` - Index page styles - `public/assets/css/user.css` - User center styles **Common Panel Pattern:** ```html
{:build_heading()}
{:build_toolbar('refresh,edit,del')}
``` ## Error Handling **Controller Layer:** - `$this->error($message)` - Returns error, terminates execution - `$this->success($message, $data)` - Returns success response - Backend: throws `HttpResponseException` internally - API: sets HTTP status codes (401/403) via header **Transaction Pattern:** ```php Db::startTrans(); try { // database operations Db::commit(); } catch (ValidateException|PDOException|Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result === false) { $this->error(__('No rows were inserted')); } $this->success(); ``` **Model Event Hooks:** ```php protected static function init() { self::beforeWrite(function ($row) { $changed = $row->getChangedData(); if (isset($changed['password'])) { $salt = \fast\Random::alnum(); $row->password = \app\common\library\Auth::instance()->getEncryptPassword($changed['password'], $salt); $row->salt = $salt; } }); } ``` ## Logging **Framework:** ThinkPHP built-in logging - Configurable driver (file, etc.) in `application/config.php` - Custom log library: `application/common/library/Log.php` - Admin log behavior: `application/admin/behavior/AdminLog.php` - auto-logs admin actions to database ## Comments **JSDoc/TSDoc Pattern:** - All public/protected methods have PHPDoc with Chinese descriptions - Controller methods: brief action description - API methods: `@ApiMethod` + `@ApiParams` annotations for API doc generation **Controller Class Doc:** ```php /** * 会员管理 * * @icon fa fa-user */ class User extends Backend ``` **Method Doc:** ```php /** * 会员登录 * * @ApiMethod (POST) * @ApiParams (name="account", type="string", required=true, description="账号") * @ApiParams (name="password", type="string", required=true, description="密码") */ public function login() ``` **Model Doc:** ```php /** * 会员模型 * @method static mixed getByUsername($str) 通过用户名查询用户 * @method static mixed getByNickname($str) 通过昵称查询用户 */ class User extends Model ``` **Function Doc:** ```php /** * 将字节转换为可读文本 * @param int $size 大小 * @param string $delimiter 分隔符 * @param int $precision 小数位数 * @return string */ function format_bytes($size, $delimiter = '', $precision = 2) ``` ## Function Design **Size:** - Controller action methods: 10-30 lines (when extending Backend, most logic is in trait) - Backend trait methods: 30-80 lines (`index`, `add`, `edit`, `del`) - Import method: ~130 lines (`application/admin/library/traits/Backend.php`) - Library methods: 10-50 lines - Global functions: 5-30 lines **Parameters:** - Primary key as method parameter: `edit($ids = null)`, `del($ids = "")` - POST form data: `$this->request->post('row/a')` (returns associative array) - Query params: `$this->request->request('key')`, `$this->request->get('filter')` **Return Values:** - AJAX: `json(['total' => N, 'rows' => [...]])` - Non-AJAX: `$this->view->fetch()` - API: Always JSON via `$this->success()` / `$this->error()` ## Module Design **PHP Exports:** - No explicit exports; PSR-4 autoloading handles class loading - Language files: `return [...]` array - Config files: `return [...]` array **JS Exports:** - AMD `define()` with `return Controller;` pattern - No ES modules, no CommonJS **No Barrel Files:** Direct imports throughout, no index/re-export files **Addon Structure:** ``` addons/{addon_name}/ ├── config.php # Addon configuration (returns array) ├── Command.php # Main addon class (extends \fast\Addons) ├── controller/Index.php # Addon controller ├── library/Output.php # Addon library └── info.ini # Addon metadata ``` ## Where to Add New Code **New Admin Feature (CRUD):** - Controller: `application/admin/controller/{module}/{Name}.php` - Model: `application/admin/model/{Name}.php` - Validate: `application/admin/validate/{Name}.php` - Language: `application/admin/lang/zh-cn/{module}/{name}.php` - View: `application/admin/view/{module}/{name}/index.html`, `add.html`, `edit.html` - JS: `public/assets/js/backend/{module}/{name}.js` - Or auto-generate: `php think crud --table={table} --controller={name}` **New API Endpoint:** - Controller: `application/api/controller/{Name}.php` (extends `app\common\controller\Api`) - Response via `$this->success()` / `$this->error()` - Add `@ApiMethod` and `@ApiParams` annotations for doc generation **New Common Library:** - Location: `application/common/library/{Name}.php` - Use singleton `instance()` pattern if needed **New Global Function:** - Location: `application/common.php` - Wrap in `if (!function_exists('name')) { ... }` **New Module-Level Function:** - Location: `application/{module}/common.php` **New Model:** - Shared model: `application/common/model/{Name}.php` - Admin-only model: `application/admin/model/{Name}.php` - Extend `think\Model`, set `$name`, `$autoWriteTimestamp`, `$createTime`, `$updateTime` **New Validate:** - Location: `application/admin/validate/{Name}.php` - Extend `think\Validate`, define `$rule`, `$field`, `$scene` --- *Convention analysis: 2026-04-21*