Search code examples
phpmysqlsymfonyserializationdoctrine-orm

Symfony serializer not serializing all class fields


I have two classes, one is for managing Events and the other are the Users. Users can participate to Events. When they participate they become "Attendees". The issue is when I serialize an event with attendees, the response contains all field except the attendees field.

The Event class:

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

}

The User class:

<?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

}

The method where the Event is serialized:

#[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']));
    }

I maybe miss something using ManyToMany relationship and serializer but I don't guess where.

Thanks


Solution

  • Using the JMSSerializerBundle seems working fine with this relationship. Maybe JMS supports ManyToMany relations better than the Symfony serializer do.