Skip to content

[WIP] JMS Serializer Bundle Integration #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Annotation/FormType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* SimpleThings FormSerializerBundle
*
* LICENSE
*
* This source file is subject to the MIT license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so I can send you a copy immediately.
*/

namespace SimpleThings\FormSerializerBundle\Annotation;

/**
* Register form type that is responsible for serializing the annotated entity.
*
* @Annotation
* @Target("CLASS")
*/
class FormType
{
/**
* Class or form-type name that is registered in Form Factory.
*
* @var string
*/
private $type;

public function __construct(array $values)
{
if (isset($values['value'])) {
$this->type = $values['value'];
}
}

public function getType()
{
return $this->type;
}
}

2 changes: 1 addition & 1 deletion Serializer/FormSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ public function serialize($object, $typeBuilder, $format)
if (($typeBuilder instanceof FormTypeInterface) || is_string($typeBuilder)) {
$form = $this->factory->create($typeBuilder, $object);
} else if ($typeBuilder instanceof FormBuilderInterface) {
$typeBuilder->setData($object);
$form = $typeBuilder->getForm();
$form->setData($object);
} else if ($typeBuilder instanceof FormInterface) {
$form = $typeBuilder;
if ( ! $form->isBound()) {
Expand Down
117 changes: 117 additions & 0 deletions Serializer/JMS/FormMetadataDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace SimpleThings\FormSerializerBundle\Serializer\JMS;

use JMS\SerializerBundle\Metadata\ClassMetadata;
use JMS\SerializerBundle\Metadata\PropertyMetadata;
use JMS\SerializerBundle\Metadata\VirtualPropertyMetadata;

use SimpleThings\FormSerializerBundle\Annotation\FormType;

use Metadata\Driver\DriverInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Doctrine\Common\Annotations\Reader;

class FormMetadataDriver implements DriverInterface
{
private $reader;
private $formFactory;

public function __construct(Reader $reader, FormFactoryInterface $formFactory)
{
$this->reader = $reader;
$this->formFactory = $formFactory;
}

public function loadMetadataForClass(\ReflectionClass $class)
{
$classMetadata = new ClassMetadata($name = $class->getName());

foreach ($this->reader->getClassAnnotations($class) as $annot) {
if ($annot instanceof FormType) {
$type = $annot->getType();

if (class_exists($type)) {
$type = new $type;
}

$form = $this->formFactory->create($type, null, array());
$options = $form->getConfig()->getOptions();

$classMetadata->xmlRootName = $options['serialize_xml_name'];

$propertiesMetadata = array();
foreach ($form->getChildren() as $children) {
$childOptions = $children->getConfig()->getOptions();
$type = $children->getConfig()->getType();

$property = $class->getProperty($children->getName());
$propertyMetadata = new PropertyMetadata($name, $property->getName());
#$propertyMetadata->setAccessor('public_method', null, null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the form component uses getter/setters to access properties, jms serializer uses reflection by default. The line was an attempt to synchronize behavior, however jms serailizer doesnt detect public properties and therefore fails when using "public_method" with my test objects.


if (!empty($childOptions['serialize_name'])) {
$propertyMetadata->serializedName = $childOptions['serialize_name'];
}

if ($type->getName() == "collection") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about child types of the collection type ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know of any? The problem is that detection of custom form types is very hard in this context and the collection type is very special.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a custom type extending collection in one of my projects (not the project for which I could use this bundle soon though)

$propertyMetadata->xmlCollection = true;
$propertyMetadata->xmlCollectionInline = $childOptions['serialize_xml_inline'];

if ( ! empty($childOptions['serialize_xml_name'])) {
$propertyMetadata->xmlEntryName = $childOptions['serialize_xml_name'];
}

$subForm = $this->formFactory->create($childOptions['type']);

$propertyMetadata->type = sprintf('array<%s>', $this->translateType($subForm));
} else if ($type->getName() == "choice") {
$propertyMetadata->type = $childOptions['multiple'] ? "array<string>" : "string";
} else if ($type->getName() == "entity") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly the same here. you consider entity by duplicating the case of choice but forget other children (document, couchdb_document, phpcr_document or propel1_model)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, this even concerns some core types: country and locale extend choice

$propertyMetadata->type = $childOptions['multiple'] ? "array<string>" : "string";
} else {
$propertyMetadata->type = $this->translateType($children);
}

if ($childOptions['serialize_xml_attribute']) {
$propertyMetadata->xmlAttribute = true;
} else if ($childOptions['serialize_xml_value']) {
$propertyMetadata->xmlValue = true;
}

if ($childOptions['disabled']) {
$propertyMetadata->readOnly = true;
}

$classMetadata->addPropertyMetadata($propertyMetadata);
}
}
}

return $classMetadata;
}

private function translateType($form)
{
$options = $form->getConfig()->getOptions();
if ($options['data_class']) {
return $options['data_class'];
}

switch ($form->getConfig()->getType()->getName()) {
case 'date':
case 'datetime':
case 'time':
case 'birthday';
return 'DateTime';
case 'number':
return 'double';
case 'checkbox':
return 'boolean';
case 'integer';
return 'integer';
default:
return 'string';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the integer type ?

}
}
}

2 changes: 2 additions & 0 deletions Tests/Serializer/Fixture/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
namespace SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture;

use JMS\SerializerBundle\Annotation as JMS;
use SimpleThings\FormSerializerBundle\Annotation\FormType;

/**
* @JMS\ExclusionPolicy("none")
* @FormType("SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\AddressType")
*/
class Address
{
Expand Down
2 changes: 2 additions & 0 deletions Tests/Serializer/Fixture/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture;

use JMS\SerializerBundle\Annotation as JMS;
use SimpleThings\FormSerializerBundle\Annotation\FormType;

/**
* @JMS\ExclusionPolicy("none")
* @FormType("SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\UserType")
*/
class User
{
Expand Down
73 changes: 73 additions & 0 deletions Tests/Serializer/JmsFormMetadataDriverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace SimpleThings\FormSerializerBundle\Tests\Serializer;

use SimpleThings\FormSerializerBundle\Tests\TestCase;
use SimpleThings\FormSerializerBundle\Serializer\JMS\FormMetadataDriver;

use SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\User;
use SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\Address;
use SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\UserType;
use SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\AddressType;

class JmsFormMetadataDriverTest extends TestCase
{
public function testLoadMetadata()
{
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$driver = new FormMetadataDriver($reader, $this->createFormFactory());

$reflClass = new \ReflectionClass('SimpleThings\FormSerializerBundle\Tests\Serializer\Fixture\User');
$metadata = $driver->loadMetadataForClass($reflClass);

$this->assertInstanceOf('JMS\SerializerBundle\Metadata\ClassMetadata', $metadata);
$this->assertEquals(array('username', 'email', 'birthday', 'country', 'address', 'addresses'), array_keys($metadata->propertyMetadata));
}

public function testSerialize()
{
$serializer = $this->createJmsSerializer(true);

$address = new Address();
$address->street = "Somestreet 1";
$address->zipCode = 12345;
$address->city = "Bonn";

$user = new User();
$user->username = "beberlei";
$user->email = "[email protected]";
$user->birthday = new \DateTime("1984-03-18");
$user->gender = 'male';
$user->interests = array('sport', 'reading');
$user->country = "DE";
$user->address = $address;
$user->addresses = array($address, $address);

$xml = $serializer->serialize($user, 'xml');

$this->assertEquals(<<<XML
<?xml version="1.0" encoding="UTF-8"?>
<user>
<username><![CDATA[beberlei]]></username>
<email><![CDATA[[email protected]]]></email>
<birthday>1984-03-18T00:00:00+0100</birthday>
<country><![CDATA[DE]]></country>
<address street="Somestreet 1" zip_code="12345" city="Bonn"/>
<addresses>
<address street="Somestreet 1" zip_code="12345" city="Bonn"/>
<address street="Somestreet 1" zip_code="12345" city="Bonn"/>
</addresses>
</user>

XML
, $xml);

$json = $serializer->serialize($user, 'json');

$this->assertEquals(<<<JSON
{"username":"beberlei","email":"[email protected]","birthday":"1984-03-18T00:00:00+0100","country":"DE","address":{"street":"Somestreet 1","zip_code":12345,"city":"Bonn"},"addresses":[{"street":"Somestreet 1","zip_code":12345,"city":"Bonn"},{"street":"Somestreet 1","zip_code":12345,"city":"Bonn"}]}
JSON
, $json);
}
}

11 changes: 9 additions & 2 deletions Tests/Serializer/PerformanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ public function testSerializeList20Elements()

$start = microtime(true);
$xml = $formSerializer->serializeList($list, new UserType(), 'xml', 'users');
echo number_format(microtime(true) - $start, 4) . "\n";
echo "Form Serializer: " . number_format(microtime(true) - $start, 4) . "\n";

#echo $this->formatXml($xml);

$jmsSerializer = $this->createJmsSerializer();
$xml = $jmsSerializer->serialize($list, 'xml');
$start = microtime(true);
$xml = $jmsSerializer->serialize($list, 'xml');
echo number_format(microtime(true) - $start, 4) . "\n";
echo "JMS Annotations: " . number_format(microtime(true) - $start, 4) . "\n";

#echo $this->formatXml($xml);

$jmsSerializer = $this->createJmsSerializer(true);
$xml = $jmsSerializer->serialize($list, 'xml');
$start = microtime(true);
$xml = $jmsSerializer->serialize($list, 'xml');
echo "JMS with Form Metadata: " . number_format(microtime(true) - $start, 4) . "\n";
}
}

27 changes: 17 additions & 10 deletions Tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function createFormSerializer(SerializerOptions $options = null)
return $formSerializer;
}

public function createJmsSerializer()
public function createJmsSerializer($forms = false)
{
$namingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
$objectConstructor = new UnserializeObjectConstructor();
Expand All @@ -83,19 +83,26 @@ public function createJmsSerializer()
'xml' => new XmlDeserializationVisitor($namingStrategy, $customDeserializationHandlers, $objectConstructor),
);

$factory = $this->createJmsMetadataFactory();
$factory = $this->createJmsMetadataFactory($forms);
return new JMSSerializer($factory, $serializationVisitors, $deserializationVisitors);
}

public function createJmsMetadataFactory()
public function createJmsMetadataFactory($forms = false)
{
$fileLocator = new \Metadata\Driver\FileLocator(array());
$driver = new \Metadata\Driver\DriverChain(array(
new \JMS\SerializerBundle\Metadata\Driver\YamlDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\XmlDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\PhpDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\AnnotationDriver(new \Doctrine\Common\Annotations\AnnotationReader())
));
if ($forms) {
$driver = new \SimpleThings\FormSerializerBundle\Serializer\JMS\FormMetadataDriver(
new \Doctrine\Common\Annotations\AnnotationReader(),
$this->createFormFactory()
);
} else {
$driver = new \Metadata\Driver\DriverChain(array(
new \JMS\SerializerBundle\Metadata\Driver\YamlDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\XmlDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\PhpDriver($fileLocator),
new \JMS\SerializerBundle\Metadata\Driver\AnnotationDriver(new \Doctrine\Common\Annotations\AnnotationReader())
));
}
return new MetadataFactory($driver);
}

Expand Down Expand Up @@ -175,7 +182,7 @@ protected function formatJson($json)
}
default:
$new_json .= $char;
break;
break;
}
}

Expand Down
5 changes: 4 additions & 1 deletion Tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
);
}

spl_autoload_register(function($class) {
$bundleLoader = (function($class) {
if (0 === strpos($class, 'SimpleThings\\FormSerializerBundle\\')) {
$path = __DIR__.'/../'.implode('/', array_slice(explode('\\', $class), 2)).'.php';
if (!stream_resolve_include_path($path)) {
Expand All @@ -19,5 +19,8 @@
return true;
}
});
spl_autoload_register($bundleLoader);

Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($bundleLoader);

4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.