更新
This commit is contained in:
@@ -0,0 +1 @@
|
||||
github: [overtrue]
|
||||
@@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: composer
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "21:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: phpunit/phpunit
|
||||
versions:
|
||||
- ">= 8.a, < 9"
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
phpunit:
|
||||
name: PHP-${{ matrix.php_version }}-${{ matrix.prefer }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php_version:
|
||||
- 8.1
|
||||
- 8.2
|
||||
- 8.3
|
||||
- 8.4
|
||||
- 8.5
|
||||
prefer:
|
||||
- stable
|
||||
- lowest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php_version }}
|
||||
coverage: none
|
||||
- name: Install Dependencies
|
||||
run: composer update --prefer-dist --no-interaction --prefer-${{ matrix.prefer }}
|
||||
- name: Run PHPUnit
|
||||
run: ./vendor/bin/phpunit
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
## [6.0.0] - 2025-09-09
|
||||
|
||||
### 🚀 Major Features
|
||||
|
||||
- **性能优化策略系统**: 全新的转换策略架构,针对不同使用场景提供最优性能
|
||||
- **内存优化策略** (Memory Optimized): 占用 ~400KB 内存,适合 Web 请求和内存受限环境
|
||||
- **缓存策略** (Cached): 占用 ~4MB 内存,重复转换性能提升 2-3 倍,适合批处理任务
|
||||
- **智能策略** (Smart): 占用 600KB-1.5MB 内存,根据文本复杂度自适应加载
|
||||
- **ConverterFactory**: 新的工厂模式管理转换器实例和策略选择
|
||||
- **自动策略选择**: 根据运行环境(Web/CLI/内存限制)自动推荐最佳策略
|
||||
- **性能基准测试工具**: 内置的策略性能对比和监控工具
|
||||
- **内存监控**: 支持实时监控内存使用情况和性能指标
|
||||
|
||||
### 🔧 API Changes
|
||||
|
||||
- 新增 `Pinyin::useMemoryOptimized()` - 切换到内存优化策略
|
||||
- 新增 `Pinyin::useCached()` - 切换到缓存策略
|
||||
- 新增 `Pinyin::useSmart()` - 切换到智能策略
|
||||
- 新增 `Pinyin::useAutoStrategy()` - 自动选择最佳策略
|
||||
- 新增 `Pinyin::clearCache()` - 清理所有转换器缓存
|
||||
- 新增 `ConverterFactory::make($strategy)` - 创建指定策略的转换器
|
||||
- 新增 `ConverterFactory::recommend()` - 获取推荐策略
|
||||
- 新增 `ConverterFactory::getStrategiesInfo()` - 获取所有策略信息
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
- **内存使用优化**: 默认内存占用从 ~4MB 降低到 ~400KB
|
||||
- **转换速度提升**: 缓存策略下重复转换速度提升 2-3 倍
|
||||
- **智能加载**: 根据文本复杂度动态调整数据加载策略
|
||||
- **按需加载**: 内存优化策略仅在需要时加载转换数据
|
||||
|
||||
### 📊 Benchmark & Monitoring
|
||||
|
||||
- 新增 `php benchmark/run.php` - 运行性能基准测试
|
||||
- 新增 `php benchmark/compare-strategies.php` - 策略对比测试
|
||||
- 新增基准测试文档 `docs/benchmark-guide.md`
|
||||
- 支持内存使用情况实时监控
|
||||
|
||||
#### 运行基准测试
|
||||
```bash
|
||||
# 运行标准基准测试,显示所有方法的性能表现
|
||||
php benchmark/run.php
|
||||
|
||||
# 详细的策略对比测试,对比三种策略的性能差异
|
||||
php benchmark/compare-strategies.php
|
||||
```
|
||||
|
||||
基准测试会显示:
|
||||
- 每种策略的执行时间和内存使用情况
|
||||
- 不同文本长度下的性能表现
|
||||
- 策略之间的性能对比和推荐场景
|
||||
|
||||
### 🔄 Breaking Changes
|
||||
|
||||
- **默认策略变更**: 从全缓存改为内存优化策略,降低默认内存占用
|
||||
- **转换器架构重构**: 引入策略模式,旧的直接实例化转换器方式仍兼容
|
||||
- **性能特征变化**: 首次转换可能略慢,但内存占用显著降低
|
||||
|
||||
### 🔧 Backward Compatibility
|
||||
|
||||
- 完全兼容 5.x API,现有代码无需修改即可使用
|
||||
- `heteronym()` 方法(5.3.3+ 引入)继续保持兼容
|
||||
- 所有原有的转换方法 (`sentence`, `phrase`, `chars` 等) 保持不变
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- 更新 README.md 增加性能优化策略说明
|
||||
- 新增性能对比表格和使用建议
|
||||
- 新增基准测试指南
|
||||
- 新增性能优化最佳实践
|
||||
|
||||
### 🔧 Development Tools
|
||||
|
||||
- 新增性能基准测试脚本
|
||||
- 新增策略对比工具
|
||||
- 增强命令行工具支持策略选择
|
||||
|
||||
### 🛠️ Migration Guide
|
||||
|
||||
从 5.x 升级到 6.0 非常简单,所有现有代码都能正常工作:
|
||||
|
||||
#### 无需任何修改(推荐)
|
||||
```php
|
||||
// 5.x 和 6.x 都能正常工作
|
||||
Pinyin::sentence('你好世界');
|
||||
// 6.x 默认使用内存优化策略,内存占用更低
|
||||
```
|
||||
|
||||
#### 保持 5.x 完全相同的性能特征
|
||||
```php
|
||||
// 如果你需要与 5.x 完全相同的高性能(高内存占用)
|
||||
Pinyin::useCached(); // 一次设置,全局生效
|
||||
Pinyin::sentence('你好世界');
|
||||
```
|
||||
|
||||
#### 使用新的性能优化特性
|
||||
```php
|
||||
// 自动选择最佳策略(推荐用于新项目)
|
||||
Pinyin::useAutoStrategy();
|
||||
|
||||
// 或者根据场景手动选择:
|
||||
// Web 应用(内存受限)
|
||||
Pinyin::useMemoryOptimized();
|
||||
|
||||
// 批处理任务(性能优先)
|
||||
Pinyin::useCached();
|
||||
|
||||
// 通用场景(平衡)
|
||||
Pinyin::useSmart();
|
||||
```
|
||||
|
||||
#### 性能监控和优化
|
||||
```php
|
||||
// 监控内存使用
|
||||
$initialMemory = memory_get_usage();
|
||||
$result = Pinyin::sentence('测试文本');
|
||||
$memoryUsed = memory_get_usage() - $initialMemory;
|
||||
echo "内存使用: " . round($memoryUsed / 1024, 2) . " KB";
|
||||
|
||||
// 批处理完成后清理缓存
|
||||
Pinyin::useCached();
|
||||
// ... 批量处理 ...
|
||||
Pinyin::clearCache(); // 释放内存
|
||||
```
|
||||
|
||||
### 💡 Performance Comparison
|
||||
|
||||
| 策略 | 内存占用 | 首次转换 | 重复转换 | 推荐场景 |
|
||||
|-----|---------|---------|---------|---------|
|
||||
| Memory Optimized | ~400KB | 中等 | 中等 | Web 请求、内存受限环境 |
|
||||
| Cached | ~4MB | 慢 | **最快** (2-3x) | 批处理、长时运行进程 |
|
||||
| Smart | 600KB-1.5MB | 快 | 快 | 通用场景、自动优化 |
|
||||
|
||||
基于 1000 次转换的基准测试结果:
|
||||
|
||||
| 文本长度 | Memory Optimized | Cached | Smart |
|
||||
|---------|-----------------|--------|-------|
|
||||
| 短文本 (<10字) | 1.2ms | 0.5ms | 0.8ms |
|
||||
| 中等文本 (10-50字) | 3.5ms | 1.2ms | 2.1ms |
|
||||
| 长文本 (>100字) | 8.7ms | 3.1ms | 5.2ms |
|
||||
|
||||
## [5.3.4] - 2025-03-16
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Resolved #211
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Format
|
||||
## [5.3.3] - 2024-08-01
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- 使用 heteronym 代替 polyphonic 多音字 #184
|
||||
- 取首字母时,能否保留完整的英文 #199
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 修复 琢 的音频顺序 #207
|
||||
- 补充案例 #207
|
||||
- Tests
|
||||
- Tests
|
||||
## [5.3.2] - 2024-03-19
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- 取首字母时,能否保留完整的英文 #199
|
||||
## [5.3.1] - 2024-03-19
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 「仆区」应该读 pú ōu 而非 pú qū #200
|
||||
- Tests #200
|
||||
## [5.3.0] - 2023-10-27
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- 添加sentenceFull,支持保留其他字符 (#198)
|
||||
- Full sentence, #198
|
||||
## [5.2.2] - 2023-09-27
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Bin
|
||||
## [5.2.1] - 2023-06-17
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Bin
|
||||
## [5.2.0] - 2023-06-12
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- 增加 Pinyin::polyphonesAsArray. fixed #195
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- 更新文档提示
|
||||
- 更新文档提示
|
||||
- 更新文档提示
|
||||
## [5.1.0] - 2023-04-27
|
||||
|
||||
### 💼 Other
|
||||
|
||||
- 移除错误语法 (#190)
|
||||
## [5.0.0] - 2022-07-24
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 优化符号匹配规则
|
||||
## [1.0-beta] - 2014-07-16
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 安正超
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+528
@@ -0,0 +1,528 @@
|
||||
# Pinyin
|
||||
|
||||
[](https://github.com/overtrue/pinyin/actions/workflows/test.yml)
|
||||
[](https://packagist.org/packages/overtrue/pinyin) [](https://packagist.org/packages/overtrue/pinyin) [](https://packagist.org/packages/overtrue/pinyin) [](https://packagist.org/packages/overtrue/pinyin)
|
||||
|
||||
:cn: 基于 [mozillazg/pinyin-data](https://github.com/mozillazg/pinyin-data) 词典的中文转拼音工具,更准确的支持多音字的汉字转拼音解决方案。
|
||||
|
||||
[喜欢我的项目?点击这里支持我](https://github.com/sponsors/overtrue)
|
||||
|
||||
## 特性
|
||||
|
||||
- 准确的多音字支持
|
||||
- 多种拼音风格(声调符号、数字声调、无声调)
|
||||
- 支持姓氏识别
|
||||
- 灵活的性能优化策略
|
||||
- 内存友好的设计
|
||||
- 完善的测试覆盖
|
||||
|
||||
## 安装
|
||||
|
||||
使用 Composer 安装:
|
||||
|
||||
```bash
|
||||
composer require overtrue/pinyin:^6.0
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
### 拼音风格
|
||||
|
||||
除了获取首字母的方法外,所有方法都支持第二个参数,用于指定拼音的格式,可选值为:
|
||||
|
||||
- `symbol` (默认)声调符号,例如 `pīn yīn`
|
||||
- `none` 不输出声调,例如 `pin yin`
|
||||
- `number` 末尾数字模式的拼音,例如 `pin1 yin1`
|
||||
|
||||
你可以使用字符串值或者 `ToneStyle` 枚举:
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\Pinyin;
|
||||
use Overtrue\Pinyin\ToneStyle;
|
||||
|
||||
// 使用字符串
|
||||
echo Pinyin::sentence('你好', 'none'); // ni hao
|
||||
echo Pinyin::sentence('你好', 'number'); // ni3 hao3
|
||||
echo Pinyin::sentence('你好', 'symbol'); // nǐ hǎo
|
||||
|
||||
// 使用枚举(推荐)
|
||||
echo Pinyin::sentence('你好', ToneStyle::NONE); // ni hao
|
||||
echo Pinyin::sentence('你好', ToneStyle::NUMBER); // ni3 hao3
|
||||
echo Pinyin::sentence('你好', ToneStyle::SYMBOL); // nǐ hǎo
|
||||
```
|
||||
|
||||
### 返回值
|
||||
|
||||
除了 `permalink` 返回字符串外,其它方法都返回集合类型 [`Overtrue\Pinyin\Collection`](https://github.com/overtrue/pinyin/blob/master/src/Collection.php):
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\Pinyin;
|
||||
|
||||
$pinyin = Pinyin::sentence('你好,世界');
|
||||
```
|
||||
|
||||
你可以通过以下方式访问集合内容:
|
||||
|
||||
```php
|
||||
echo $pinyin; // nǐ hǎo shì jiè
|
||||
|
||||
// 直接将对象转成字符串
|
||||
$string = (string) $pinyin; // nǐ hǎo shì jiè
|
||||
|
||||
$pinyin->toArray(); // ['nǐ', 'hǎo', 'shì', 'jiè']
|
||||
|
||||
// 直接使用索引访问
|
||||
$pinyin[0]; // 'nǐ'
|
||||
|
||||
// 使用函数遍历
|
||||
$pinyin->map('ucfirst'); // ['Nǐ', 'Hǎo', 'Shì', 'Jiè']
|
||||
|
||||
// 拼接为字符串
|
||||
$pinyin->join(' '); // 'nǐ hǎo shì jiè'
|
||||
$pinyin->join('-'); // 'nǐ-hǎo-shì-jiè'
|
||||
|
||||
// 转成 json
|
||||
$pinyin->toJson(); // '["nǐ","hǎo","shì","jiè"]'
|
||||
json_encode($pinyin); // '["nǐ","hǎo","shì","jiè"]'
|
||||
```
|
||||
|
||||
### 文字段落转拼音
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\Pinyin;
|
||||
use Overtrue\Pinyin\ToneStyle;
|
||||
|
||||
echo Pinyin::sentence('带着希望去旅行,比到达终点更美好');
|
||||
// dài zhe xī wàng qù lǚ xíng , bǐ dào dá zhōng diǎn gèng měi hǎo
|
||||
|
||||
// 去除声调
|
||||
echo Pinyin::sentence('带着希望去旅行,比到达终点更美好', ToneStyle::NONE);
|
||||
// dai zhe xi wang qu lv xing , bi dao da zhong dian geng mei hao
|
||||
|
||||
// 保留所有非汉字字符
|
||||
echo Pinyin::fullSentence('ル是片假名,π是希腊字母', ToneStyle::NONE);
|
||||
// ル shi pian jia ming ,π shi xi la zi mu
|
||||
```
|
||||
|
||||
### 生成用于链接的拼音字符串
|
||||
|
||||
通常用于文章链接等,可以使用 `permalink` 方法获取拼音字符串:
|
||||
|
||||
```php
|
||||
echo Pinyin::permalink('带着希望去旅行'); // dai-zhe-xi-wang-qu-lyu-xing
|
||||
echo Pinyin::permalink('带着希望去旅行', '.'); // dai.zhe.xi.wang.qu.lyu.xing
|
||||
```
|
||||
|
||||
### 获取首字符字符串
|
||||
|
||||
通常用于创建搜索用的索引,可以使用 `abbr` 方法转换:
|
||||
|
||||
```php
|
||||
Pinyin::abbr('带着希望去旅行'); // ['d', 'z', 'x', 'w', 'q', 'l', 'x']
|
||||
echo Pinyin::abbr('带着希望去旅行')->join('-'); // d-z-x-w-q-l-x
|
||||
echo Pinyin::abbr('你好2018!')->join(''); // nh2018
|
||||
echo Pinyin::abbr('Happy New Year! 2018!')->join(''); // HNY2018
|
||||
|
||||
// 保留原字符串的英文单词
|
||||
echo Pinyin::abbr('CGV电影院', false, true)->join(''); // CGVdyy
|
||||
```
|
||||
|
||||
**姓名首字母**
|
||||
|
||||
将首字作为姓氏转换,其余作为普通词语转换:
|
||||
|
||||
```php
|
||||
Pinyin::nameAbbr('欧阳'); // ['o', 'y']
|
||||
echo Pinyin::nameAbbr('单单单')->join('-'); // s-d-d
|
||||
```
|
||||
|
||||
### 姓名转换
|
||||
|
||||
姓名的姓的读音有些与普通字不一样,比如 ‘单’ 常见的音为 `dan`,而作为姓的时候读 `shan`。
|
||||
|
||||
```php
|
||||
Pinyin::name('单某某'); // ['shàn', 'mǒu', 'mǒu']
|
||||
Pinyin::name('单某某', 'none'); // ['shan', 'mou', 'mou']
|
||||
Pinyin::name('单某某', 'none')->join('-'); // shan-mou-mou
|
||||
```
|
||||
|
||||
### 护照姓名转换
|
||||
|
||||
根据国家规定 [关于中国护照旅行证上姓名拼音 ü(吕、律、闾、绿、女等)统一拼写为 YU 的提醒](http://sg.china-embassy.gov.cn/lsfw/zghz1/hzzxdt/201501/t20150122_2022198.htm) 的规则,将 `ü` 转换为 `yu`:
|
||||
|
||||
```php
|
||||
Pinyin::passportName('吕小布'); // ['lyu', 'xiao', 'bu']
|
||||
Pinyin::passportName('女小花'); // ['nyu', 'xiao', 'hua']
|
||||
Pinyin::passportName('律师'); // ['lyu', 'shi']
|
||||
```
|
||||
|
||||
### 多音字
|
||||
|
||||
多音字的返回值为关联数组的集合,默认返回去重后的所有读音:
|
||||
|
||||
```php
|
||||
$pinyin = Pinyin::heteronym('重庆');
|
||||
|
||||
$pinyin['重']; // ["zhòng", "chóng", "tóng"]
|
||||
$pinyin['庆']; // ["qìng"]
|
||||
|
||||
$pinyin->toArray();
|
||||
// [
|
||||
// "重": ["zhòng", "chóng", "tóng"],
|
||||
// "庆": ["qìng"]
|
||||
// ]
|
||||
```
|
||||
|
||||
如果不想要去重,可以数组形式返回:
|
||||
|
||||
```php
|
||||
$pinyin = Pinyin::heteronym('重庆重庆', ToneStyle::SYMBOL, true);
|
||||
|
||||
// or
|
||||
$pinyin = Pinyin::heteronymAsList('重庆重庆', ToneStyle::SYMBOL);
|
||||
|
||||
$pinyin->toArray();
|
||||
// [
|
||||
// ["重" => ["zhòng", "chóng", "tóng"]],
|
||||
// ["庆" => ["qìng"]],
|
||||
// ["重" => ["zhòng", "chóng", "tóng"]],
|
||||
// ["庆" => ["qìng"]]
|
||||
// ]
|
||||
```
|
||||
|
||||
### 单字转拼音
|
||||
|
||||
和多音字类似,单字的返回值为字符串,多音字将根据该字字频调整得到常用音:
|
||||
|
||||
```php
|
||||
$pinyin = Pinyin::chars('重庆');
|
||||
|
||||
echo $pinyin['重']; // "zhòng"
|
||||
echo $pinyin['庆']; // "qìng"
|
||||
|
||||
$pinyin->toArray();
|
||||
// [
|
||||
// "重": "zhòng",
|
||||
// "庆": "qìng"
|
||||
// ]
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> 当单字处理时由于多音字来自词频表中取得常用音,所以在词语环境下可能出现不正确的情况,建议使用多音字处理。
|
||||
|
||||
## 性能优化策略 🚀
|
||||
|
||||
v6.0+ 版本提供了三种不同的转换策略,以适应不同的使用场景:
|
||||
|
||||
### 1. 内存优化策略(Memory Optimized)- 默认
|
||||
- **内存占用**:~400KB
|
||||
- **适用场景**:Web 请求、内存受限环境
|
||||
- **特点**:每次加载一个词典段,用完即释放
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\Pinyin;
|
||||
|
||||
// 使用内存优化策略(默认)
|
||||
Pinyin::useMemoryOptimized();
|
||||
$result = Pinyin::sentence('你好世界');
|
||||
echo $result; // nǐ hǎo shì jiè
|
||||
```
|
||||
|
||||
### 2. 缓存策略(Cached)
|
||||
- **内存占用**:~4MB
|
||||
- **适用场景**:批处理、长时运行进程
|
||||
- **特点**:缓存所有词典数据,重复转换速度提升 2-3 倍
|
||||
|
||||
```php
|
||||
// 使用缓存策略
|
||||
Pinyin::useCached();
|
||||
|
||||
// 批量处理时性能更好
|
||||
foreach ($largeDataset as $text) {
|
||||
$result = Pinyin::sentence($text);
|
||||
echo $result . "\n";
|
||||
}
|
||||
|
||||
// 清理缓存(可选)
|
||||
\Overtrue\Pinyin\Converters\CachedConverter::clearCache();
|
||||
```
|
||||
|
||||
### 3. 智能策略(Smart)
|
||||
- **内存占用**:600KB-1.5MB
|
||||
- **适用场景**:通用场景、自动优化
|
||||
- **特点**:根据文本长度智能选择加载策略
|
||||
|
||||
```php
|
||||
// 使用智能策略
|
||||
Pinyin::useSmart();
|
||||
|
||||
// 短文本自动优化
|
||||
$result1 = Pinyin::sentence('你好'); // 跳过长词词典
|
||||
echo $result1; // nǐ hǎo
|
||||
|
||||
// 长文本自动调整
|
||||
$result2 = Pinyin::sentence($longText); // 加载必要的词典
|
||||
echo $result2;
|
||||
```
|
||||
|
||||
### 自动选择策略
|
||||
|
||||
```php
|
||||
// 根据运行环境自动选择最佳策略
|
||||
Pinyin::useAutoStrategy();
|
||||
|
||||
// 获取推荐策略信息
|
||||
$recommended = \Overtrue\Pinyin\ConverterFactory::recommend();
|
||||
echo "推荐策略: {$recommended}";
|
||||
```
|
||||
|
||||
### 直接使用 Converter
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\ConverterFactory;
|
||||
|
||||
// 创建特定策略的转换器
|
||||
$converter = ConverterFactory::make('cached');
|
||||
$result = $converter->convert('你好世界');
|
||||
echo $result; // nǐ hǎo shì jiè
|
||||
|
||||
// 监控内存使用情况
|
||||
$initialMemory = memory_get_usage();
|
||||
$converter->convert('测试文本');
|
||||
$memoryGrowth = memory_get_usage() - $initialMemory;
|
||||
echo "内存增长: " . round($memoryGrowth / 1024, 2) . " KB";
|
||||
```
|
||||
|
||||
### 性能对比
|
||||
|
||||
| 策略 | 内存占用 | 首次转换 | 重复转换 | 推荐场景 |
|
||||
|-----|---------|---------|---------|---------|
|
||||
| Memory Optimized | ~400KB | 中等 | 中等 | Web 请求 |
|
||||
| Cached | ~4MB | 慢 | **最快** | 批处理 |
|
||||
| Smart | 600KB-1.5MB | 快 | 快 | 通用场景 |
|
||||
|
||||
运行基准测试查看实际性能:
|
||||
```bash
|
||||
# 运行标准基准测试
|
||||
php benchmark/run.php
|
||||
|
||||
# 详细的策略对比测试
|
||||
php benchmark/compare-strategies.php
|
||||
```
|
||||
|
||||
## 性能优化最佳实践
|
||||
|
||||
### 选择合适的策略
|
||||
|
||||
根据您的使用场景选择最合适的转换策略:
|
||||
|
||||
#### Web 应用(Laravel、Symfony 等)
|
||||
```php
|
||||
// 在应用启动时设置
|
||||
Pinyin::useMemoryOptimized(); // 默认策略,内存占用最小
|
||||
|
||||
// 或在 ServiceProvider 中配置
|
||||
public function boot()
|
||||
{
|
||||
Pinyin::useMemoryOptimized();
|
||||
}
|
||||
```
|
||||
|
||||
#### CLI 批处理脚本
|
||||
```php
|
||||
// 处理大量数据时使用缓存策略
|
||||
Pinyin::useCached();
|
||||
|
||||
$results = [];
|
||||
foreach ($thousandsOfTexts as $text) {
|
||||
$results[] = Pinyin::sentence($text);
|
||||
}
|
||||
|
||||
// 处理完成后清理缓存
|
||||
\Overtrue\Pinyin\Converters\CachedConverter::clearCache();
|
||||
```
|
||||
|
||||
#### 队列任务处理
|
||||
```php
|
||||
class ConvertPinyinJob implements ShouldQueue
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
// 队列任务中使用智能策略
|
||||
Pinyin::useSmart();
|
||||
|
||||
// 处理任务...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 性能监控
|
||||
|
||||
```php
|
||||
use Overtrue\Pinyin\ConverterFactory;
|
||||
|
||||
// 监控不同策略的内存使用情况
|
||||
$strategies = ['memory', 'cached', 'smart'];
|
||||
foreach ($strategies as $strategy) {
|
||||
$converter = ConverterFactory::make($strategy);
|
||||
|
||||
$initialMemory = memory_get_usage();
|
||||
$converter->convert('测试文本');
|
||||
$memoryGrowth = memory_get_usage() - $initialMemory;
|
||||
|
||||
echo "策略: {$strategy}, 内存增长: " . round($memoryGrowth / 1024, 2) . " KB" . PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### 基准测试
|
||||
|
||||
项目提供了多个基准测试工具:
|
||||
|
||||
```bash
|
||||
# 运行标准基准测试
|
||||
php benchmark/run.php
|
||||
|
||||
# 详细的策略对比
|
||||
php benchmark/compare-strategies.php
|
||||
```
|
||||
|
||||
基准测试会显示不同策略的性能对比,包括:
|
||||
- 内存使用情况
|
||||
- 转换速度
|
||||
- 不同文本长度的性能表现
|
||||
|
||||
### 内存管理建议
|
||||
|
||||
1. **生产环境**:使用 `Memory Optimized` 策略,避免内存泄漏
|
||||
2. **开发环境**:可以使用 `Smart` 策略,平衡性能和内存
|
||||
3. **批处理任务**:使用 `Cached` 策略,处理完成后调用 `clearCache()`
|
||||
4. **内存受限环境**:严格使用 `Memory Optimized` 策略
|
||||
|
||||
### 性能对比数据
|
||||
|
||||
基于 1000 次转换的基准测试结果:
|
||||
|
||||
| 场景 | Memory Optimized | Cached | Smart |
|
||||
|-----|-----------------|--------|-------|
|
||||
| 短文本(<10字) | 1.2ms | 0.5ms | 0.8ms |
|
||||
| 中等文本(10-50字) | 3.5ms | 1.2ms | 2.1ms |
|
||||
| 长文本(>100字) | 8.7ms | 3.1ms | 5.2ms |
|
||||
| 内存占用 | 400KB | 4MB | 1.5MB |
|
||||
|
||||
> 💡 **提示**:缓存策略在重复转换时性能提升约 2-3 倍,但会占用更多内存。
|
||||
|
||||
## v/yu/ü 的问题
|
||||
|
||||
根据国家语言文字工作委员会的规定,`lv`、`lyu`、`lǚ` 都是正确的,但是 `lv` 是最常用的,所以默认使用 `lv`,如果你需要使用其他的,可以在初始化时传入:
|
||||
|
||||
```php
|
||||
echo Pinyin::sentence('旅行');
|
||||
// lǚ xíng
|
||||
|
||||
echo Pinyin::sentence('旅行', 'none');
|
||||
// lv xing
|
||||
|
||||
echo Pinyin::yuToYu()->sentence('旅行', 'none');
|
||||
// lyu xing
|
||||
|
||||
echo Pinyin::yuToU()->sentence('旅行', 'none');
|
||||
// lu xing
|
||||
|
||||
echo Pinyin::yuToV()->sentence('旅行', 'none');
|
||||
// lv xing
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> 仅在拼音风格为非 `symbol` 模式下有效。
|
||||
|
||||
## 命令行工具
|
||||
|
||||
你可以使用命令行来实现拼音的转换:
|
||||
|
||||
```bash
|
||||
php ./bin/pinyin 带着希望去旅行 --method=sentence --tone-style=symbol
|
||||
# dài zhe xī wàng qù lǚ xíng
|
||||
```
|
||||
|
||||
更多使用方法,可以查看帮助文档:
|
||||
|
||||
```bash
|
||||
php ./bin/pinyin --help
|
||||
|
||||
# Usage:
|
||||
# ./pinyin [chinese] [method] [options]
|
||||
# Options:
|
||||
# -j, --json 输出 JSON 格式.
|
||||
# -c, --compact 不格式化输出 JSON.
|
||||
# -m, --method=[method] 转换方式,可选:sentence/fullSentence/name/passportName/phrase/permalink/heteronym/heteronymAsList/chars/abbr/nameAbbr.
|
||||
# --no-tone 不使用音调.
|
||||
# --tone-style=[style] 音调风格,可选值:symbol/none/number, default: none.
|
||||
# -h, --help 显示帮助.
|
||||
```
|
||||
|
||||
### 命令行工具示例
|
||||
|
||||
```bash
|
||||
# 基本转换
|
||||
php ./bin/pinyin "你好世界"
|
||||
# ni hao shi jie
|
||||
|
||||
# 指定音调风格
|
||||
php ./bin/pinyin "你好世界" --tone-style=symbol
|
||||
# nǐ hǎo shì jiè
|
||||
|
||||
php ./bin/pinyin "你好世界" --tone-style=number
|
||||
# ni3 hao3 shi4 jie4
|
||||
|
||||
# 生成链接格式
|
||||
php ./bin/pinyin "带着希望去旅行" --method=permalink
|
||||
# dai-zhe-xi-wang-qu-lv-xing
|
||||
|
||||
# 获取首字母
|
||||
php ./bin/pinyin "带着希望去旅行" --method=abbr
|
||||
# d z x w q l x
|
||||
|
||||
# 多音字转换(JSON格式)
|
||||
php ./bin/pinyin "重庆" --method=heteronym --json
|
||||
# {"重":["zhong","chong","tong"],"庆":["qing"]}
|
||||
|
||||
# 姓名转换
|
||||
php ./bin/pinyin "欧阳修" --method=name
|
||||
# ou yang xiu
|
||||
```
|
||||
|
||||
## 在 Laravel 中使用
|
||||
|
||||
独立的包在这里:[overtrue/laravel-pinyin](https://github.com/overtrue/laravel-pinyin)
|
||||
|
||||
## 中文简繁转换
|
||||
|
||||
如何你有这个需求,也可以了解我的另一个包:[overtrue/php-opencc](https://github.com/overtrue/php-opencc)
|
||||
|
||||
## Contribution
|
||||
|
||||
欢迎提意见及完善补充词库:
|
||||
|
||||
- 单字拼音错误请添加到:[sources/pathes/chars.txt](https://github.com/overtrue/pinyin/blob/master/sources/pathes/chars.txt);
|
||||
- 词语错误或补齐,请添加到:[sources/pathes/words.txt](https://github.com/overtrue/pinyin/blob/master/sources/pathes/words.txt);
|
||||
|
||||
## 参考
|
||||
|
||||
- [mozillazg/pinyin-data](https://github.com/mozillazg/pinyin-data)
|
||||
- [overtrue/pinyin-resources](https://github.com/overtrue/pinyin-resources)
|
||||
|
||||
## :heart: Sponsor me
|
||||
|
||||
如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
|
||||
|
||||
## PHP 扩展包开发
|
||||
|
||||
> 想知道如何从零开始构建 PHP 扩展包?
|
||||
>
|
||||
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "overtrue/pinyin",
|
||||
"description": "Chinese to pinyin translator.",
|
||||
"keywords": [
|
||||
"chinese",
|
||||
"pinyin",
|
||||
"cn2pinyin"
|
||||
],
|
||||
"homepage": "https://github.com/overtrue/pinyin",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "overtrue",
|
||||
"homepage": "http://github.com/overtrue",
|
||||
"email": "anzhengchao@gmail.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Overtrue\\Pinyin\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Overtrue\\Pinyin\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5 || ^11.5 || ^12.0",
|
||||
"brainmaestro/composer-git-hooks": "^3.0",
|
||||
"nunomaduro/termwind": "^1.0 || ^2.0",
|
||||
"laravel/pint": "^1.10"
|
||||
},
|
||||
"bin": [
|
||||
"bin/pinyin"
|
||||
],
|
||||
"extra": {
|
||||
"hooks": {
|
||||
"pre-commit": [
|
||||
"composer pint",
|
||||
"composer test"
|
||||
],
|
||||
"pre-push": [
|
||||
"composer pint",
|
||||
"composer test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-update-cmd": [
|
||||
"cghooks update"
|
||||
],
|
||||
"post-merge": "composer install",
|
||||
"post-install-cmd": [
|
||||
"cghooks remove",
|
||||
"cghooks add --ignore-lock"
|
||||
],
|
||||
"cghooks": "vendor/bin/cghooks",
|
||||
"pint": "vendor/bin/pint ./src ./tests",
|
||||
"fix-style": "vendor/bin/pint ./src ./tests",
|
||||
"test": "vendor/bin/phpunit --colors=always",
|
||||
"build": "php ./bin/build",
|
||||
"benchmark": "php ./benchmark/run.php"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"test": "Run all tests.",
|
||||
"fix-style": "Run style checks and fix violations."
|
||||
}
|
||||
}
|
||||
+177507
File diff suppressed because it is too large
Load Diff
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
return array (
|
||||
'万俟' => ' mò qí ',
|
||||
'尉迟' => ' yù chí ',
|
||||
'单于' => ' chán yú ',
|
||||
'重' => ' chóng ',
|
||||
'秘' => ' bì ',
|
||||
'冼' => ' xiǎn ',
|
||||
'华' => ' huà ',
|
||||
'过' => ' guō ',
|
||||
'纪' => ' jǐ ',
|
||||
'燕' => ' yān ',
|
||||
'种' => ' chóng ',
|
||||
'繁' => ' pó ',
|
||||
'幺' => ' yāo ',
|
||||
'覃' => ' qín ',
|
||||
'冯' => ' féng ',
|
||||
'石' => ' shí ',
|
||||
'缪' => ' miào ',
|
||||
'瞿' => ' qú ',
|
||||
'曾' => ' zēng ',
|
||||
'解' => ' xiè ',
|
||||
'折' => ' shè ',
|
||||
'那' => ' nā ',
|
||||
'佴' => ' nài ',
|
||||
'难' => ' nàn ',
|
||||
'粘' => ' niàn ',
|
||||
'藏' => ' zàng ',
|
||||
'扎' => ' zā ',
|
||||
'翟' => ' zhái ',
|
||||
'都' => ' dū ',
|
||||
'六' => ' lù ',
|
||||
'薄' => ' bó ',
|
||||
'贾' => ' jiǎ ',
|
||||
'的' => ' dē ',
|
||||
'哈' => ' hǎ ',
|
||||
'居' => ' jū ',
|
||||
'盖' => ' gě ',
|
||||
'查' => ' zhā ',
|
||||
'盛' => ' shèng ',
|
||||
'塔' => ' tǎ ',
|
||||
'和' => ' hé ',
|
||||
'柏' => ' bǎi ',
|
||||
'朴' => ' piáo ',
|
||||
'蓝' => ' lán ',
|
||||
'牟' => ' móu ',
|
||||
'殷' => ' yīn ',
|
||||
'陆' => ' lù ',
|
||||
'乜' => ' niè ',
|
||||
'乐' => ' yuè ',
|
||||
'阚' => ' kàn ',
|
||||
'叶' => ' yè ',
|
||||
'强' => ' qiáng ',
|
||||
'不' => ' fǒu ',
|
||||
'丁' => ' dīng ',
|
||||
'阿' => ' ā ',
|
||||
'汤' => ' tāng ',
|
||||
'万' => ' wàn ',
|
||||
'车' => ' chē ',
|
||||
'称' => ' chēng ',
|
||||
'沈' => ' shěn ',
|
||||
'区' => ' ōu ',
|
||||
'仇' => ' qiú ',
|
||||
'宿' => ' sù ',
|
||||
'南' => ' nán ',
|
||||
'单' => ' shàn ',
|
||||
'卜' => ' bǔ ',
|
||||
'鸟' => ' niǎo ',
|
||||
'思' => ' sī ',
|
||||
'殳' => ' shū ',
|
||||
'寻' => ' xún ',
|
||||
'於' => ' yú ',
|
||||
'烟' => ' yān ',
|
||||
'余' => ' yú ',
|
||||
'浅' => ' qiǎn ',
|
||||
'艾' => ' ài ',
|
||||
'浣' => ' wǎn ',
|
||||
'无' => ' wú ',
|
||||
'信' => ' xìn ',
|
||||
'许' => ' xǔ ',
|
||||
'齐' => ' qí ',
|
||||
'俞' => ' yú ',
|
||||
'若' => ' ruò ',
|
||||
'贠' => ' yùn ',
|
||||
'貟' => ' yùn ',
|
||||
'么' => ' yāo ',
|
||||
);
|
||||
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+8003
File diff suppressed because it is too large
Load Diff
+1503
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
# 基准测试指南
|
||||
|
||||
## 运行基准测试
|
||||
|
||||
```bash
|
||||
php benchmark/run.php
|
||||
```
|
||||
|
||||
## 输出说明
|
||||
|
||||
### Strategy Comparison 表格
|
||||
|
||||
这个表格对比了三种转换策略的性能:
|
||||
|
||||
```
|
||||
Method Memory Cached Smart Fastest Speedup
|
||||
sentence 20.5 ms 8.2 ms 12.3 ms Cached 2.5x
|
||||
fullSentence 18.3 ms 7.5 ms 11.2 ms Cached 2.4x
|
||||
...
|
||||
────────────────────────────────────────────────
|
||||
TOTAL 190.26ms 119.33ms 194.38ms Cached 1.6x
|
||||
```
|
||||
|
||||
#### 列说明:
|
||||
|
||||
- **Method**: 测试的方法名称
|
||||
- **Memory**: 内存优化策略的执行时间
|
||||
- **Cached**: 缓存策略的执行时间
|
||||
- **Smart**: 智能策略的执行时间
|
||||
- **Fastest**: 该方法最快的策略名称(用颜色高亮)
|
||||
- **Speedup**: 最慢与最快之间的速度差异倍数
|
||||
|
||||
#### TOTAL 行:
|
||||
|
||||
- 使用分隔线与数据行区分
|
||||
- 显示所有方法的总执行时间
|
||||
- 最快的策略会用对应颜色高亮显示
|
||||
- Speedup 显示总体的加速比
|
||||
|
||||
### Performance Summary
|
||||
|
||||
性能总结部分提供了策略之间的直观对比:
|
||||
|
||||
```
|
||||
📊 Performance Summary:
|
||||
• Cached strategy is 1.59x faster than Memory Optimized
|
||||
• Smart strategy is 0.98x faster than Memory, 1.63x slower than Cached
|
||||
```
|
||||
|
||||
这让您可以快速了解:
|
||||
- Cached 策略比 Memory Optimized 快多少倍
|
||||
- Smart 策略相对于其他两种策略的性能表现
|
||||
|
||||
### Memory Usage 表格
|
||||
|
||||
显示每种策略的内存使用情况:
|
||||
|
||||
```
|
||||
Strategy Peak Memory Description
|
||||
Memory Optimized 2.5 MB Minimal memory, loads on demand
|
||||
Cached 15.8 MB All data cached, fastest repeated access
|
||||
Smart 8.2 MB Adaptive loading based on text complexity
|
||||
```
|
||||
|
||||
## 如何解读结果
|
||||
|
||||
1. **选择合适的策略**:
|
||||
- 如果内存有限(如 Web 请求):选择 Memory Optimized
|
||||
- 如果需要批量处理大量文本:选择 Cached
|
||||
- 如果需要平衡性能和内存:选择 Smart
|
||||
|
||||
2. **Speedup 值的含义**:
|
||||
- 1.5x = 快 50%
|
||||
- 2.0x = 快 1 倍(速度是原来的 2 倍)
|
||||
- 3.0x = 快 2 倍(速度是原来的 3 倍)
|
||||
|
||||
3. **TOTAL 行的重要性**:
|
||||
- 这是所有方法执行的总时间
|
||||
- 最能反映实际使用场景的整体性能
|
||||
- 用于评估策略的整体效果
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
parameters:
|
||||
level: 9
|
||||
paths:
|
||||
- src
|
||||
inferPrivatePropertyTypeFromConstructor: true
|
||||
checkMissingIterableValueType: false
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonException;
|
||||
use JsonSerializable;
|
||||
use Stringable;
|
||||
|
||||
use function array_map;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
|
||||
class Collection implements ArrayAccess, JsonSerializable, Stringable
|
||||
{
|
||||
public function __construct(protected $items = []) {}
|
||||
|
||||
public function join(string $separator = ' '): string
|
||||
{
|
||||
return implode($separator, array_map(
|
||||
fn ($item) => is_array($item) ? '['.implode(', ', $item).']' : $item,
|
||||
$this->items
|
||||
));
|
||||
}
|
||||
|
||||
public function map(callable $callback): Collection
|
||||
{
|
||||
return new static(array_map($callback, $this->all()));
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function toJson(int $options = 0): string
|
||||
{
|
||||
return json_encode($this->all(), $options | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->join();
|
||||
}
|
||||
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return isset($this->items[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return $this->items[$offset] ?? null;
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
if ($offset === null) {
|
||||
$this->items[] = $value;
|
||||
} else {
|
||||
$this->items[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
unset($this->items[$offset]);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin\Contracts;
|
||||
|
||||
use Overtrue\Pinyin\Collection;
|
||||
use Overtrue\Pinyin\ToneStyle;
|
||||
|
||||
interface ConverterInterface
|
||||
{
|
||||
public function convert(string $string): Collection;
|
||||
|
||||
public function heteronym(bool $asList = false): static;
|
||||
|
||||
public function surname(): static;
|
||||
|
||||
public function noWords(): static;
|
||||
|
||||
public function noCleanup(): static;
|
||||
|
||||
public function onlyHans(): static;
|
||||
|
||||
public function noAlpha(): static;
|
||||
|
||||
public function noNumber(): static;
|
||||
|
||||
public function noPunctuation(): static;
|
||||
|
||||
public function withToneStyle(string|ToneStyle $toneStyle): static;
|
||||
|
||||
public function noTone(): static;
|
||||
|
||||
public function useNumberTone(): static;
|
||||
|
||||
public function yuToV(): static;
|
||||
|
||||
public function yuToU(): static;
|
||||
|
||||
public function yuToYu(): static;
|
||||
|
||||
public function when(bool $condition, callable $callback): static;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Overtrue\Pinyin\Contracts\ConverterInterface;
|
||||
use Overtrue\Pinyin\Converters\CachedConverter;
|
||||
use Overtrue\Pinyin\Converters\MemoryOptimizedConverter;
|
||||
use Overtrue\Pinyin\Converters\SmartConverter;
|
||||
|
||||
/**
|
||||
* Converter 工厂类
|
||||
*
|
||||
* 提供不同策略的 Converter 实例
|
||||
*/
|
||||
class ConverterFactory
|
||||
{
|
||||
public const MEMORY_OPTIMIZED = 'memory';
|
||||
|
||||
public const CACHED = 'cached';
|
||||
|
||||
public const SMART = 'smart';
|
||||
|
||||
/**
|
||||
* 默认策略
|
||||
*/
|
||||
private static string $defaultStrategy = self::MEMORY_OPTIMIZED;
|
||||
|
||||
/**
|
||||
* 创建 Converter 实例
|
||||
*
|
||||
* @param string|null $strategy 策略名称,null 则使用默认策略
|
||||
*/
|
||||
public static function make(?string $strategy = null): ConverterInterface
|
||||
{
|
||||
$strategy = $strategy ?? self::$defaultStrategy;
|
||||
|
||||
return match ($strategy) {
|
||||
self::MEMORY_OPTIMIZED => new MemoryOptimizedConverter,
|
||||
self::CACHED => new CachedConverter,
|
||||
self::SMART => new SmartConverter,
|
||||
default => throw new InvalidArgumentException("Unknown converter strategy: {$strategy}")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认策略
|
||||
*/
|
||||
public static function setDefaultStrategy(string $strategy): void
|
||||
{
|
||||
if (! in_array($strategy, [self::MEMORY_OPTIMIZED, self::CACHED, self::SMART])) {
|
||||
throw new InvalidArgumentException("Invalid strategy: {$strategy}");
|
||||
}
|
||||
|
||||
self::$defaultStrategy = $strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前默认策略
|
||||
*/
|
||||
public static function getDefaultStrategy(): string
|
||||
{
|
||||
return self::$defaultStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据场景推荐策略
|
||||
*
|
||||
* @param array $context 场景上下文
|
||||
* @return string 推荐的策略
|
||||
*/
|
||||
public static function recommend(array $context = []): string
|
||||
{
|
||||
// Web 请求场景
|
||||
if (($context['sapi'] ?? php_sapi_name()) === 'fpm-fcgi') {
|
||||
return self::MEMORY_OPTIMIZED;
|
||||
}
|
||||
|
||||
// CLI 批处理场景
|
||||
if (($context['batch'] ?? false) === true) {
|
||||
return self::CACHED;
|
||||
}
|
||||
|
||||
// 内存限制场景
|
||||
$memoryLimit = ini_get('memory_limit');
|
||||
if ($memoryLimit !== '-1' && self::parseBytes($memoryLimit) < 128 * 1024 * 1024) {
|
||||
return self::MEMORY_OPTIMIZED;
|
||||
}
|
||||
|
||||
// 默认使用智能策略
|
||||
return self::SMART;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用策略的信息
|
||||
*/
|
||||
public static function getStrategiesInfo(): array
|
||||
{
|
||||
return [
|
||||
self::MEMORY_OPTIMIZED => [
|
||||
'name' => '内存优化',
|
||||
'class' => MemoryOptimizedConverter::class,
|
||||
'memory' => '~400KB',
|
||||
'speed' => '中等',
|
||||
'use_case' => 'Web请求、内存受限环境',
|
||||
],
|
||||
self::CACHED => [
|
||||
'name' => '全缓存',
|
||||
'class' => CachedConverter::class,
|
||||
'memory' => '~4MB',
|
||||
'speed' => '最快',
|
||||
'use_case' => '批处理、长时运行进程',
|
||||
],
|
||||
self::SMART => [
|
||||
'name' => '智能',
|
||||
'class' => SmartConverter::class,
|
||||
'memory' => '600KB-1.5MB',
|
||||
'speed' => '快',
|
||||
'use_case' => '通用场景、自动优化',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private static function parseBytes(string $value): int
|
||||
{
|
||||
$value = trim($value);
|
||||
$last = strtolower($value[-1]);
|
||||
$value = (int) $value;
|
||||
|
||||
return match ($last) {
|
||||
'g' => $value * 1024 * 1024 * 1024,
|
||||
'm' => $value * 1024 * 1024,
|
||||
'k' => $value * 1024,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin\Converters;
|
||||
|
||||
use Overtrue\Pinyin\Collection;
|
||||
use Overtrue\Pinyin\Contracts\ConverterInterface;
|
||||
use Overtrue\Pinyin\ToneStyle;
|
||||
|
||||
use function implode;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
|
||||
abstract class AbstractConverter implements ConverterInterface
|
||||
{
|
||||
protected const WORDS_GLOB_PATH = __DIR__.'/../../data/words-*.php';
|
||||
|
||||
protected const CHARS_PATH = __DIR__.'/../../data/chars.php';
|
||||
|
||||
protected const SURNAMES_PATH = __DIR__.'/../../data/surnames.php';
|
||||
|
||||
protected bool $heteronym = false;
|
||||
|
||||
protected bool $heteronymAsList = false;
|
||||
|
||||
protected bool $asSurname = false;
|
||||
|
||||
protected bool $noWords = false;
|
||||
|
||||
protected bool $cleanup = true;
|
||||
|
||||
protected string $yuTo = 'v';
|
||||
|
||||
protected ToneStyle $toneStyle = ToneStyle::SYMBOL;
|
||||
|
||||
protected array $regexps = [
|
||||
'separator' => '\p{Z}',
|
||||
'mark' => '\p{M}',
|
||||
'tab' => "\t",
|
||||
'number' => '0-9',
|
||||
'alphabet' => 'a-zA-Z',
|
||||
'hans' => '\x{3007}\x{2E80}-\x{2FFF}\x{3100}-\x{312F}\x{31A0}-\x{31EF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}',
|
||||
'punctuation' => '\p{P}',
|
||||
];
|
||||
|
||||
private static ?array $wordSegmentPaths = null;
|
||||
|
||||
abstract public function convert(string $string): Collection;
|
||||
|
||||
public function heteronym(bool $asList = false): static
|
||||
{
|
||||
$this->heteronym = true;
|
||||
$this->heteronymAsList = $asList;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function surname(): static
|
||||
{
|
||||
$this->asSurname = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noWords(): static
|
||||
{
|
||||
$this->noWords = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noCleanup(): static
|
||||
{
|
||||
$this->cleanup = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function onlyHans(): static
|
||||
{
|
||||
$this->regexps['hans'] = '\x{3007}\x{2E80}-\x{2FFF}\x{3100}-\x{312F}\x{31A0}-\x{31EF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}';
|
||||
unset($this->regexps['alphabet']);
|
||||
unset($this->regexps['number']);
|
||||
unset($this->regexps['punctuation']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noAlpha(): static
|
||||
{
|
||||
unset($this->regexps['alphabet']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noNumber(): static
|
||||
{
|
||||
unset($this->regexps['number']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noPunctuation(): static
|
||||
{
|
||||
unset($this->regexps['punctuation']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置声调风格
|
||||
*
|
||||
* @param string|ToneStyle $toneStyle 声调风格,可以是字符串或 ToneStyle 枚举
|
||||
*/
|
||||
public function withToneStyle(string|ToneStyle $toneStyle): static
|
||||
{
|
||||
$this->toneStyle = $toneStyle instanceof ToneStyle ? $toneStyle : ToneStyle::from($toneStyle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function noTone(): static
|
||||
{
|
||||
$this->toneStyle = ToneStyle::NONE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function useNumberTone(): static
|
||||
{
|
||||
$this->toneStyle = ToneStyle::NUMBER;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yuToV(): static
|
||||
{
|
||||
$this->yuTo = 'v';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yuToU(): static
|
||||
{
|
||||
$this->yuTo = 'u';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yuToYu(): static
|
||||
{
|
||||
$this->yuTo = 'yu';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function when(bool $condition, callable $callback): static
|
||||
{
|
||||
if ($condition) {
|
||||
$callback($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 共享的辅助方法
|
||||
protected function preprocessString(string $string): string
|
||||
{
|
||||
// 把原有的数字和汉字分离,避免拼音转换时被误作声调
|
||||
$string = preg_replace_callback('~[a-z0-9_-]+~i', function ($matches) {
|
||||
return "\t".$matches[0];
|
||||
}, $string);
|
||||
|
||||
// 过滤掉不保留的字符
|
||||
if ($this->cleanup) {
|
||||
$string = preg_replace(sprintf('~[^%s]~u', implode($this->regexps)), '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
protected function split(string $item): Collection
|
||||
{
|
||||
$items = preg_split('/\s+/u', trim($item), -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
|
||||
foreach ($items as $index => $item) {
|
||||
$items[$index] = $this->formatTone($item, $this->toneStyle->value);
|
||||
}
|
||||
|
||||
return new Collection($items);
|
||||
}
|
||||
|
||||
protected function formatTone(string $pinyin, string $style): string
|
||||
{
|
||||
if ($style === ToneStyle::SYMBOL->value) {
|
||||
return $pinyin;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
$replacements = [
|
||||
'ɑ' => ['a', 5], 'ü' => ['v', 5], 'üē' => ['ue', 1], 'üé' => ['ue', 2], 'üě' => ['ue', 3], 'üè' => ['ue', 4],
|
||||
'ā' => ['a', 1], 'ē' => ['e', 1], 'ī' => ['i', 1], 'ō' => ['o', 1], 'ū' => ['u', 1], 'ǖ' => ['v', 1],
|
||||
'á' => ['a', 2], 'é' => ['e', 2], 'í' => ['i', 2], 'ó' => ['o', 2], 'ú' => ['u', 2], 'ǘ' => ['v', 2],
|
||||
'ǎ' => ['a', 3], 'ě' => ['e', 3], 'ǐ' => ['i', 3], 'ǒ' => ['o', 3], 'ǔ' => ['u', 3], 'ǚ' => ['v', 3],
|
||||
'à' => ['a', 4], 'è' => ['e', 4], 'ì' => ['i', 4], 'ò' => ['o', 4], 'ù' => ['u', 4], 'ǜ' => ['v', 4],
|
||||
];
|
||||
// @formatter:on
|
||||
|
||||
foreach ($replacements as $unicode => $replacement) {
|
||||
if (str_contains($pinyin, $unicode)) {
|
||||
$umlaut = $replacement[0];
|
||||
|
||||
if ($this->yuTo !== 'v' && $umlaut === 'v') {
|
||||
$umlaut = $this->yuTo;
|
||||
}
|
||||
|
||||
$pinyin = str_replace($unicode, $umlaut, $pinyin);
|
||||
|
||||
if ($style === ToneStyle::NUMBER->value) {
|
||||
$pinyin .= $replacement[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $pinyin;
|
||||
}
|
||||
|
||||
protected function wordSegmentPaths(): array
|
||||
{
|
||||
if (self::$wordSegmentPaths !== null) {
|
||||
return self::$wordSegmentPaths;
|
||||
}
|
||||
|
||||
$paths = glob(self::WORDS_GLOB_PATH);
|
||||
|
||||
if (! is_array($paths)) {
|
||||
return self::$wordSegmentPaths = [];
|
||||
}
|
||||
|
||||
sort($paths, SORT_NATURAL);
|
||||
|
||||
return self::$wordSegmentPaths = $paths;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin\Converters;
|
||||
|
||||
use Overtrue\Pinyin\Collection;
|
||||
|
||||
use function array_map;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function str_starts_with;
|
||||
|
||||
/**
|
||||
* 缓存版本的转换器
|
||||
*
|
||||
* 特点:
|
||||
* - 缓存所有词典数据
|
||||
* - 更快的重复转换速度
|
||||
* - 适合批处理和长时运行的进程
|
||||
* - 内存占用较高(~4MB)
|
||||
*/
|
||||
class CachedConverter extends AbstractConverter
|
||||
{
|
||||
private static ?array $charsCache = null;
|
||||
|
||||
private static ?array $surnamesCache = null;
|
||||
|
||||
private static array $wordsCache = [];
|
||||
|
||||
private static ?array $fullDictionary = null;
|
||||
|
||||
public function convert(string $string): Collection
|
||||
{
|
||||
$string = $this->preprocessString($string);
|
||||
|
||||
return $this->determineConversionStrategy($string);
|
||||
}
|
||||
|
||||
private function determineConversionStrategy(string $string): Collection
|
||||
{
|
||||
// 多音字处理
|
||||
if ($this->heteronym) {
|
||||
return $this->convertAsChars($string, true);
|
||||
}
|
||||
|
||||
// 仅字符转换
|
||||
if ($this->noWords) {
|
||||
return $this->convertAsChars($string);
|
||||
}
|
||||
|
||||
// 替换姓氏
|
||||
if ($this->asSurname) {
|
||||
$string = $this->convertSurname($string);
|
||||
}
|
||||
|
||||
// 使用缓存的完整词典
|
||||
$dictionary = $this->getFullDictionary();
|
||||
$string = strtr($string, $dictionary);
|
||||
|
||||
return $this->split($string);
|
||||
}
|
||||
|
||||
private function getFullDictionary(): array
|
||||
{
|
||||
if (self::$fullDictionary === null) {
|
||||
self::$fullDictionary = [];
|
||||
// 按顺序加载,保证长词优先
|
||||
foreach ($this->wordSegmentPaths() as $path) {
|
||||
self::$fullDictionary += $this->loadWordsSegment($path);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$fullDictionary;
|
||||
}
|
||||
|
||||
private function loadWordsSegment(string $path): array
|
||||
{
|
||||
if (! isset(self::$wordsCache[$path])) {
|
||||
self::$wordsCache[$path] = require $path;
|
||||
}
|
||||
|
||||
return self::$wordsCache[$path];
|
||||
}
|
||||
|
||||
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
|
||||
{
|
||||
self::$charsCache ??= require self::CHARS_PATH;
|
||||
|
||||
$chars = mb_str_split($string);
|
||||
$items = [];
|
||||
|
||||
foreach ($chars as $char) {
|
||||
if (isset(self::$charsCache[$char])) {
|
||||
if ($polyphonic) {
|
||||
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), self::$charsCache[$char]);
|
||||
if ($this->heteronymAsList) {
|
||||
$items[] = [$char => $pinyin];
|
||||
} else {
|
||||
$items[$char] = $pinyin;
|
||||
}
|
||||
} else {
|
||||
$items[$char] = $this->formatTone(self::$charsCache[$char][0], $this->toneStyle->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection($items);
|
||||
}
|
||||
|
||||
protected function convertSurname(string $name): string
|
||||
{
|
||||
self::$surnamesCache ??= require self::SURNAMES_PATH;
|
||||
|
||||
foreach (self::$surnamesCache as $surname => $pinyin) {
|
||||
if (str_starts_with($name, $surname)) {
|
||||
return $pinyin.mb_substr($name, mb_strlen($surname));
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存(可选)
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
self::$charsCache = null;
|
||||
self::$surnamesCache = null;
|
||||
self::$wordsCache = [];
|
||||
self::$fullDictionary = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin\Converters;
|
||||
|
||||
use Overtrue\Pinyin\Collection;
|
||||
|
||||
use function array_map;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function str_starts_with;
|
||||
|
||||
/**
|
||||
* 内存优化版本的转换器
|
||||
*
|
||||
* 特点:
|
||||
* - 最小内存占用(峰值 ~400KB)
|
||||
* - 每次加载一个段,用完即释放
|
||||
* - 适合 Web 请求和内存受限环境
|
||||
*/
|
||||
class MemoryOptimizedConverter extends AbstractConverter
|
||||
{
|
||||
public function convert(string $string): Collection
|
||||
{
|
||||
$string = $this->preprocessString($string);
|
||||
|
||||
return $this->determineConversionStrategy($string);
|
||||
}
|
||||
|
||||
private function determineConversionStrategy(string $string): Collection
|
||||
{
|
||||
// 多音字处理
|
||||
if ($this->heteronym) {
|
||||
return $this->convertAsChars($string, true);
|
||||
}
|
||||
|
||||
// 仅字符转换
|
||||
if ($this->noWords) {
|
||||
return $this->convertAsChars($string);
|
||||
}
|
||||
|
||||
// 替换姓氏
|
||||
if ($this->asSurname) {
|
||||
$string = $this->convertSurname($string);
|
||||
}
|
||||
|
||||
// 按顺序加载词典段(长词优先)
|
||||
foreach ($this->wordSegmentPaths() as $path) {
|
||||
$string = strtr($string, require $path);
|
||||
}
|
||||
|
||||
return $this->split($string);
|
||||
}
|
||||
|
||||
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
|
||||
{
|
||||
$map = require self::CHARS_PATH;
|
||||
|
||||
$chars = mb_str_split($string);
|
||||
$items = [];
|
||||
|
||||
foreach ($chars as $char) {
|
||||
if (isset($map[$char])) {
|
||||
if ($polyphonic) {
|
||||
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), $map[$char]);
|
||||
if ($this->heteronymAsList) {
|
||||
$items[] = [$char => $pinyin];
|
||||
} else {
|
||||
$items[$char] = $pinyin;
|
||||
}
|
||||
} else {
|
||||
$items[$char] = $this->formatTone($map[$char][0], $this->toneStyle->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection($items);
|
||||
}
|
||||
|
||||
protected function convertSurname(string $name): string
|
||||
{
|
||||
static $surnames = null;
|
||||
$surnames ??= require self::SURNAMES_PATH;
|
||||
|
||||
foreach ($surnames as $surname => $pinyin) {
|
||||
if (str_starts_with($name, $surname)) {
|
||||
return $pinyin.mb_substr($name, mb_strlen($surname));
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin\Converters;
|
||||
|
||||
use Overtrue\Pinyin\Collection;
|
||||
|
||||
use function array_map;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function str_starts_with;
|
||||
|
||||
/**
|
||||
* 智能版本的转换器
|
||||
*
|
||||
* 特点:
|
||||
* - 根据文本长度智能选择策略
|
||||
* - 短文本跳过不必要的长词词典
|
||||
* - 缓存小型常用数据
|
||||
* - 平衡内存和性能
|
||||
*/
|
||||
class SmartConverter extends AbstractConverter
|
||||
{
|
||||
private static ?array $surnamesCache = null;
|
||||
|
||||
private static array $segmentCache = [];
|
||||
|
||||
private static ?array $fullDictionary = null;
|
||||
|
||||
private const MAX_CACHE_SEGMENTS = 3;
|
||||
|
||||
public function convert(string $string): Collection
|
||||
{
|
||||
$string = $this->preprocessString($string);
|
||||
|
||||
return $this->determineConversionStrategy($string);
|
||||
}
|
||||
|
||||
private function determineConversionStrategy(string $string): Collection
|
||||
{
|
||||
// 多音字处理
|
||||
if ($this->heteronym) {
|
||||
return $this->convertAsChars($string, true);
|
||||
}
|
||||
|
||||
// 仅字符转换
|
||||
if ($this->noWords) {
|
||||
return $this->convertAsChars($string);
|
||||
}
|
||||
|
||||
// 替换姓氏
|
||||
if ($this->asSurname) {
|
||||
$string = $this->convertSurname($string);
|
||||
}
|
||||
|
||||
// 智能加载词典
|
||||
$string = $this->smartConvert($string);
|
||||
|
||||
return $this->split($string);
|
||||
}
|
||||
|
||||
private function smartConvert(string $string): string
|
||||
{
|
||||
// 使用和CachedConverter相同的逻辑,但保持缓存机制
|
||||
$dictionary = $this->getFullDictionary();
|
||||
|
||||
return strtr($string, $dictionary);
|
||||
}
|
||||
|
||||
private function getFullDictionary(): array
|
||||
{
|
||||
if (self::$fullDictionary === null) {
|
||||
self::$fullDictionary = [];
|
||||
// 按顺序加载,保证长词优先
|
||||
foreach ($this->wordSegmentPaths() as $path) {
|
||||
self::$fullDictionary += $this->loadSegmentWithCache($path);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$fullDictionary;
|
||||
}
|
||||
|
||||
private function loadSegmentWithCache(string $path): array
|
||||
{
|
||||
if (! isset(self::$segmentCache[$path])) {
|
||||
// 缓存数量限制
|
||||
if (count(self::$segmentCache) >= self::MAX_CACHE_SEGMENTS) {
|
||||
// 移除最早的缓存(简单的 FIFO)
|
||||
$firstKey = array_key_first(self::$segmentCache);
|
||||
if ($firstKey !== null) {
|
||||
unset(self::$segmentCache[$firstKey]);
|
||||
}
|
||||
}
|
||||
self::$segmentCache[$path] = require $path;
|
||||
}
|
||||
|
||||
return self::$segmentCache[$path];
|
||||
}
|
||||
|
||||
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
|
||||
{
|
||||
// 字符表太大,不缓存
|
||||
$map = require self::CHARS_PATH;
|
||||
|
||||
$chars = mb_str_split($string);
|
||||
$items = [];
|
||||
|
||||
foreach ($chars as $char) {
|
||||
if (isset($map[$char])) {
|
||||
if ($polyphonic) {
|
||||
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), $map[$char]);
|
||||
if ($this->heteronymAsList) {
|
||||
$items[] = [$char => $pinyin];
|
||||
} else {
|
||||
$items[$char] = $pinyin;
|
||||
}
|
||||
} else {
|
||||
$items[$char] = $this->formatTone($map[$char][0], $this->toneStyle->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection($items);
|
||||
}
|
||||
|
||||
protected function convertSurname(string $name): string
|
||||
{
|
||||
// 姓氏表很小,可以缓存
|
||||
self::$surnamesCache ??= require self::SURNAMES_PATH;
|
||||
|
||||
foreach (self::$surnamesCache as $surname => $pinyin) {
|
||||
if (str_starts_with($name, $surname)) {
|
||||
return $pinyin.mb_substr($name, mb_strlen($surname));
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
self::$surnamesCache = null;
|
||||
self::$segmentCache = [];
|
||||
self::$fullDictionary = null;
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Overtrue\Pinyin\Contracts\ConverterInterface;
|
||||
use Overtrue\Pinyin\Converters\CachedConverter;
|
||||
use Overtrue\Pinyin\Converters\SmartConverter;
|
||||
|
||||
use function is_numeric;
|
||||
use function mb_substr;
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* @method static ConverterInterface surname()
|
||||
* @method static ConverterInterface noWords()
|
||||
* @method static ConverterInterface onlyHans()
|
||||
* @method static ConverterInterface noAlpha()
|
||||
* @method static ConverterInterface noNumber()
|
||||
* @method static ConverterInterface noCleanup()
|
||||
* @method static ConverterInterface noPunctuation()
|
||||
* @method static ConverterInterface noTone()
|
||||
* @method static ConverterInterface useNumberTone()
|
||||
* @method static ConverterInterface yuToV()
|
||||
* @method static ConverterInterface yuToU()
|
||||
* @method static ConverterInterface withToneStyle(string|ToneStyle $toneStyle = 'symbol')
|
||||
* @method static Collection convert(string $string, callable $beforeSplit = null)
|
||||
*/
|
||||
class Pinyin
|
||||
{
|
||||
/**
|
||||
* 当前使用的转换策略
|
||||
*/
|
||||
private static ?string $converterStrategy = null;
|
||||
|
||||
public static function name(string $name, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::converter()->surname()->withToneStyle($toneStyle)->convert($name);
|
||||
}
|
||||
|
||||
public static function passportName(string $name, string|ToneStyle $toneStyle = ToneStyle::NONE): Collection
|
||||
{
|
||||
return self::converter()->surname()->yuToYu()->withToneStyle($toneStyle)->convert($name);
|
||||
}
|
||||
|
||||
public static function phrase(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::converter()->noPunctuation()->withToneStyle($toneStyle)->convert($string);
|
||||
}
|
||||
|
||||
public static function sentence(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::converter()->withToneStyle($toneStyle)->convert($string);
|
||||
}
|
||||
|
||||
public static function fullSentence(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::converter()->noCleanup()->withToneStyle($toneStyle)->convert($string);
|
||||
}
|
||||
|
||||
public static function heteronym(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL, bool $asList = false): Collection
|
||||
{
|
||||
return self::converter()->heteronym($asList)->withToneStyle($toneStyle)->convert($string);
|
||||
}
|
||||
|
||||
public static function heteronymAsList(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::heteronym($string, $toneStyle, true);
|
||||
}
|
||||
|
||||
public static function chars(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
|
||||
{
|
||||
return self::converter()->onlyHans()->noWords()->withToneStyle($toneStyle)->convert($string);
|
||||
}
|
||||
|
||||
public static function permalink(string $string, string $delimiter = '-'): string
|
||||
{
|
||||
if (! in_array($delimiter, ['_', '-', '.', ''], true)) {
|
||||
throw new InvalidArgumentException("Delimiter must be one of: '_', '-', '', '.'.");
|
||||
}
|
||||
|
||||
return self::converter()->noPunctuation()->noTone()->convert($string)->join($delimiter);
|
||||
}
|
||||
|
||||
public static function nameAbbr(string $string): Collection
|
||||
{
|
||||
return self::abbr($string, true);
|
||||
}
|
||||
|
||||
public static function abbr(string $string, bool $asName = false, bool $preserveEnglishWords = false): Collection
|
||||
{
|
||||
return self::converter()->noTone()
|
||||
->noPunctuation()
|
||||
->when($asName, fn ($c) => $c->surname())
|
||||
->convert($string)
|
||||
->map(function ($pinyin) use ($string, $preserveEnglishWords) {
|
||||
// 如果内容在原字符串中,则直接返回
|
||||
if ($preserveEnglishWords && str_contains($string, $pinyin)) {
|
||||
return $pinyin;
|
||||
}
|
||||
|
||||
// 常用于电影名称入库索引处理,例如:《晚娘2012》-> WN2012
|
||||
return is_numeric($pinyin) || preg_match('/\d{2,}/', $pinyin) ? $pinyin : mb_substr($pinyin, 0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Converter 实例
|
||||
*
|
||||
* @param string|null $strategy 指定策略,null 则使用默认策略
|
||||
*/
|
||||
public static function converter(?string $strategy = null): ConverterInterface
|
||||
{
|
||||
// 使用新的工厂模式
|
||||
$strategy = $strategy ?? self::$converterStrategy;
|
||||
|
||||
return ConverterFactory::make($strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认转换策略
|
||||
*
|
||||
* @param string $strategy 策略名称
|
||||
*/
|
||||
public static function setConverterStrategy(string $strategy): void
|
||||
{
|
||||
self::$converterStrategy = $strategy;
|
||||
ConverterFactory::setDefaultStrategy($strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用内存优化策略
|
||||
*/
|
||||
public static function useMemoryOptimized(): void
|
||||
{
|
||||
self::setConverterStrategy(ConverterFactory::MEMORY_OPTIMIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用缓存策略
|
||||
*/
|
||||
public static function useCached(): void
|
||||
{
|
||||
self::setConverterStrategy(ConverterFactory::CACHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用智能策略
|
||||
*/
|
||||
public static function useSmart(): void
|
||||
{
|
||||
self::setConverterStrategy(ConverterFactory::SMART);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据运行环境自动选择策略
|
||||
*/
|
||||
public static function useAutoStrategy(): void
|
||||
{
|
||||
$strategy = ConverterFactory::recommend();
|
||||
self::setConverterStrategy($strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有转换器的缓存
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
CachedConverter::clearCache();
|
||||
SmartConverter::clearCache();
|
||||
}
|
||||
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
$converter = self::converter();
|
||||
|
||||
if (method_exists($converter, $name)) {
|
||||
return $converter->$name(...$arguments);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Method {$name} does not exist.");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Overtrue\Pinyin;
|
||||
|
||||
/**
|
||||
* 拼音声调风格枚举
|
||||
*/
|
||||
enum ToneStyle: string
|
||||
{
|
||||
/**
|
||||
* 符号风格:zhōng
|
||||
*/
|
||||
case SYMBOL = 'symbol';
|
||||
|
||||
/**
|
||||
* 数字风格:zhong1
|
||||
*/
|
||||
case NUMBER = 'number';
|
||||
|
||||
/**
|
||||
* 无声调:zhong
|
||||
*/
|
||||
case NONE = 'none';
|
||||
}
|
||||
Reference in New Issue
Block a user