Search code examples
phpsymfonyfosrestbundle

Symfony Serializer with FOSRestBundle?


I'm using Symfony Serializer with FOSRestBundle. So the problems are:

  • it uses snake_case by default
  • I can't set setCircularReferenceHandler callback. I set in services.yml, but the property is equal to null when debugging. And server throws CircularReferenceException.

My config.yml:

framework:
    serializer: { enable_annotations: true }
#.....
fos_rest:
    param_fetcher_listener: true
    body_listener: true
    format_listener:
        rules:
            - { path: '^/oauth', priorities: ['json'], fallback_format: json, prefer_extension: false }
            - { path: '^/api', priorities: ['json'], fallback_format: json, prefer_extension: false }
            - { path: '^/', stop: true }
    view:
        view_response_listener: true
    access_denied_listener:
        json: true
    exception:
        enabled: true
    serializer:
        serialize_null: true

My services.yml:

circular_reference_handler:
    public: false
    class: callback
    factory: [ApiBundle\Serializer\CircularHandlerFactory, getId]

serializer.normalizer.get_set_method:
    class: ApiBundle\Serializer\Normalizer\GetSetMethodNormalizer
    public: false
    tags: [serializer.normalizer]
    calls:
        - method: setCircularReferenceLimit
          arguments: [1]
        - method: setCircularReferenceHandler
          arguments: ['@circular_reference_handler']

Normalizer:

class GetSetMethodNormalizer extends BaseGetSetMethodNormalizer
{
    /**
     * {@inheritdoc}
     */
    public function supportsNormalization($data, $format = null)
    {
        return !$data instanceof \Exception && parent::supportsNormalization($data, $format);
    }
}

CircularHandler:

class CircularHandlerFactory
{
    /**
     * @return \Closure
     */
    public static function getId()
    {
        return function ($object) {
            return $object->getId();
        };
    }
}

Entity:

class PartnerGuaranteeImportArchive
{
    use IdentityBehavior;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=255, unique=true)
     */
    private $fileName;

    /**
     * @var string
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @ORM\Column(type="string", length=255)
     */
    private $originalFileName;

    /**
     * @var string
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @ORM\Column(type="string", length=20)
     */
    private $importStatus;

    /**
     * @var \DateTime
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @Gedmo\Timestampable(on="create")
     *
     * @ORM\Column(type="datetime")
     */
    private $createdAt;

    /**
     * @var \DateTime
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @Gedmo\Timestampable(on="update")
     *
     * @ORM\Column(type="datetime")
     */
    private $updatedAt;

    /**
     * @var string
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @ORM\Column(type="text", nullable=true)
     */
    private $errorMessage;

    /**
     * @var int
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @ORM\Column(type="integer", options={"default": 0})
     */
    private $rows = 0;

    /**
     * @var null
     *
     * @Serializer\Groups({"partnerGuaranteeImportArchive"})
     *
     * @ORM\Column(type="integer", nullable=true)
     */
    private $errorCode = null;

    /**
     * @var PartnerOcamUser
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\PartnerOcamUser", inversedBy="partnerGuaranteeImportArchives")
     * @ORM\JoinColumn(name="partner_ocam_user_id", referencedColumnName="id")
     */
    private $partnerOcamUser;

    // ....
}

Controller action:

/**
 * @Rest\Get(name="api_v1_import_partner_guarantee_archive_index")
 * @Rest\View(statusCode=200, serializerGroups={"partnerGuaranteeImportArchive"})
 *
 * @param Request $request
 * @param PaginationFactory $paginationFactory
 * @return array
 */
public function indexAction(Request $request, PaginationFactory $paginationFactory)
{
    $qb = $this->getDoctrine()->getRepository('AppBundle:PartnerGuaranteeImportArchive')
        ->getByPartnerOcamUser($this->getUser());

    return $paginationFactory
        ->setMaxPage(self::PAGE_COUNT)
        ->createCollection($qb, $request, 'api_v1_import_partner_guarantee_archive_index')
        ->toArray();
}

Solution

  • I've been searching for a long time how to use Symfony Serializer with FOSRestBundle. All tutorials are about JMS Serializer. After searching a while I decided to use custom Symfony Normalizer:

    <?php
    
    namespace ApiBundle\Serializer\Normalizer;
    
    use AppBundle\Entity\PartnerInsuredImportArchive;
    use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
    use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
    use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
    
    class PartnerInsuredImportArchiveNormalizer implements NormalizerInterface, NormalizerAwareInterface
    {
        use NormalizerAwareTrait;
    
        /**
         * @param PartnerInsuredImportArchive $object
         * @param string|null $format
         * @param array $context
         * @return array
         */
        public function normalize($object, $format = null, array $context = [])
        {
            return [
                'id' => $object->getId(),
                'originalFileName' => $object->getOriginalFileName(),
                'importStatus' => $object->getImportStatus(),
                'rows' => $object->getRows(),
                'errorCode' => $object->getErrorCode(),
                'errorMessage' => $object->getErrorMessage(),
                'createdAt' => $this->normalizer->normalize($object->getCreatedAt()),
                'updatedAt' => $this->normalizer->normalize($object->getUpdatedAt()),
            ];
        }
    
        /**
         * {@inheritdoc}
         */
        public function supportsNormalization($data, $format = null)
        {
            return $data instanceof PartnerInsuredImportArchive;
        }
    }