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); } }