API Platform v3中隐藏自引用嵌套实体的children属性
解决自引用分类实体的序列化显示问题
你要实现的是只让请求的根分类显示children属性,嵌套的父级、子级都隐藏这个字段,MaxDepth只能限制层级,确实做不到这个需求,给你几个实用的解决方案:
方案一:序列化分组+回调控制
先给实体的属性加序列化分组注解,把children单独归到一个组里,然后在序列化的时候,根分类用全量组,嵌套的实体只用基础组。
实体代码修改
use Symfony\Component\Serializer\Annotation\Groups; class Category { private $id; /** * @Groups({"category:base"}) */ private $name; /** * @Groups({"category:base"}) */ private $parent; /** * @Groups({"category:with_children"}) */ private $children; // 对应的getter、setter省略 }
控制器处理逻辑
在序列化根分类时,指定包含category:base和category:with_children,但处理嵌套的parent和children时,只传category:base组:
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; $rootCategory = $this->getDoctrine()->getRepository(Category::class)->find(5); $serializedData = $serializer->serialize( $rootCategory, 'json', [ 'groups' => ['category:base', 'category:with_children'], AbstractNormalizer::CALLBACKS => [ // 序列化父分类时,只拿基础字段 'parent' => function ($parent) use ($serializer) { return $serializer->normalize($parent, null, ['groups' => ['category:base']]); }, // 序列化子分类时,同样只拿基础字段,不包含它们的children 'children' => function ($children) use ($serializer) { return array_map(function ($child) use ($serializer) { return $serializer->normalize($child, null, ['groups' => ['category:base']]); }, $children); } ] ] );
这样根分类会带出children,而它的父级、所有子级都不会显示children字段。
方案二:自定义序列化Normalizer
如果你的场景更复杂,写个自定义的Normalizer来控制显示逻辑更灵活。
自定义Normalizer代码
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; class CategoryNormalizer extends ObjectNormalizer implements ContextAwareNormalizerInterface { public function normalize($object, string $format = null, array $context = []): array { $data = parent::normalize($object, $format, $context); // 只有标记为根实体的才保留children,其他都删掉 if (!($context['is_root'] ?? false)) { unset($data['children']); } // 处理父级,传递非根上下文 if (isset($data['parent'])) { $data['parent'] = $this->normalize($object->getParent(), $format, ['is_root' => false]); } // 处理子级,同样传递非根上下文 if (isset($data['children'])) { $data['children'] = array_map(function ($child) use ($format, $context) { return $this->normalize($child, $format, ['is_root' => false]); }, $data['children']); } return $data; } public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof Category; } }
控制器使用
序列化根分类时,在上下文里标记is_root为true:
$serializedData = $serializer->serialize( $rootCategory, 'json', ['is_root' => true] );
方案三:实体序列化回调(轻量方案)
给实体加个临时标记,序列化后动态移除children:
实体修改
use Symfony\Component\Serializer\Annotation\PostSerialize; class Category { // 其他属性省略 private bool $isRoot = false; public function markAsRoot(): void { $this->isRoot = true; } /** * @PostSerialize */ public function postSerialize(array &$data): void { if (!$this->isRoot) { unset($data['children']); } // 重置标记,防止影响后续序列化 $this->isRoot = false; } }
控制器使用
找到根分类后先标记为根,再序列化:
$rootCategory = $this->getDoctrine()->getRepository(Category::class)->find(5); $rootCategory->markAsRoot(); $serializedData = $serializer->serialize($rootCategory, 'json');
这三个方案都能满足你的需求,选哪个看你的场景复杂度:简单场景用方案三最省事,需要更精细控制的话用方案一,复杂业务逻辑优先方案二。
内容的提问来源于stack exchange,提问作者Matías Cánepa
相关产品推荐
相关产品推荐

