first commit
This commit is contained in:
163
Serializers/Serializer.php
Normal file
163
Serializers/Serializer.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user