Files
23cm/Serializers/Serializer.php
2026-01-25 18:18:09 +08:00

164 lines
3.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\serializers;
class Serializer
{
/* ========= 规则存储 ========= */
protected array $handlerTrie = [];
protected array $removeTrie = [];
protected array $appendRules = [];
public function __construct(
protected array|null $source = null
) {}
/* ========= API ========= */
public function handle(string $path, callable $fn): static
{
$this->insertTrie($this->handlerTrie, explode('.', $path), $fn);
return $this;
}
public function remove(string $path): static
{
$this->insertTrie($this->removeTrie, explode('.', $path), true);
return $this;
}
/**
* 节点级追加path 指向节点)
*/
public function append(string $path, callable $fn): static
{
$this->appendRules[] = [
'path' => explode('.', $path),
'fn' => $fn,
];
return $this;
}
/* ========= 执行 ========= */
public function serialize(): array
{
$data = $this->source ?? [];
// Phase 1字段级Trie
$this->walkTrie($data, $this->handlerTrie, $this->removeTrie);
// Phase 2节点级append
$this->applyAppend($data);
return $data;
}
/* ========= Phase 1 ========= */
protected function walkTrie(
&$node,
array $handlerNode,
array $removeNode
): void {
if (!is_array($node)) return;
foreach ($node as $key => &$value) {
// list不消费 trie
if (is_int($key)) {
$this->walkTrie($value, $handlerNode, $removeNode);
continue;
}
$nextHandler = $handlerNode[$key] ?? [];
$nextRemove = $removeNode[$key] ?? [];
// 删除优先
if (isset($nextRemove['_end'])) {
unset($node[$key]);
continue;
}
// 修改字段
if (isset($nextHandler['_fn'])) {
$value = ($nextHandler['_fn'])($value);
}
if (is_array($value)) {
$this->walkTrie($value, $nextHandler, $nextRemove);
}
}
}
/* ========= Phase 2 ========= */
protected function applyAppend(array &$data): void
{
foreach ($this->appendRules as $rule) {
$this->walkAppend($data, $rule['path'], $rule['fn']);
}
}
protected function walkAppend(
&$node,
array $path,
callable $fn
): void {
if (!is_array($node)) return;
// 命中节点
if (empty($path)) {
if (!$this->isList($node)) {
$fn($node);
}
return;
}
$key = array_shift($path);
if ($this->isList($node)) {
foreach ($node as &$item) {
$this->walkAppend($item, array_merge([$key], $path), $fn);
}
return;
}
if (isset($node[$key])) {
$this->walkAppend($node[$key], $path, $fn);
}
}
/* ========= Trie ========= */
protected function insertTrie(array &$trie, array $path, $value): void
{
$node = &$trie;
foreach ($path as $segment) {
if (!isset($node[$segment])) {
$node[$segment] = [];
}
$node = &$node[$segment];
}
if (is_callable($value)) {
$node['_fn'] = $value;
} else {
$node['_end'] = true;
}
}
protected function isList(array $arr): bool
{
return array_keys($arr) === range(0, count($arr) - 1);
}
}