分类目录归档:PHP

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和静态分析工具可能支持额外的扩展标签,建议根据项目需求选择合适的工具链。

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, '请勿重复请求');
        }

编译php7.1 openssl3.x版本过高?

下载:

wget https://openssl.org/source/openssl-1.1.1u.tar.gz tar -xzvf openssl-1.1.1u.tar.gz cd openssl-1.1.1u

编译安装:

./config --prefix=/usr/local/openssl1.1 --openssldir=/usr/local/openssl1.1/ssl
make -j$(nproc)
make install

临时改环境变量:

export PATH=/usr/local/openssl1.1/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/openssl1.1/lib:$LD_LIBRARY_PATH

configure php:

./configure --with-mysqli --with-pdo-mysql --enable-mbstring --enable-fpm --with-gd=/usr --with-curl --with-mcrypt --with-openssl --with-freetype-dir --with-zlib --with-jpeg-dir --with-png-dir --with-xpm-dir --enable-pcntl --with-gmp --enable-zip --enable-bcmath --with-openssl=/usr/local/openssl1.1/ --with-openssl-dir=/usr/local/openssl1.1/

编译安装:(略)

php switch 不加 break 会怎么样?

在PHP中,switch语句如果不加break会导致”case穿透”现象。具体表现如下:

当匹配到某个case后,如果没有break语句,代码会继续执行后续所有case中的语句,直到遇到break或switch结束

这种特性有时会被故意利用来实现多个case共享同一段代码的逻辑

示例说明:

switch ($value) {
  case 1:
    echo "这是1";
    // 这里没有break
  case 2:
    echo "这是2";
    break;
  case 3:
    echo "这是3";
    break;
}

当$value=1时,输出会是:
“这是1这是2”

因为匹配到case 1后,没有break阻止,所以继续执行了case 2的代码。

建议:除非有特殊需求,否则每个case后都应该加上break语句以避免意外行为。

PHP安全的获取ip

    public static function getIpX(): ?string {
        [$ip] = self::getIp();
        return $ip;
    }

    public static function getIp(): array {
        $ip0 = $ip = $_SERVER['REMOTE_ADDR'] ?? null;
        if (in_array($ip, [
            '10.29.185.7', '127.0.0.1', '172.17.0.1', '172.31.242.237', # 可信IP列表
        ])) {
            $ip1 = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
            if ($ip1) {
                $ip0 = $ip1;
                $ip = explode(',', $ip1)[0];
            }
        }
        if (!$ip) {
            $ip = $_SERVER['REMOTE_ADDR'] ?? null;
        }
        return [$ip, $ip0];
    }