23 KiB
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
.csswith.min.cssvariants - Less: lowercase
.less(source files inpublic/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(inapplication/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 incomposer.json - Fast tools namespace:
fast\maps toextend/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
<?phpfollowed by a blank line - Blank line after namespace declaration
- Blank line after last
usestatement before class declaration
PHP Version: PHP >= 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, orbiome.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:
namespace app\admin\controller;usestatements for app classes:use app\common\controller\Backend;usestatements for ThinkPHP classes:use think\Db;,use think\Config;,use think\Exception;usestatements for vendor/third-party classes:use fast\Tree;- No grouping separators between use statement categories
Example from application/admin/controller/user/User.php:
namespace app\admin\controller\user;
use app\common\controller\Backend;
use app\common\library\Auth;
Example from application/common/controller/Backend.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/incomposer.json - ThinkPHP autoload handles
app\namespace mapping automatically fast\namespace handled by ThinkPHP custom autoloader ->extend/fast/
ThinkPHP Conventions
Model Table Naming:
$nameproperty defines the table name without prefix. Example:protected $name = 'user';maps to{prefix}user- Table prefix configured in
application/config.phpdatabase 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->modelinstance in_initialize() - Assigns view data:
$this->view->assign("statusList", ...)
Magic Model Methods:
- Dynamic finders via
@methodPHPDoc annotations getBy{Field}()auto-generated by ThinkPHP for any column- Example:
@method static mixed getByUsername($str)inapplication/common/model/User.php - Usage:
\app\common\model\User::getByMobile($mobile)
AJAX Detection Pattern:
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 filteringadd()- Create recordedit($ids)- Update recorddel($ids)- Soft deletedestroy($ids)- Hard delete (from recycle bin)restore($ids)- Restore from recycle binmulti($ids)- Batch updaterecyclebin()- Recycle bin listimport()- Excel/CSV importselectpage()- 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:
/**
* 会员管理
*
* @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 crudcommand - 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': <timestamp>, '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\Authfor 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 modulesen- only exists forapplication/index/lang/en/index.php
File Format:
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 tozh-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 <head> 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:
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 asrequire-backend.min.js)public/assets/js/require-frontend.js- Frontend module config (minified)public/assets/js/require-table.js- Bootstrap-table wrapper modulepublic/assets/js/require-form.js- Form handling modulepublic/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 handlersFrontend- Frontend: captcha sending, touch swipe for sidebarTable- Bootstrap-table wrapper:api.init(),api.bindevent(), formatters, eventsForm- Form validation and submission:api.bindevent()Template- art-template JS template engineMoment- Date/time formatting (withmoment/locale/zh-cn)
Global Objects (set on window):
window.Backend- Backend namespacewindow.Frontend- Frontend namespacewindow.Config- Server-rendered config objectwindow.Toastr- Toastr notification librarywindow.Layer- Layer popup library (layui-based)window.Table- (via require) Table modulewindow.Form- (via require) Form modulewindow.Template- Template enginewindow.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 displayTable.api.formatter.datetime- Date formattingTable.api.formatter.status- Status badgeTable.api.formatter.normal- Generic label with colorTable.api.formatter.search- Clickable search linkTable.api.formatter.operate- Action buttons (edit/del)Table.api.formatter.toggle- Toggle switchTable.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.csspublic/assets/less/frontend.less->public/assets/css/frontend.csspublic/assets/less/bootstrap.less->public/assets/css/bootstrap.csspublic/assets/css/fastadmin.css- FastAdmin base overridespublic/assets/css/index.css- Index page stylespublic/assets/css/user.css- User center styles
Common Panel Pattern:
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="toolbar" class="toolbar">
{:build_toolbar('refresh,edit,del')}
</div>
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('user/user/edit')}"
data-operate-del="{:$auth->check('user/user/del')}"
width="100%">
</table>
</div>
</div>
Error Handling
Controller Layer:
$this->error($message)- Returns error, terminates execution$this->success($message, $data)- Returns success response- Backend: throws
HttpResponseExceptioninternally - API: sets HTTP status codes (401/403) via header
Transaction Pattern:
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:
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+@ApiParamsannotations for API doc generation
Controller Class Doc:
/**
* 会员管理
*
* @icon fa fa-user
*/
class User extends Backend
Method Doc:
/**
* 会员登录
*
* @ApiMethod (POST)
* @ApiParams (name="account", type="string", required=true, description="账号")
* @ApiParams (name="password", type="string", required=true, description="密码")
*/
public function login()
Model Doc:
/**
* 会员模型
* @method static mixed getByUsername($str) 通过用户名查询用户
* @method static mixed getByNickname($str) 通过昵称查询用户
*/
class User extends Model
Function Doc:
/**
* 将字节转换为可读文本
* @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()withreturn 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(extendsapp\common\controller\Api) - Response via
$this->success()/$this->error() - Add
@ApiMethodand@ApiParamsannotations 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