Search by

laraveladmin / formatter

laraveladmin

Template-driven data structure transformation for PHP / Hyperf.

Package info

gitee.com/laravel-admin/formatter

pkg:composer/laraveladmin/formatter

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

v1.0.0 2026-09-08 14:22 UTC

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

ExpressionMeaning
a.bDot path from source
a.b$def:xDefault x when missing ($def: alone → '')
$def:1Constant default (no path)
a.b:number / :string / :div:2Cast; combinable: a.b:number$def:0
$i / $n / $countLoop context
$global.xThird 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
  • @arr on 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; Hyperf make() 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