laraveladmin / formatter
Template-driven data structure transformation for PHP / Hyperf.
Requires
- php: >=8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-09 00:06:08 UTC
README
Template-driven data structure transformation for PHP 8.2+ / Hyperf.
Stateless and singleton-safe: the service never stores request data on the instance, so it is safe in long-running Hyperf workers.
中文说明:README.zh-CN.md
Install
composer require laraveladmin/formatter
Repository: https://gitee.com/laravel-admin/formatter
API
use LaravelAdmin\Formatter\Formatter;
/** @var Formatter $formatter */ // prefer constructor injection (process singleton)
$result = $formatter->render($template, $source, $global = []);
$result = $formatter->map($data, $maps);
$maps = $formatter->flipMaps($maps);
Optional static proxy (still uses the DI singleton when the Hyperf container is available):
use LaravelAdmin\Formatter\Facades\Formatter;
$data = Formatter::render($template, $source, $global);
Examples (input → output)
1. Rename & dot path
$formatter->render(
['name' => 'user_name', 'id' => 'user.id'],
['user_name' => 'Alice', 'user' => ['id' => 42]],
);
// => ['name' => 'Alice', 'id' => 42]
2. Default ($def) & type cast
$formatter->render(
[
'age' => 'age$def:0',
'flag' => '$def:1',
'score' => 'score:number$def:0',
'tags' => 'tags:string',
'amount' => 'amount:div:2',
],
['tags' => ['a', 'b'], 'amount' => 10000],
);
// => [
// 'age' => '0',
// 'flag' => '1',
// 'score' => 0,
// 'tags' => 'a,b',
// 'amount' => '100.00',
// ]
3. Nested object
$formatter->render(
['order' => ['buyer' => ['name' => 'order.buyer.name']]],
['order' => ['buyer' => ['name' => 'Bob']]],
);
// => ['order' => ['buyer' => ['name' => 'Bob']]]
4. Prototype list (no $index)
Fill a list-shaped slot from the current context (common for “always one applicant” payloads):
$formatter->render(
['applicants' => [['name' => 'bidderName:string$def:', 'cf_type' => '$def:10']]],
['bidderName' => 'Acme Ltd'],
);
// => ['applicants' => [['name' => 'Acme Ltd', 'cf_type' => '10']]]
5. $index loop + $i / $n / $count + $global
$formatter->render(
[[
'name' => 'items.$index.name',
'i' => '$i',
'n' => '$n',
'count' => '$count',
'currency' => 'currency',
]],
['items' => [['name' => 'a'], ['name' => 'b']]],
['currency' => 'CNY'],
);
// => [
// ['name' => 'a', 'i' => 0, 'n' => 1, 'count' => 2, 'currency' => 'CNY'],
// ['name' => 'b', 'i' => 1, 'n' => 2, 'count' => 2, 'currency' => 'CNY'],
// ]
Leaves without $index are rewritten to $global.… after the collection prefix is stripped.
6. @arr flatten into parent
$formatter->render(
[
'Risks' => [
'keep' => '$def:header',
'rows' => [
'@arr' => true,
'code' => 'products.$index.code',
],
],
],
['products' => [['code' => 'P1'], ['code' => 'P2']]],
);
// => [
// 'Risks' => [
// 'keep' => 'header',
// 0 => ['code' => 'P1'],
// 1 => ['code' => 'P2'],
// ],
// ]
7. Value map / flipMaps
$maps = ['status' => [1 => 'OK', 2 => 'NO', '@default' => 'UNK']];
$formatter->map(['status' => 1], $maps);
// => ['status' => 'OK']
$formatter->map(['status' => 9], $maps);
// => ['status' => 'UNK']
$formatter->map(['status' => null], $maps);
// => ['status' => null] // null is never mapped
$formatter->map(['status' => 'OK'], $formatter->flipMaps($maps));
// => ['status' => 1]
8. Order-like assemble (mini business sample)
$template = [
'pay_method' => ':number$def:0',
'applicants' => [[
'name' => 'bidderName:string$def:',
'cf_type' => '$def:10',
]],
'mark' => [
'type' => '$def:3',
'name' => 'sectionName$def:',
'options' => ['houseCertificate' => 'sectionCode'],
],
'order_product' => [[
'amount_insured' => 'tenderBond',
'product_id' => 'zlts_data.product.id',
]],
];
$signData = [
'bidderName' => '某某公司',
'sectionName' => '标段一',
'sectionCode' => 'SC-001',
'tenderBond' => '100000',
'zlts_data' => ['product' => ['id' => 16]],
];
$formatter->render($template, $signData, []);
// => [
// 'pay_method' => 0,
// 'applicants' => [['name' => '某某公司', 'cf_type' => '10']],
// 'mark' => [
// 'type' => '3',
// 'name' => '标段一',
// 'options' => ['houseCertificate' => 'SC-001'],
// ],
// 'order_product' => [['amount_insured' => '100000', 'product_id' => 16]],
// ]
Leaf DSL
| Expression | Meaning |
|---|---|
a.b | Dot path from source |
a.b$def:x | Default x when missing ($def: alone → '') |
$def:1 | Constant default (no path) |
a.b:number / :string / :div:2 | Cast; combinable: a.b:number$def:0 |
$i / $n / $count | Loop context |
$global.x | Third argument ($global) |
Structure rules
- Associative array → nested object
- List without
$index→ prototype mode (each element resolved against current context) - List with
$index.…→ iterate the collection before$index @arron a child → render as list then flatten into parent numeric keys
Design notes
- Inputs are not mutated
- Prefer constructor injection /
container->get(Formatter::class)for the process singleton; Hyperfmake()creates a new instance - No Laravel framework dependency; PHP ≥ 8.2 only
Tests
vendor/bin/pest vendor/laraveladmin/formatter/tests
# or from a Hyperf app that path-requires the package:
docker compose exec -T server vendor/bin/pest ../packages/formatter/tests