PHPDoc 语法大全

我来为您整理一份完整的 PHPDoc 语法大全。

PHPDoc 语法大全

1. 基本结构

/**
 * 简短描述(一行)
 * 
 * 详细描述(多行)
 * 可以包含更多信息
 * 
 * @tag 标签内容
 */

2. 常用标签

@param – 参数说明

/**
 * @param string $name 参数描述
 * @param int $age 年龄
 * @param array|null $data 可选数据
 * @param mixed $value 混合类型
 */

@return – 返回值

/**
 * @return string 返回用户名称
 * @return bool 返回布尔值
 * @return void 无返回值
 * @return array|object 多种返回类型
 */

@var – 变量类型

/**
 * @var string 用户名
 * @var int 年龄
 * @var array 数据数组
 */
public $name;

@throws / @exception – 异常

/**
 * @throws InvalidArgumentException 参数无效时抛出
 * @throws RuntimeException 运行时错误
 */

3. 类型声明

基本类型

  • string – 字符串
  • int / integer – 整数
  • float / double – 浮点数
  • bool / boolean – 布尔值
  • array – 数组
  • object – 对象
  • callable – 可调用
  • resource – 资源
  • mixed – 混合类型
  • void – 无类型
  • null – null值

复合类型

/**
 * @param string|int $id ID可以是字符串或整数
 * @return array|null 返回数组或null
 * @var object|false 对象或false
 */

数组类型

/**
 * @param string[] $names 字符串数组
 * @return int[][] 二维整数数组
 * @var array<string, int> 键为字符串,值为整数的数组
 */

泛型/集合类型(PHPStan/Psalm风格)

/**
 * @param list<string> $items 列表
 * @return array<int, User> 用户数组
 * @var Collection<User> 用户集合
 */

4. 类相关标签

@package – 包名

/**
 * @package App\Models
 */

@subpackage – 子包

/**
 * @subpackage Controllers
 */

@author – 作者

/**
 * @author John Doe <john@example.com>
 */

@copyright – 版权

/**
 * @copyright 2024 Company Name
 */

@license – 许可证

/**
 * @license MIT
 */

@version – 版本

/**
 * @version 1.0.0
 */

@since – 起始版本

/**
 * @since 2.0.0
 */

@deprecated – 已废弃

/**
 * @deprecated 3.0.0 使用 newMethod() 替代
 */

5. 方法相关标签

@abstract – 抽象方法

/**
 * @abstract
 */

@final – 最终方法

/**
 * @final
 */

@static – 静态方法

/**
 * @static
 */

@access – 访问权限

/**
 * @access public
 * @access private
 * @access protected
 */

6. 继承和引用

@inheritDoc – 继承父类文档

/**
 * {@inheritDoc}
 */

@see – 参考

/**
 * @see MyClass::myMethod()
 * @see https://example.com
 * @see ClassName
 */

@link – 链接

/**
 * @link https://example.com 描述
 */

@uses – 使用

/**
 * @uses MyClass::method()
 */

@used-by – 被使用

/**
 * @used-by AnotherClass::anotherMethod()
 */

7. 属性和常量

@property – 魔术属性

/**
 * @property string $name 用户名
 * @property-read int $id 只读ID
 * @property-write string $password 只写密码
 */

@method – 魔术方法

/**
 * @method static User find(int $id) 查找用户
 * @method string getName() 获取名称
 */

@const – 常量

/**
 * @const int MAX_SIZE 最大尺寸
 */

8. 命名空间和类引用

@category – 分类

/**
 * @category Database
 */

@todo – 待办

/**
 * @todo 实现缓存功能
 */

@fixme – 需要修复

/**
 * @fixme 修复内存泄漏问题
 */

@internal – 内部使用

/**
 * @internal 仅供内部使用
 */

9. 完整示例

<?php

namespace App\Services;

use App\Models\User;
use Exception;

/**
 * 用户服务类
 * 
 * 提供用户相关的业务逻辑处理
 * 
 * @package App\Services
 * @author John Doe <john@example.com>
 * @version 1.0.0
 * @since 2024-01-01
 * @copyright 2024 Company
 * @license MIT
 */
class UserService
{
    /**
     * @var UserRepository 用户仓库
     */
    private $userRepository;

    /**
     * @var int 最大尝试次数
     * @const
     */
    const MAX_ATTEMPTS = 3;

    /**
     * 构造函数
     * 
     * @param UserRepository $userRepository 用户仓库实例
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * 根据ID获取用户信息
     * 
     * 从数据库中查询用户详细信息,包括关联的角色和权限
     * 
     * @param int $id 用户ID,必须大于0
     * @param bool $withRelations 是否加载关联数据,默认true
     * @return User|null 返回用户对象,未找到返回null
     * @throws InvalidArgumentException 当ID无效时抛出
     * @throws DatabaseException 数据库查询失败时抛出
     * @see UserRepository::findById()
     * @link https://example.com/docs/user-api
     * @since 1.0.0
     * @deprecated 2.0.0 使用 getUserById() 替代
     * 
     * @example
     * ```
php
     * $service = new UserService($repository);
     * $user = $service->findUser(123, true);
     * ```
     */
    public function findUser(int $id, bool $withRelations = true): ?User
    {
        if ($id <= 0) {
            throw new InvalidArgumentException("Invalid user ID: {$id}");
        }
        
        return $this->userRepository->findById($id, $withRelations);
    }

    /**
     * 批量获取用户列表
     * 
     * @param array $ids 用户ID数组
     * @param int $page 页码,从1开始
     * @param int $perPage 每页数量
     * @return array{
     *     data: User[],
     *     total: int,
     *     page: int,
     *     perPage: int
     * } 分页结果
     * @throws Exception 查询失败时抛出
     */
    public function getUsers(array $ids, int $page = 1, int $perPage = 20): array
    {
        // 实现代码
    }

    /**
     * 创建新用户
     * 
     * @param array<string, mixed> $data 用户数据
     *   - name: string 用户名(必填)
     *   - email: string 邮箱(必填)
     *   - age: int|null 年龄(可选)
     * @return User 创建的用户对象
     * @throws ValidationException 数据验证失败
     * @throws DuplicateException 用户已存在
     */
    public function createUser(array $data): User
    {
        // 实现代码
    }

    /**
     * 魔术方法:动态获取属性
     * 
     * @property-read string $version 版本号
     * @property-read array $config 配置信息
     * 
     * @param string $name 属性名
     * @return mixed 属性值
     * @throws BadMethodCallException 属性不存在
     */
    public function __get(string $name)
    {
        // 实现代码
    }

    /**
     * 魔术方法:动态调用方法
     * 
     * @method static User findById(int $id) 通过ID查找
     * @method array findAll() 查找所有用户
     * 
     * @param string $name 方法名
     * @param array $arguments 参数
     * @return mixed
     */
    public function __call(string $name, array $arguments)
    {
        // 实现代码
    }
}

10. IDE支持的工具

  • PhpStorm – 完全支持PHPDoc
  • VS Code + PHP Intelephense
  • PHPDocumentor – 生成文档工具
  • PHPStan – 静态分析
  • Psalm – 静态分析
  • phpcs + Squiz标准

11. 最佳实践

  1. ✅ 始终使用 /** */ 而不是 /* */
  2. ✅ 保持描述简洁明了
  3. ✅ 为所有公共API添加文档
  4. ✅ 使用正确的类型声明
  5. ✅ 包含示例代码(复杂功能)
  6. ✅ 标注废弃方法和替代方案
  7. ❌ 不要过度文档化显而易见的代码
  8. ❌ 避免冗余信息

这份PHPDoc语法大全涵盖了日常开发中常用的所有标签和用法。不同的IDE和静态分析工具可能支持额外的扩展标签,建议根据项目需求选择合适的工具链。

systemd 配置目录介绍

systemd 配置目录详解

主要配置目录

1. /etc/systemd/system/ (用户自定义配置)

  • 用途: 存放用户和系统管理员创建的服务单元
  • 优先级: 最高(会覆盖 /usr/lib/systemd/system/ 中的同名文件)
  • 典型应用:
    • 自定义服务配置
    • 服务覆盖配置(override)
    • 服务的符号链接
# 查看目录内容示例
ls -la /etc/systemd/system/

2. /usr/lib/systemd/system/ (系统默认配置)

  • 用途: 软件包安装时提供的默认服务单元文件
  • 管理方式: 由包管理器(apt/yum/dnf)维护
  • 注意: 不应直接修改此目录的文件
# 查看已安装服务的默认配置
ls -la /usr/lib/systemd/system/

3. /run/systemd/system/ (运行时配置)

  • 用途: 运行时动态生成的单元文件
  • 特点: 系统重启后内容会丢失
  • 优先级: 高于 /etc/systemd/system/

配置文件类型

服务单元(.service

定义系统服务的行为:

[Unit]
Description=My Application Service
After=network.target

[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/php /var/www/html/artisan queue:work
Restart=always

[Install]
WantedBy=multi-user.target

路径监控单元(.path

监控文件或目录变化:

[Unit]
Description=Watch config file changes

[Path]
PathChanged=/var/www/html/config.php
Unit=myapp.service

[Install]
WantedBy=multi-user.target

定时器单元(.timer

定时任务(类似 cron):

[Unit]
Description=Run backup daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

目标单元(.target

分组管理服务(类似运行级别):

  • multi-user.target – 多用户文本模式
  • graphical.target – 图形界面模式
  • network.target – 网络就绪

常用管理命令

# 重载 systemd 配置(修改配置后必须执行)
sudo systemctl daemon-reload

# 启用服务(开机启动)
sudo systemctl enable myapp.service

# 启动服务
sudo systemctl start myapp.service

# 停止服务
sudo systemctl stop myapp.service

# 重启服务
sudo systemctl restart myapp.service

# 查看服务状态
sudo systemctl status myapp.service

# 查看服务日志
sudo journalctl -u myapp.service -f

# 禁用服务
sudo systemctl disable myapp.service

# 列出所有失败的服务
sudo systemctl --failed

# 查看服务依赖关系
systemctl list-dependencies myapp.service

# 查看启用的所有服务
systemctl list-unit-files --type=service --state=enabled

配置优先级顺序

/run/systemd/system/     (最高优先级)
    ↓
/etc/systemd/system/     (用户配置,推荐修改此处)
    ↓
/usr/lib/systemd/system/ (最低优先级,软件包默认配置)

实际应用场景

场景 1:创建自定义服务

# 1. 创建服务文件
sudo vim /etc/systemd/system/myapp.service

# 2. 重载配置
sudo systemctl daemon-reload

# 3. 启用并启动服务
sudo systemctl enable --now myapp.service

场景 2:覆盖现有服务配置

# 创建覆盖配置(不修改原文件)
sudo systemctl edit nginx

# 这会在 /etc/systemd/system/nginx.service.d/override.conf
# 添加自定义配置片段

场景 3:调试服务配置

# 测试服务配置是否正确
systemd-analyze verify /etc/systemd/system/myapp.service

# 查看服务启动时间分析
systemd-analyze blame

结合您的 WordPress 项目

基于您的项目结构,如果要为 WordPress 或相关服务创建 systemd 配置:

示例:WordPress Cron 服务

# /etc/systemd/system/wp-cron.service
[Unit]
Description=WordPress Cron Handler
After=network.target mysql.service

[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/bin/php wp-cron.php

[Install]
WantedBy=multi-user.target

示例:监控配置文件变化

# /etc/systemd/system/wp-config-monitor.path
[Unit]
Description=Monitor wp-config.php changes

[Path]
PathChanged=/var/www/html/wp-config.php
Unit=php-fpm.service

[Install]
WantedBy=multi-user.target

需要我帮您创建具体的 systemd 配置文件吗?

MySQL 长度较大的VARCHAR索引限制说明

我来为您详细说明 MySQL 中长度较大的 VARCHAR 字段的索引限制问题。

MySQL VARCHAR 索引限制说明

1. 核心限制规则

MySQL 对索引长度有以下主要限制:

InnoDB 存储引擎

  • 最大索引长度: 767 字节 (MySQL 5.6 及更早版本)
  • 最大索引长度: 3072 字节 (MySQL 5.7+, 需启用 innodb_large_prefix)
  • 行格式要求: 必须使用 DYNAMICCOMPRESSED 行格式

MyISAM 存储引擎

  • 最大索引长度: 1000 字节

2. 字符集对索引长度的影响

不同字符集下,VARCHAR 字段能创建的最大索引长度不同:

单字节字符集 (latin1):
  - 767 字节限制:VARCHAR(767)
  - 3072 字节限制:VARCHAR(3072)

多字节字符集 (utf8mb4):
  - 每个字符最多 4 字节
  - 767 字节限制:VARCHAR(191)  [767 ÷ 4 = 191.75]
  - 3072 字节限制:VARCHAR(768)  [3072 ÷ 4 = 768]

3. 实际应用场景

问题场景

-- ❌ 在 utf8mb4 字符集下会报错
CREATE TABLE users (
    name VARCHAR(500),
    INDEX idx_name (name)  -- 错误:索引长度 2000 字节 > 767 字节
);

-- ✅ 安全的做法
CREATE TABLE users (
    name VARCHAR(191),
    INDEX idx_name (name)  -- 正确:191 × 4 = 764 字节 < 767 字节
);

解决方案

方案 1: 使用前缀索引

-- 只对字段的前 N 个字符创建索引
CREATE TABLE articles (
    title VARCHAR(500),
    INDEX idx_title_prefix (title(191))  -- 只索引前 191 个字符
);

方案 2: 升级配置 (MySQL 5.7+)

-- 1. 设置 innodb_large_prefix
SET GLOBAL innodb_large_prefix = ON;

-- 2. 使用 DYNAMIC 行格式
CREATE TABLE users (
    name VARCHAR(500),
    INDEX idx_name (name)
) ROW_FORMAT=DYNAMIC;

-- 3. 或者修改现有表
ALTER TABLE users ROW_FORMAT=DYNAMIC;

方案 3: 使用全文索引 (适合长文本搜索)

CREATE TABLE articles (
    content TEXT,
    FULLTEXT INDEX ft_content (content)
);

4. 查看当前配置

-- 查看 innodb_large_prefix 设置
SHOW VARIABLES LIKE 'innodb_large_prefix';

-- 查看表的行格式
SHOW TABLE STATUS LIKE 'your_table_name';

-- 查看数据库字符集
SHOW VARIABLES LIKE 'character_set_database';

-- 查看索引长度限制
SHOW VARIABLES LIKE 'innodb_default_row_format';

5. 最佳实践建议

  1. 推荐长度: 在 utf8mb4 字符集下,VARCHAR 索引字段最好不超过 191 个字符
  2. 前缀索引: 对于较长的字段,使用前缀索引可以平衡性能和空间
  3. 避免过度索引: 过长的索引会占用更多存储空间,降低写入性能
  4. 考虑替代方案:
    • 使用哈希索引 (适合精确匹配)
    • 使用全文索引 (适合模糊搜索)
    • 使用外部搜索引擎 (如 Elasticsearch)

6. 常见错误示例

Error 1071: Specified key was too long; max key length is 767 bytes
Error 1170: BLOB/TEXT column 'xxx' used in key specification without a key length

如果您需要针对具体的业务场景选择合适的索引策略,请告诉我您的具体需求(如字段用途、查询模式等),我可以给出更精准的建议。

mysql 表分区的不同是否会影响正常使用?

MySQL 表分区的不同通常不会影响正常使用,但有一些需要注意的地方:

对正常使用的影响

基本不影响的操作

  • CRUD 操作:增删改查操作完全透明,应用层无需感知分区存在
  • 索引使用:分区表可以正常创建和使用索引
  • 事务支持:分区表完全支持事务
  • 外键约束:支持(但有局限性)

⚠️ 可能影响的情况

  1. 查询性能

    • ✅ 如果查询条件包含分区键,会进行分区裁剪,只扫描相关分区,性能更好
    • ⚠️ 如果查询不包含分区键,会扫描所有分区,可能比非分区表更慢
  2. 唯一键限制

    • ⚠️ 分区表中,主键或唯一键必须包含分区键字段
    • 这是 MySQL 的强制要求
  3. 外键约束

    • ⚠️ 分区表不支持作为子表被外键引用
    • ⚠️ 分区表也不能引用其他表的外键
  4. 维护操作

    • 某些 DDL 操作可能需要更长时间
    • 备份恢复策略可能需要调整

建议

如果你考虑使用表分区,需要确保:

  • 合理选择分区键(通常是时间、ID 等)
  • 查询条件尽量包含分区键
  • 主键设计要包含分区键

MySQL导入远程数据库

mysqldump -h 123.3.3.3 --compress --single-transaction --quick -u OOOK -pPP01 OOOK | mysql -h 127.0.0.3 -u OOOK -pPP01 OOOK
127.0.0.3 适用于连接 OOOK@%
--quick 用于省内存
--compress 用于压缩传输
--single-transaction 防止锁库

basename丢失中文问题解决

# 输入:
var_dump(basename("xf/中文qq.zip"));
# 输出:
string(12) "qq.zip"
# 期待的输出为:
string(12) "中文qq.zip"
可以发现中文部分丢失,使用下面的方法解决,或者手动拆分字符串
解决办法:
setlocale(LC_ALL, 'zh_CN.UTF-8');
var_dump(basename("xf/中文qq.zip"));
// ❌ 默认情况下,中文可能丢失
setlocale(LC_ALL, 'C'); // 或 'POSIX'
var_dump(basename('/path/文件.pdf')); // Linux下可能输出乱码 string(4) ".pdf"

// ✅ 设置正确的 locale 后
setlocale(LC_ALL, 'zh_CN.UTF-8');
var_dump(basename('/path/文件.pdf')); // 正确输出:string(10) "文件.pdf"

php swoole 队列、异步、协程并发curl请求

  public function curls()
    {
        cli_set_process_title(__FILE__ . ':curls');
        $file_mtime = $this->file_mtime();
        $st = time();

        error_reporting(E_ALL);
        ini_set('swoole.display_errors', 'On');

        \Swoole\Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]);
        \Swoole\Coroutine\run(function () use ($st, $file_mtime) {
            $channel = new \Swoole\Coroutine\Channel(1);
            $exit = null;
            \Swoole\Coroutine::create(function () use ($channel, $st, $file_mtime, &$exit) {
                $redis = \services\iRedis::getInstance();
                while (true) {
                    $task = $redis->blPop('curl_queue', 5);
                    if (!empty($task)) $channel->push($task[1]);
                    if ($file_mtime != $this->file_mtime()) break;
                    if (time() - $st > 3600) break;
                }
                $exit = true;
            });
            for ($i = 0; $i < 5; $i++) {
                \Swoole\Coroutine::create(function () use ($channel, &$exit) {
                    while (true) {
                        $task = $channel->pop(5);
                        if (!empty($task)) {
                            $json = json_decode($task, true);
                            if ($json) {
                                $method = $json['method'];
                                $uri = $json['uri'];
                                $headers = $json['headers'] ?? [];
                                $body = $json['body'] ?? null;
                                $result = \services\Tools::curl($method, $uri, $body, $headers);
                            }
                        }
                        if ($exit && $channel->isEmpty()) break;
                    }
                });
            }
        });
    }

微信PHP解密

openssl 实现:

        $result = openssl_decrypt(base64_decode($data),
            "AES-128-CBC",
            base64_decode($key),
            OPENSSL_RAW_DATA,
            base64_decode($iv));
        var_dump($result);

mcrypt 实现


        $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
        //用密钥key、初始化向量初始化
        mcrypt_generic_init($module, base64_decode($key), base64_decode($iv));
        //**执行解密**(得到带有PKCS#7填充的半原文,所以要去除填充)
        $result = mdecrypt_generic($module, base64_decode($data));
        //清理工作与关闭解密
        mcrypt_generic_deinit($module);
        mcrypt_module_close($module);
        //去除填充
        $lastByte = substr($result, -1);
        $result = substr($result, 0, strlen($result) - ord($lastByte));
        var_dump($result);

PHP单机文件排队锁


class Locker
{

    private static array $lockers = [];

    /**
     * 获取锁
     * @param string $key 锁的唯一标识
     * @param int $timeout 超时时间(秒),0表示无限等待
     * @return bool 是否获取到锁
     */
    public static function wait(string $key, int $timeout = 0): bool
    {
        $file = sys_get_temp_dir() . "/.lock_" . md5($key) . ".tmp";
        $start_time = time();

        while (true) {
            // 检查是否超时
            if ($timeout > 0 && (time() - $start_time) >= $timeout) {
                return false;
            }

            // 尝试打开文件
            $fp = @fopen($file, "c+");
            if (!$fp) {
                usleep(10000); // 等待10毫秒
                continue;
            }

            // 尝试获取锁
            if (flock($fp, LOCK_EX | LOCK_NB)) {
                // 获取锁成功
                self::$lockers[$key] = $fp;
                return true;
            } else {
                // 获取锁失败,关闭文件句柄
                fclose($fp);
                usleep(10000); // 等待10毫秒后重试
            }
        }
    }

    /**
     * 释放锁
     * @param string $key 锁的唯一标识
     * @return bool 是否成功释放
     */
    public static function release(string $key): bool
    {
        if (!isset(self::$lockers[$key])) {
            return false;
        }

        $fp = self::$lockers[$key];
        if (is_resource($fp)) {
            flock($fp, LOCK_UN); // 释放文件锁
            fclose($fp);         // 关闭文件句柄
        }

        unset(self::$lockers[$key]);
        return true;
    }

    /**
     * 检查是否持有某个锁
     * @param string $key 锁的唯一标识
     * @return bool 是否持有锁
     */
    public static function isLocked(string $key): bool
    {
        return isset(self::$lockers[$key]) && is_resource(self::$lockers[$key]);
    }

    /**
     * 析构时释放所有锁
     */
    public function __destruct()
    {
        foreach (array_keys(self::$lockers) as $key) {
            self::release($key);
        }
    }
}

使用:
        if (!Locker::wait(md5("key"), 3)) {
            recordlog("3秒内获取不到锁返回提示 请勿重复请求  ");
            # 3秒内获取不到锁返回提示
            exit_json(0, '请勿重复请求');
        }