# 从零打造 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 文档

相关推荐
jerryinwuhan9 小时前
基于各城市站点流量的复合功能比较
开发语言·php
独隅13 小时前
CodeX + Visual Studio Code 联动的全面指南
开发语言·php
爱吃小白兔的猫14 小时前
LPA算法详解:一种近线性时间的图社区发现方法
开发语言·php
棒棒的唐16 小时前
在国内安装yii2新项目由于网络超时安装失败的解决办法
php·yii2
xinhuanjieyi20 小时前
Deprecated: Directive ‘track_errors‘ is deprecated in Unknown on line 0
php
棒棒的唐1 天前
Composer国内镜像配置全指南:加速依赖下载
php·composer
神净讨魔7651 天前
【php】老旧PHP项目(PHP 5.6)本地环境搭建与踩坑记录
php
在角落发呆1 天前
DTU 数据转发服务器:工业物联网的隐形桥梁
开发语言·php
古城小栈1 天前
宝塔面板部署 ThinkPHP6 后端
php