辅助工具类(`Mtphp\Support\*`)
平台开发中反复手写的“纯工具”已统一实现于 mtphp/Support/ 下,零依赖、纯 PHP、不依赖任何扩展,可离线安装即用。它们彼此独立,既可在框架里直接用,也可脱离框架单独 require 后使用。
触发词:唯一 ID、UUID、雪花 ID、业务流水号、XML 互转、数据脱敏、树形、IP、限流、中文大写金额、CSV、重试。
28.1 Str —— 字符串
静态类 Mtphp\Support\Str:
| 方法 | 说明 |
|---|---|
uuid4() / uuid7(?\DateTimeInterface) | UUID v4(随机)/ v7(48 位毫秒时间戳前缀,时间有序,适合做主键,避免索引碎片) |
ulid() | 26 位 Crockford Base32、字典序可排序 |
random($len=16, $alphabet=null) | 随机字符串(默认字母+数字) |
camel/snake/kebab/studly | 命名风格互转 |
contains/startsWith/endsWith | 子串 / 前后缀判断(支持数组) |
limit($s,$n,$end='...') | 安全截断(按字符数,不掉多字节) |
replaceFirst/replaceLast | 替换首次 / 末次出现 |
before/after | 取分隔符前 / 后内容 |
slug($title,$sep='-') | URL 友好 slug(仅保留字母数字,中文会被移除) |
mask($s,$start,$end,$mask='*') | 保留前 $start 与后 $end 个字符,掩码中间段 |
isUuid/isUlid/isJson | 类型判断 |
Str::uuid7(); // 36 位时间有序 UUID,推荐做主键
Str::camel('hello_world'); // helloWorld
Str::mask('13800001111', 3, 4); // 138****1111
28.2 Id —— 唯一数 / 序号
静态类 Mtphp\Support\Id(Str 的 uuid 委托于此,避免重复实现):
| 方法 | 说明 |
|---|---|
uuid4() / uuid7() / ulid() | 同 Str |
nanoid($size=21) | URL 安全随机串(Crockford 风格字母表) |
shortId($length=12) | Base62 短码 |
snowflake($workerId=null,$datacenterId=null) | 分布式 64 位 ID(41 位时间戳 + 10 位 worker/datacenter + 12 位序列),返回 int |
serial($prefix='',$seq=0,$dateFormat='Ymd',$width=6) | 业务流水号 ORD-20261015-000001 |
Id::snowflake(1, 1); // 分布式场景,需稳定 workerId
Id::serial('ORD-', 1); // ORD-20261015-000001(前缀+日期+零填充序号)
28.3 Arr —— 数组
静态类 Mtphp\Support\Arr:点号路径读写、dot/undot 展平还原、only/except、divide、flatten、sortBy、crossJoin、shuffle(可选种子,便于测试复现)、pluck、isAssoc、wrap、forget。
Arr::get($data, 'user.profile.name', '默认');
Arr::dot(['a'=>['b'=>1]]); // ['a.b'=>1]
Arr::crossJoin([1,2],[3,4]); // [[1,3],[1,4],[2,3],[2,4]]
28.4 Xml —— XML 与数组互转
静态类 Mtphp\Support\Xml(基于内置 SimpleXML + DOMDocument):
parse($xmlString)/parseFile($path)→ 数组。约定:元素属性进@attributes,文本进@value,同名多子元素进列表,CDATA 进文本。toXml($array, $root='root', $config=[])→ 字符串。约定:@attributes为属性、@value/#text为文本、@cdata为 CDATA、值为数字索引数组时同名元素重复出现。$config支持encoding/pretty。
$data = Xml::parse($xmlString); // 微信/支付/第三方开放平台回调直接转数组
$xml = Xml::toXml(['item'=>[['id'=>1],['id'=>2]]], 'root'); // 重复 <item>
28.5 Mask —— 数据脱敏
静态类 Mtphp\Support\Mask(隐私合规展示):phone/email/name/idCard/bankCard,以及通用 mask()。
Mask::phone('13800001111'); // 138****1111
Mask::email('zhangsan@example.com'); // z*******@example.com
Mask::name('张三丰'); // 张*丰
Mask::idCard('110101199001011234'); // 110101********1234
Mask::bankCard('6222021234567890'); // **** **** **** 7890
28.6 Tree —— 树形结构
静态类 Mtphp\Support\Tree:build(扁平列表→嵌套树)、flatten(树→扁平,去 children)、ancestors(祖先)、descendants(后代)、maxDepth。常用于部门 / 分类 / 菜单 / 权限树。
$tree = Tree::build($flatList, 'id', 'parent_id', 'children');
$desc = Tree::descendants($flatList, $rootId);
28.7 Filesystem —— 文件 / 路径
静态类 Mtphp\Support\Filesystem:join(路径拼接,保留绝对前导 /)、normalize(反斜杠转正、合并连续斜杠)、mimeFromExt、humanSize(1.2 MB)、ensureDir、get、put(自动建父目录)、tempName、delete、copy、move。
Filesystem::join('/var', 'www/', 'file.txt'); // /var/www/file.txt
Filesystem::humanSize(1234567); // 1.18 MB
28.8 Date —— 日期时间
静态类 Mtphp\Support\Date(纯包 DateTimeImmutable,不引入 Carbon):format、humanize(“刚刚”/“3分钟前”/“2天前”)、startOfDay/endOfDay、between、diffInSeconds/Minutes/Hours/Days、isToday/isPast/isFuture、add。
Date::humanize(strtotime('-3 minutes')); // 3分钟前
Date::startOfDay(time()); // 当日 00:00:00 时间戳
28.9 Csv —— CSV 读写
静态类 Mtphp\Support\Csv:parse/parseFile(首行作表头→关联数组列表)、toCsv(@header 显式表头,或自动取首行键)。
$rows = Csv::parse($csvString); // 后台导入
$csv = Csv::toCsv($list); // 后台导出
28.10 Number —— 数字 / 金额
静态类 Mtphp\Support\Number:thousands(千分位)、currency、percent、safeDivide(分母为 0 返回默认)、wan(万 / 亿单位)、rmbUpperCase(中文大写金额)。
Number::rmbUpperCase(1234.56); // 壹仟贰佰叁拾肆圆伍角陆分
Number::wan(123456789); // 1.23亿
Number::currency(1234.5); // ¥1,234.50
28.11 Hash —— 哈希 / 签名 / 口令
静态类 Mtphp\Support\Hash:hmac、bcrypt/verify(口令)、token(随机令牌)、sign/verifySign(载荷签名 + 可选有效期)、signedQuery/verifySignedQuery(签名查询串,用于临时免登录链接)。
$hash = Hash::bcrypt('password'); Hash::verify('password', $hash);
$url = '/download?'.Hash::signedQuery(['file'=>$fid], $secret, 3600);
28.12 Encoding / Ip / Url / Html
Mtphp\Support\Encoding:base64UrlEncode/Decode(URL 安全)、htmlEntities(防 XSS 转义)。Mtphp\Support\Ip:realClientIp(穿透X-Forwarded-For/X-Real-IP)、cidrMatch(IP 白/黑名单,支持 IPv4/IPv6)、isPrivate/isPublic、toLong/fromLong。Mtphp\Support\Url:buildQuery、parse、current、full、asset、isValid。Mtphp\Support\Html:escape(转义)、stripTags、excerpt(去标签 + 截断摘要)。
28.13 RateLimiter —— 通用限流
Mtphp\Support\RateLimiter(从 Throttle 中间件抽离,基于缓存驱动的固定窗口):tooManyAttempts、attempts、hit、clear、attempt(一键式)。Throttle 中间件内部已改用它。
$limiter = new RateLimiter(cache());
if ($limiter->tooManyAttempts('sms:'.$ip, 5, 60)) { abort(429, '发送过于频繁'); }
$limiter->hit('sms:'.$ip, 60);
28.14 Macroable —— 运行时扩展
Mtphp\Support\Macroable trait 已应用于 Str、Arr、Collection:可在不改动框架源码的情况下动态挂载方法。
Str::macro('rev', fn (string $s) => strrev($s));
Str::rev('abc'); // cba
28.15 全局助手 retry()
mtphp/helpers.php 新增全局函数 retry($times, $callback, $delayMs=0, $catch=[\Throwable::class]):最多尝试 $times 次,仅当抛出 $catch 所列类型时重试(其余异常直接抛出),可选毫秒退避;回调接收当前尝试次数(0 起)。
$resp = retry(3, fn () => http('https://api')->get('/x'), 200);
28.16 校验规则扩展(中国)
Validator 内置了三条带校验位的中国规则,无需再用 extend() 自实现:
mobile_cn—— 中国大陆手机号(1[3-9]开头共 11 位);id_card—— 18 位身份证(含校验位);credit_code—— 18 位统一社会信用代码(GB32100 校验)。
Validator::make($input, ['mobile'=>'required|mobile_cn','id'=>'required|id_card'])->validate();
以上工具均配套零依赖单测
tests/SupportUtilsTest.php(32 用例 / 147 断言),php tests/run.php SupportUtilsTest单独跑;全量php tests/run.php也已通过。
29. 集成胶水层(Storage / JWT / 统一 API 响应)
真实业务里最高频、却最该“框架统一掉”的三块——对象存储、JWT 鉴权、API 响应信封——mtphp 现已内置,零依赖、可插拔。
29.1 统一存储抽象 Storage
屏蔽本地磁盘与对象存储(七牛 / 阿里云 OSS / 腾讯云 COS)的差异,业务层只依赖接口。
// 默认盘(config/storage.php 的 default)
storage()->put('avatars/1.jpg', $binary);
$content = storage()->get('avatars/1.jpg');
$url = storage()->url('avatars/1.jpg'); // 公开访问 URL
// 指定盘
storage('qiniu')->put('x.jpg', $binary);
接口 Mtphp\Storage\StorageInterface:put / get / exists / delete / size / mime / url / tempUrl。框架内置 LocalDriver(零依赖);云驱动不在框架内内置(引入各云 SDK 会破坏零依赖),用 StorageManager::extend 注册即可:
use Mtphp\Storage\StorageManager;
StorageManager::extend('qiniu', fn (array $cfg) => new App\Storage\QiniuDriver($cfg));
QiniuDriver 只需实现 StorageInterface(引入对应 composer SDK 后包装),注册后即可 storage('qiniu')->... 使用,业务代码零改动切换。配置 config/storage.php:default + disks[local][root|url]。
29.2 标准 JWT 鉴权
Mtphp\Auth\Jwt 零依赖实现,支持 HS256/384/512(对称)与 RS256/384/512(非对称,私钥签、公钥验),自动校验 exp/nbf/iat。
$token = jwt()->encode(['uid' => 1, 'role' => 'admin']); // 默认 HS256
$p = jwt()->decode($token); // 成功返回载荷,失败 null
// 微服务非对称:私钥签发、公钥校验
$token = jwt()->encode($payload, $privateKeyPem, 'RS256');
$p = jwt()->decode($token, $publicKeyPem, 'RS256');
密钥优先级:显式传入 > config/jwt.secret > config/app.key。中间件 Mtphp\Http\Middleware\JwtAuth 从 Authorization: Bearer <token> 取令牌并挂载到 $request->authUser(),失败时返回 401;按路由/路由组挂载即可(同 AuthApi)。配置 config/jwt.php:secret / alg / ttl。
与既有的无状态 opaque
Token(AuthApi +Mtphp\Auth\Token,支持吊销名单)并存:JWT 适合标准互操作 / 跨服务,opaque Token 适合需要服务端吊销的场景。
29.3 统一 API 响应信封
前后端分离项目几乎每个接口都要返回统一结构的 JSON,框架提供三个助手(code=0 成功,非 0 失败):
return api_success($list, '获取成功'); // {code:0,message,data}
return api_error('参数错误', 1001); // {code:1001,message,data:null} HTTP 400
return api_error('未登录', 401, null, 401); // 第四参为 HTTP 状态码
return api_paginate(DB::table('users')->paginate(15)); // data=当前页, meta={total,per_page,current_page,last_page}
配套零依赖单测
tests/IntegrationGlueTest.php(16 用例 / 43 断言),php tests/run.php IntegrationGlueTest单独跑;全量php tests/run.php也已通过。