Search code examples
symfonyapi-platform.comdenormalization

How to modify data before the denormalization and send It back?


I'd like to have advice on the best practice to achieve something.

I've a JSON output of an entity called "Session" with 3 related entities (platform, user, course ) and I'd like to use the nested way to create those 3 related if they don't exist.

But If they do exist I'd like to add their IRI (or ID) to the JSON output before API Platform does the magic. (or another way to achieve this behavior)

I naively thought that I should bind to the pre_denormalization event but I've no idea how to return the data to the event.

Here is what I've got so far.

public static function getSubscribedEvents()
{
    return [
        KernelEvents::REQUEST => ['onSessionDenormalize', EventPriorities::PRE_DESERIALIZE],
    ];
}

public function onSessionDenormalize(RequestEvent $event)
{
    $data = $event->getRequest()->getContent();
}

public function modifyPayLoad($data) {
    $dataObject = json_decode($data);
    $platform = $dataObject->platform;
    $user = $dataObject->user;
    $course = $dataObject->course;

    if($this->platformRepository->findOneBy(['slug' => $platform->slug])) {
        $platformID = $this->courseRepository->findOneBy(['slug' => $platform->slug])->getId();
        $dataObject->id = $platformID;

        if($this->userRepository->findOneBy(['email' => $user->slug])) {
            $dataObject->id = $this->userRepository->findOneBy(['email' => $user->slug])->getId();
            $dataObject->user->platform->id = $platformID;
        }

        if($this->courseRepository->findOneBy(['slug' => $course->slug])) {
            $dataObject->id = $this->courseRepository->findOneBy(['slug' => $course->slug])->getId();
            $dataObject->course->platform->id = $platformID;
        }
    }

    return json_encode($dataObject);
}

And the JSON:

{
"user": {
    "firstname": "string",
    "lastname": "string",
    "address": "string",
    "city": "string",
    "email": "string",
    "zipCode": int,
    "hubspotID": int
},
"course": {
    "id": "string",
    "title": "string",
    "platform": {
        "slug": "string",
        "name": "string"
    }
},
"startDate": "2022-01-09T23:59:00.000Z",
"endDate": "2022-02-09T23:59:00.000Z",
"hubspotDealId": int

}

I can't get the ID in this JSON since those information are provided by a Puppeteer APP, or I should do 3 request to check if the related entity exist first, which is not adviced I think.

I also tried to change the identifier on the user, course and platform but In both cases, I've duplicate entries in database


Solution

  • I manage to do what I want with a custom denormalizer.

    So I can post and update data comming from tierce source without ID.

    class SessionDenormalizer implements DenormalizerAwareInterface, ContextAwareDenormalizerInterface
    

    { use DenormalizerAwareTrait;

    public function __construct(
        private UserRepository     $userRepository,
        private PlatformRepository $platformRepository,
        private CourseRepository   $courseRepository,
        private SessionRepository  $sessionRepository,
    )
    {
    }
    
    private const ALREADY_CALLED = 'SESSION_DENORMALIZER_ALREADY_CALLED';
    
    public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
    {
        if (isset($context[self::ALREADY_CALLED])) {
            return false;
        }
        return $type === Session::class;
    }
    
    public function denormalize($data, string $type, string $format = null, array $context = [])
    {
    
        if (isset(
            $data["user"]["email"],
            $data["course"]["slug"],
            $data["course"]["platform"]["slug"],
        )) {
    
            $user = $this->userRepository->findOneBy(["email" => $data["user"]["email"]]);
            $course = $this->courseRepository->findOneBy(["slug" => $data["course"]["slug"]]);
            $platform = $this->platformRepository->findOneBy(["slug" => $data["course"]["platform"]["slug"]]);
    
            if ($user && $course && $platform) {
                $data["user"]["@id"] = "/v1/users/" . $user?->getId();
                $data["course"]["@id"] = "/v1/courses/" . $course?->getId();
                $data["course"]["platform"]["@id"] = "/v1/platforms/" . $platform?->getId();
    
                $session = $this->sessionRepository->findOneBy(["cpfID" => $data["cpfID"]]);
                if($session) {
                    $data["@id"] = "/v1/sessions/" . $session->getId();
                    if(isset($context["collection_operation_name"])) {
                        $context["collection_operation_name"] = "put";
                    }
                    if(isset($context['api_allow_update'])) {
                        $context['api_allow_update'] = true;
                    }
                }
            }
        }
    
        $context[self::ALREADY_CALLED] = true;
        return $this->denormalizer->denormalize($data , $type , $format , $context);
    } 
    }
    

    Services.yaml :

    'app.session.denormalizer.json':
        class: 'App\Serializer\Denormalizer\SessionDenormalizer'
        tags:
          - { name: 'serializer.normalizer', priority: 64 }