Files
2026-04-21 23:02:15 +08:00

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 .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 <?php followed by a blank line
  • Blank line after namespace declaration
  • Blank line after last use statement 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, 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:

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/ 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:

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:

/**
 * 会员管理
 *
 * @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': <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\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:

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

<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 HttpResponseException internally
  • 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 + @ApiParams annotations 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() 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