# 从零打造 Composer 依赖包:ThinkPHP 项目开发实战指南

前言

在 PHP 开发中,Composer 已成为现代 PHP 生态的基石。学会编写自己的 Composer 包,不仅是团队代码复用的利器,更是理解现代 PHP 框架设计思路的必经之路。本文以一个实际场景出发,带你完成从「创建包」到「ThinkPHP 项目中使用」的全流程。


一、Composer 是什么

Composer 是 PHP 的依赖管理工具,类似 Node.js 的 npm、Python 的 pip。它的核心能力:

  • 声明依赖composer.json 声明项目需要哪些包
  • 自动加载 :PSR-4 自动加载规范,一行 require 即可使用
  • 版本控制 :语义化版本(Semantic Versioning),^1.0.0~2.1.0

Composer 常用命令

bash 复制代码
# 初始化项目,生成 composer.json
composer init

# 安装依赖
composer install

# 更新依赖
composer update

# 搜索包
composer search 包名

# 列出已安装的包
composer show

二、从零创建 Composer 包

2.1 项目结构

我们在thinkphp的vendor创建一个示例包 /easy-utils,包含常用工具函数集合:

复制代码
easy-utils/
├── src/
│   └── EasyUtils/
│       ├── Str.php           # 字符串工具
│       └── Arr.php           # 数组工具
├── tests/
│   └── EasyUtils/
│       ├── StrTest.php
│       └── ArrTest.php
├── composer.json             # 包描述文件
└── README.md

2.2 编写 composer.json

json 复制代码
{
    "name": "easy-utils",
    "description": "PHP 常用工具函数集合",
    "type": "library",
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "EasyUtils\\": "src/EasyUtils/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "EasyUtils\\Tests\\": "tests/EasyUtils/"
        }
    },
    "require": {
        "php": ">=7.4"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.0"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

关键字段说明:

字段 说明
name 包名,格式为 vendor/package,全局唯一
autoload.psr-4 PSR-4 自动加载规范,命名空间前缀 => 目录
minimum-stability stable / dev,最低稳定版要求
require.php PHP 版本要求

2.3 编写工具类

src/EasyUtils/Str.php

php 复制代码
<?php
namespace EasyUtils;

/**
 * 字符串工具类
 */
class Str
{
    /**
     * 生成随机字符串
     *
     * @param int $length 长度
     * @param string $chars 可用字符集
     * @return string
     */
    public static function random(int $length = 16, string $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): string
    {
        $result = '';
        $charsLen = strlen($chars) - 1;
        for ($i = 0; $i < $length; $i++) {
            $result .= $chars[mt_rand(0, $charsLen)];
        }
        return $result;
    }

    /**
     * 隐藏手机号中间4位
     *
     * @param string $phone 手机号
     * @return string 138****5678
     */
    public static function maskPhone(string $phone): string
    {
        return preg_replace('/(\d{3})\d{4}(\d{4})/', '$1****$2', $phone);
    }

    /**
     * 截取字符串(支持中文)
     *
     * @param string $string 原始字符串
     * @param int $length 截取长度
     * @param string $suffix 超过长度的后缀
     * @return string
     */
    public static function truncate(string $string, int $length = 100, string $suffix = '...'): string
    {
        if (mb_strlen($string, 'utf-8') <= $length) {
            return $string;
        }
        return mb_substr($string, 0, $length, 'utf-8') . $suffix;
    }
}

src/EasyUtils/Arr.php

php 复制代码
<?php
namespace EasyUtils;

/**
 * 数组工具类
 */
class Arr
{
    /**
     * 从数组中获取值,支持点号语法
     *
     * @param array $array
     * @param string $key 例如 'user.profile.name'
     * @param mixed $default 默认值
     * @return mixed
     */
    public static function get(array $array, string $key, $default = null)
    {
        $keys = explode('.', $key);
        $value = $array;

        foreach ($keys as $k) {
            if (!is_array($value) || !array_key_exists($k, $value)) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }

    /**
     * 将数组转为带缩进的字符串(用于调试)
     *
     * @param array $array
     * @param int $indent 缩进空格数
     * @return string
     */
    public static function toString(array $array, int $indent = 0): string
    {
        $lines = [];
        foreach ($array as $key => $value) {
            $prefix = str_repeat(' ', $indent);
            if (is_array($value)) {
                $lines[] = "{$prefix}{$key}:";
                $lines[] = self::toString($value, $indent + 4);
            } else {
                $lines[] = "{$prefix}{$key}: " . var_export($value, true);
            }
        }
        return implode("\n", $lines);
    }
}

调试

在vendor/composer/autoload_psr4.php中添加一行

php 复制代码
RealToken\\' => array($vendorDir . '/RealToken/src')

在vendor/composer/autoload_static.php中

在 public static $prefixLengthsPsr4 下添加

php 复制代码
        'E' =>
        array (
            'EasyUtils\\' => 11,
        ),
        
在  public static $prefixDirsPsr4 下添加
```php 
        'EasyUtils\\' =>
        array (
            0 => __DIR__ . '/..' . '/RealToken/src',
        ),

2.4 发布到 Packagist(可选)

发布到 Packagist 后,全世界都可以通过 composer require 安装你的包:

bash 复制代码
# 1. 在 GitHub 创建仓库并推送代码
git init
git add .
git commit -m "Initial commit"
git remote add origin git@github.com:dengjianping/easy-utils.git
git push -u origin master

# 2. 登录 https://packagist.org,用 GitHub 账号登录
# 3. 点击 "Submit Package",填入仓库地址
# 4. 验证通过后,即可使用
composer require dengjianping/easy-utils

如果包未审核通过,也可以通过私有仓库方式引用:

json 复制代码
{
    "repositories": [
        {
            "type": "vcs",
            "url": "git@github.com:dengjianping/easy-utils.git"
        }
    ],
    "require": {
        "dengjianping/easy-utils": "dev-master"
    }
}

三、ThinkPHP 项目中使用 Composer 包

3.1 创建 ThinkPHP 项目

bash 复制代码
# 使用 Composer 创建 ThinkPHP 项目
composer create-project topthink/think my-tp-app

# 进入项目目录
cd my-tp-app

# 目录结构
my-tp-app/
├── app/                    # 应用目录
│   └── controller/         # 控制器
├── config/                 # 配置文件
├── public/                 # 入口文件
├── route/                  # 路由定义
├── think                   # 命令行工具
├── vendor/                 # Composer 依赖目录
└── composer.json           # 依赖声明文件

3.2 安装我们的包

bash 复制代码
# 方法一:通过 Packagist(发布后)
composer require easy-utils

# 方法二:手动指定仓库(未发布时)
# 编辑 composer.json,加入:
composer config repositories.easy-utils vcs git@github.com:dengjianping/easy-utils.git
composer require dengjianping/easy-utils:dev-master

3.3 在控制器中使用

app/controller/Demo.php

php 复制代码
<?php
namespace app\controller;

use EasyUtils\Str;
use EasyUtils\Arr;

class Demo
{
    /**
     * 字符串工具示例
     */
    public function strDemo()
    {
        // 生成随机字符串
        $token = Str::random(32);
        dump("随机Token: " . $token);

        // 隐藏手机号
        $phone = Str::maskPhone('13812345678');
        dump("手机号脱敏: " . $phone);

        // 截取字符串
        $intro = Str::truncate('这是一段很长的文字介绍,用于演示字符串截取功能。', 10);
        dump("截取结果: " . $intro);
    }

    /**
     * 数组工具示例
     */
    public function arrDemo()
    {
        $user = [
            'name' => '张三',
            'age' => 28,
            'profile' => [
                'city' => '北京',
                'skill' => ['PHP', 'Go', 'Vue']
            ]
        ];

        // 使用点号语法获取嵌套值
        $city = Arr::get($user, 'profile.city');
        $skill = Arr::get($user, 'profile.skill');
        $avatar = Arr::get($user, 'profile.avatar', '/default.png'); // 默认值

        dump("城市: " . $city);
        dump("技能: " . implode(', ', $skill));
        dump("头像(默认): " . $avatar);

        // 转为可读字符串
        $str = Arr::toString($user);
        dump("数组内容:\n" . $str);
    }
}

3.4 在服务中使用(进阶)

为了更好地与 ThinkPHP 融合,可以创建服务类将工具包接入框架:

app/service/EasyUtilsService.php

php 复制代码
<?php
namespace app\service;

use EasyUtils\Str;
use EasyUtils\Arr;
use think\Service;

/**
 * EasyUtils 服务类
 * 注册到框架服务中,与 ThinkPHP 生命周期集成
 */
class EasyUtilsService extends Service
{
    public function register()
    {
        // 可以在这里添加框架级别的初始化逻辑
        // 例如:配置文件加载、中间件注册等
    }

    public function boot()
    {
        // 应用启动时执行的逻辑
    }
}

app/provider.php --- 注册服务提供器:

php 复制代码
<?php
use app\service\EasyUtilsService;

return [
    'EasyUtils' => EasyUtilsService::class,
];

四、在 ThinkPHP 中直接开发自定义包

如果你的工具类仅用于当前项目,不需要独立发布,可以直接在应用目录下开发:

4.1 目录结构

复制代码
my-tp-app/
├── app/
│   └── common/              # 公共函数库
│       └── util/
│           ├── Str.php
│           └── Arr.php
└── composer.json

4.2 修改 composer.json

json 复制代码
{
    "name": "my-tp-app",
    "type": "project",
    "autoload": {
        "psr-4": {
            "app\\\": "app/",
            "app\\common\\util\\": "app/common/util/"
        }
    }
}

更新自动加载:

bash 复制代码
composer dump-autoload

4.3 完整项目示例:用户管理

app/model/User.php

php 复制代码
<?php
namespace app\model;

use think\Model;
use app\common\util\Str as StrUtil;
use app\common\util\Arr as ArrUtil;

class User extends Model
{
    protected $name = 'user';

    /**
     * 创建用户时自动处理
     */
    public static function createUser(array $data): User
    {
        // 生成随机盐值
        $salt = StrUtil::random(6);
        $data['salt'] = $salt;

        // 密码加盐哈希
        $data['password'] = md5($data['password'] . $salt);

        // 脱敏手机号
        $data['phone'] = StrUtil::maskPhone($data['phone']);

        return self::create($data);
    }

    /**
     * 获取用户信息(带默认值安全访问)
     */
    public static function getUserInfo(int $id): array
    {
        $user = self::find($id);
        if (!$user) {
            return [];
        }

        return [
            'name'   => ArrUtil::get($user->toArray(), 'name', '匿名用户'),
            'phone'  => ArrUtil::get($user->toArray(), 'phone', ''),
            'city'   => ArrUtil::get($user->toArray(), 'city', '未知'),
        ];
    }
}

五、常见问题与最佳实践

5.1 常见问题

Q:composer install 和 composer update 的区别?

  • composer install:安装 composer.lock 中记录的精确版本(锁定版本)
  • composer update:更新到 composer.json 允许的最新版本

生产环境建议使用 composer install,保证部署版本与开发一致。

Q:更新单个包?

bash 复制代码
composer update dengjianping/easy-utils

Q:清理 Composer 缓存?

bash 复制代码
composer clearcache

5.2 最佳实践

实践 说明
语义化版本 主版本号不兼容变更,次版本号新增功能,修订号 Bug 修复
PSR-4 规范 命名空间与目录结构对应,便于自动加载
最小依赖 只引入真正需要的包,避免依赖膨胀
写单元测试 至少覆盖核心函数,便于回归验证
代码签名 发布时使用 composer sign 对包签名,保证完整性
使用 .gitignore 忽略 vendor/ 目录(通过 Composer 安装恢复)

5.3 composer.json 最佳模板

json 复制代码
{
    "name": "vendor/package",
    "description": "简短描述包的功能",
    "type": "project",
    "license": "MIT",
    "authors": [
        {
            "name": "作者名",
            "email": "author@example.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "Vendor\\Package\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Vendor\\Package\\Tests\\": "tests/"
        }
    },
    "require": {
        "php": ">=7.4"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.0"
    },
    "scripts": {
        "test": "phpunit",
        "post-autoload-dump": [
            "@php think clear"
        ]
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "config": {
        "preferred-install": "dist"
    }
}

六、总结

本文从实际需求出发,完成了以下内容:

  1. 理解 Composer --- PHP 依赖管理的核心工具
  2. 创建 Composer 包 --- composer.json 配置 + PSR-4 自动加载 + 工具类编写
  3. 发布到 Packagist --- 让全世界的项目都能使用你的包
  4. 在 ThinkPHP 中集成 --- 通过 composer require 引入并实际使用
  5. 进阶整合 --- 服务类注册、模型集成、最佳实践

核心逻辑是:包提供能力,框架整合能力。掌握了这套模式,你既可以复用社区优质包,也可以将自己的积累封装为可复用的资产。


参考资料:Composer 官方文档 | Packagist | ThinkPHP6 文档

相关推荐
两个人的幸福8 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo10 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack10 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820711 天前
PHP 扩展——从入门到理解
php
鹏仔先生11 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下12 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip12 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒12 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog25012 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis12 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel