For a week have I been reading Symfony source and trying some tricks to get it work (on my project and without installing a third party bundle: not for that functionality) and I finally got one. I used CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)... Which works in three steps:
1. Define build
method in bundle
I choosed AppBundle
because it is my first bundle to load in app/AppKernel.php
.
src/AppBundle/AppBundle.php
<?php
namespace AppBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AppCompilerPass());
}
}
2. Write your custom CompilerPass
Symfony serializers are all under the serializer
service. So I just fetched it and added to it a configurator
option, in order to catch its instanciation.
src/AppBundle/AppCompilerPass.php
<?php
namespace AppBundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class AppCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container
->getDefinition('serializer')
->setConfigurator([
new Reference(AppConfigurer::class), 'configureNormalizer'
]);
}
}
3. Write your configurer...
Here, you create a class following what you wrote in the custom CompilerPass (I choosed AppConfigurer
)... A class with an instance method named after what you choosed in the custom compiler pass (I choosed configureNormalizer
).
This method will be called when the symfony internal serializer will be created.
The symfony serializer contains normalizers and decoders and such things as private/protected properties. That is why I used PHP's \Closure::bind
method to scope the symfony serializer as $this
into my lambda-like function (PHP Closure).
Then a loop through the nomalizers ($this->normalizers
) help customize their behaviours. Actually, not all of those nomalizers need circular reference handlers (like DateTimeNormalizer
): the reason of the condition there.
src/AppBundle/AppConfigurer.php
<?php
namespace AppBundle;
class AppConfigurer
{
public function configureNormalizer($normalizer)
{
\Closure::bind(function () use (&$normalizer)
{
foreach ($this->normalizers as $normalizer)
if (method_exists($normalizer, 'setCircularReferenceHandler'))
$normalizer->setCircularReferenceHandler(function ($object)
{
return $object->getId();
});
}, $normalizer, $normalizer)();
}
}
Conclusion
As said earlier, I did it for my project since I dind't wanted FOSRestBundle nor any third party bundle as I've seen over Internet as a solution: not for that part (may be for security). My controllers now stand as...
<?php
namespace StoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ProductController extends Controller
{
/**
*
* @Route("/products")
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$data = $em->getRepository('StoreBundle:Product')->findAll();
return $this->json(['data' => $data]);
}
/**
*
* @Route("/product")
* @Method("POST")
*
*/
public function newAction()
{
throw new \Exception('Method not yet implemented');
}
/**
*
* @Route("/product/{id}")
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$data = $em->getRepository('StoreBundle:Product')->findById($id);
return $this->json(['data' => $data]);
}
/**
*
* @Route("/product/{id}/update")
* @Method("PUT")
*
*/
public function updateAction($id)
{
throw new \Exception('Method not yet implemented');
}
/**
*
* @Route("/product/{id}/delete")
* @Method("DELETE")
*
*/
public function deleteAction($id)
{
throw new \Exception('Method not yet implemented');
}
}
\Symfony\Component\Serializer\Normalizer\AbstractNormalizer::setCircularReferenceHandler
is deprecated since Symfony 4.2. You should use thecircular_reference_handler
key of the context:$this->defaultContext['circular_reference_handler'] = function ($object) { return $object->getName(); };
– Avast