Symfony Serializer未序列化Event类的attendees字段问题
Symfony序列化Event时缺失attendees字段问题排查与解决
我定义了Event和User两个实体类,用户可参与事件并成为Attendee(参与者)。但使用Symfony Serializer序列化包含参与者的Event实例时,响应结果中缺失attendees字段。
Event类代码
<?php namespace App\Entity; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\CustomIdGenerator; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\ManyToMany; use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\Table; use Gedmo\Mapping\Annotation\Timestampable; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints\NotBlank; use Vich\UploaderBundle\Mapping\Annotation\Uploadable; use Vich\UploaderBundle\Mapping\Annotation\UploadableField; #[Entity] #[Table(name: 'event')] #[Uploadable] class Event { #[Id] #[GeneratedValue(strategy: 'CUSTOM')] #[CustomIdGenerator(class: UUIDGenerator::class)] #[Column] #[Groups(['apiExposed'])] private string $id; #[Column] #[Groups(['apiExposed', 'searchable'])] #[NotBlank] private string $title; #[Column(type: 'point', nullable: true)] #[Groups(['apiExposed', 'searchable'])] private ?Point $location; #[ManyToOne(targetEntity: User::class)] #[Groups('apiExposed')] #[NotBlank] private User $organizer; #[ManyToMany(targetEntity: User::class, inversedBy: 'participatedEvents')] #[Groups('apiExposed')] private Collection $attendees; #[Column(type: 'text')] #[Groups(['apiExposed', 'searchable'])] #[NotBlank] private string $description; #[Column] #[Groups(['aîExposed', 'searchable'])] #[NotBlank] private string $address; #[UploadableField(mapping: 'event_picture', fileNameProperty: 'pictureName')] private ?File $pictureFile = null; #[Column(nullable: true)] #[Groups(['apiExposed', 'searchable'])] private ?string $pictureName = 'event.jpg'; #[Column] #[Groups(['apiExposed', 'searchable'])] private bool $isSecret = false; #[Column] #[Groups(['apiExposed', 'searchable'])] private \DateTimeImmutable $startDate; #[Column] #[Groups('apiExposed')] #[Timestampable(on: 'create')] private \DateTimeImmutable $createdAt; #[Column] #[Groups('apiExposed')] #[Timestampable(on: 'update')] private \DateTimeImmutable $updatedAt; // Getters and setters /** * @return Collection */ public function getAttendees(): Collection { return $this->attendees; } /** * @param User $user * @return $this */ public function addAttendee(User $user): Event { $this -> attendees -> add($user); return $this; } /** * @param User $user * @return $this */ public function removeAttendee(User $user): Event { $this -> attendees -> removeElement($user); return $this; } }
User类代码
<?php namespace App\Entity; use App\Repository\UserRepository; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation\Timestampable; use OpenApi\Attributes\Items; use OpenApi\Attributes\Property; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Vich\UploaderBundle\Mapping\Annotation\Uploadable; use Vich\UploaderBundle\Mapping\Annotation\UploadableField; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Table(name: '`user`')] #[Uploadable] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups('apiExposed')] private ?int $id = null; #[ORM\Column(length: 180, unique: true)] #[Assert\NotBlank] #[Groups('apiExposed')] private ?string $username = null; #[ORM\Column] #[Groups('apiExposed')] #[Property(type: 'array', items: new Items(type: 'string'))] private array $roles = []; /** * @var string The hashed password */ #[ORM\Column] #[Assert\NotBlank] private ?string $password = null; #[ORM\Column(nullable: true)] #[Groups('apiExposed')] private ?string $bio = null; #[ORM\Column(unique: true)] #[Assert\Email] #[Assert\NotBlank] #[Groups('apiExposed')] private ?string $email = null; #[UploadableField(mapping: 'user_picture', fileNameProperty: 'pictureName')] private ?File $pictureFile = null; #[ORM\Column(nullable: true)] #[Groups('apiExposed')] private ?string $pictureName = null; #[ORM\OneToMany(targetEntity: Car::class, mappedBy: 'user')] private Collection $cars; #[ORM\ManyToMany(targetEntity: Event::class, mappedBy: 'attendees')] private Collection $participatedEvents; #[ORM\Column] #[Timestampable(on: 'create')] #[Groups('apiExposed')] private ?\DateTimeImmutable $createdAt; #[ORM\Column] #[Timestampable(on: 'update')] #[Groups('apiExposed')] private ?\DateTimeImmutable $updatedAt; // Getters and setters }
序列化Event的控制器方法
#[OA\Response(response: 200, description: 'Returns an event given by its ID.', content: new Model(type: Event::class, groups: ['apiExposed']))] #[OA\Response(response: 404, description: 'No event found with this ID.')] #[OA\Response(response: 403, description: 'Event is secret for the current user.')] #[OA\Tag(name: 'Event')] public function getByID(EntityManagerInterface $entityManager, SerializerInterface $serializer, string $eventId): JsonResponse { //getting event $event = $entityManager -> getRepository(Event::class) -> find($eventId); //checking if exists if (!$event) { return new JsonResponse([ 'code' => Response::HTTP_NOT_FOUND, 'error' => 'Unknown event' ], Response::HTTP_NOT_FOUND); } //check if event is secret if ($event -> isSecret() && $event -> getOrganizer() != $this -> getUser()) { return new JsonResponse([ 'code' => Response::HTTP_FORBIDDEN, 'error' => 'Event is secret' ], Response::HTTP_FORBIDDEN); } return JsonResponse::fromJsonString($serializer -> serialize($event, 'json', ['groups' => 'apiExposed'])); }
问题原因与解决方案
1. 集合未初始化
Event类的attendees字段是Collection类型,但没有在构造函数中初始化,导致它默认是null,Symfony Serializer无法序列化null值的集合。
修复:给Event类添加构造函数,初始化attendees为ArrayCollection:
use Doctrine\Common\Collections\ArrayCollection; // ... public function __construct() { $this->attendees = new ArrayCollection(); }
2. Doctrine懒加载问题
ManyToMany关联默认是懒加载模式,当你用find()方法获取Event时,attendees集合并没有被实际加载(只是一个代理对象),Serializer无法读取未初始化的集合数据。
修复方式一:查询时主动加载关联
修改控制器中的查询逻辑,用QueryBuilder主动join并加载attendees:
$event = $entityManager->getRepository(Event::class) ->createQueryBuilder('e') ->leftJoin('e.attendees', 'a') ->addSelect('a') ->where('e.id = :eventId') ->setParameter('eventId', $eventId) ->getQuery() ->getOneOrNullResult();
修复方式二:配置Serializer处理懒加载
在实体类的关联字段添加@MaxDepth注解,避免循环引用,同时序列化时启用深度控制:
- 在Event类的
attendees字段添加:
use Symfony\Component\Serializer\Annotation\MaxDepth; // ... #[ManyToMany(targetEntity: User::class, inversedBy: 'participatedEvents')] #[Groups('apiExposed')] #[MaxDepth(1)] private Collection $attendees;
- 在User类的
participatedEvents字段添加:
#[ORM\ManyToMany(targetEntity: Event::class, mappedBy: 'attendees')] #[Groups('apiExposed')] #[MaxDepth(1)] private Collection $participatedEvents;
- 序列化时添加
enable_max_depth参数:
return JsonResponse::fromJsonString($serializer->serialize($event, 'json', [ 'groups' => 'apiExposed', 'enable_max_depth' => true ]));
3. 序列化组拼写错误(附带修复)
Event类中address字段的Groups注解写错了:#[Groups(['aîExposed', 'searchable'])],这里的aîExposed应该是apiExposed,虽然不影响attendees字段,但会导致address字段无法被正确序列化,建议修正:
#[Column] #[Groups(['apiExposed', 'searchable'])] #[NotBlank] private string $address;
内容的提问来源于stack exchange,提问作者Cyprien Aubry
相关产品推荐
相关产品推荐

