I have a Dashboard
Entity properly serialized/deserialized by JMSSerializer (through JMSSerializerBundle):
/**
* @ORM\Table(name="dashboard", schema="myappID")
* @ORM\Entity(repositoryClass="Belka\MyBundle\Entity\Repository\DashboardRepository")
*/
class Dashboard
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue("SEQUENCE")
*
* @Serializer\Groups({"o-all", "o-all-getCDashboard", "i-p2-editDashboard"})
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Belka\MyBundle\Entity\User")
*
* @ORM\JoinTable(name="users_dashboards_associated",
* schema="myAppID",
* joinColumns={@ORM\JoinColumn(name="dashboard_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
*
* @Serializer\groups({
* "o-p2-create",
* "i-p2-create",
* "o-p2-patch",
* "i-p2-editDashboard"
* })
*/
protected $users;
}
and I'm using JMSSerializer's jms_serializer.doctrine_object_constructor
as object constructor.
Everything works like charm, but I have the following corner case: sometimes I have to set Dashboard::$users
as a string (i.e. when the client sends a semantic-incorrect users
property, after my checks I return the object along with a string in order to inform it. This is pretty convenient for the front-end apps).
JMSSerializer takes advantage of the Doctrine's annotation, but in this case I really would like to override it programmatically, since is a very corner-case. Two are the ways on my mind:
SerializationContext
to map Dashboard::$users
as a string property?Any piece of suggestion is more than welcome
I've found a solution, although it does not consider nested entities's properties (has-a relations). That would mean visiting the whole graph, but I did not find the time to study the guts of the excellent JMSSSerializer. It works perfectly for forcing the first-level entity's properties though:
first-off, a pre-serialize
subscriber is needed. It will cycle over protected properties and checks if they contain a string. Is so, the type for the serialization will be overridden.
class SerializationSubscriber implements EventSubscriberInterface
{
/**
* @inheritdoc
*/
static public function getSubscribedEvents()
{
return array(
array('event' => 'serializer.pre_serialize', 'method' => 'onPreserialize'),
);
}
public function onPreSerialize(PreSerializeEvent $event)
{
$entity = $event->getObject();
$metadata = $event->getContext()->getMetadataFactory()->getMetadataForClass($event->getType()['name']);
$reflect = new \ReflectionClass($entity);
$props = $reflect->getProperties(\ReflectionProperty::IS_PROTECTED);
foreach ($props as $prop) {
$prop->setAccessible(true);
if (is_string($prop->getValue($entity))) {
// here is the magic
$metadata->propertyMetadata[$prop->name]->type = array('name' => 'string', 'params' => array());
}
}
}
}
Next, I didn't want to listen to this each time I serialize something. This is a corner case within one of my services. We can take advantage of the JMS\Serializer\EventDispatcher\EventDispatcher::addSubscriber
, although the EventDispatcher
service is declared private
.
So, let's turn that service into public
through a compiler pass so as to take advantage of addSubscriber
:
class MyBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideJmsSerializerEventDispatcherDefPass());
}
}
... and let's turn that service into a public
one
class OverrideJmsSerializerEventDispatcherDefPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('jms_serializer.event_dispatcher');
$definition->setPublic(true);
}
}
Hence, we can inject it into our services. I.e. in my services.yml
:
belka.mybundle.dashboardhandler:
class: Belka\MyBundle\Handlers\DashboardHandler
calls:
- [setEventDispatcher, ["@jms_serializer.event_dispatcher"]]
Alright, now we can easily add our subscriber whenever we need to, without the burden of another listener each time my application is performing a serialization:
$serializationSubscriber = new SerializationSubscriber();
$this->eventDispatcher->addSubscriber($serializationSubscriber);
Feel free to complete the answer with a solution that visits the whole Entities' graph. That would be great.