minor #16547 [Serializer] Improve ObjectNormalizer performance (dunglas)

This PR was squashed before being merged into the 2.8 branch (closes #16547).

Discussion
----------

[Serializer] Improve ObjectNormalizer performance

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #16179
| License       | MIT
| Doc PR        | n/a

Cache attributes detection in a similar way than in #16294 for PropertyAccess.

As for the PropertyAccess Component, I'll open another PR (in 2.8 or 3.1) allowing to cache these attributes between requests using Doctrine Cache.

@Tobion, can you try this PR with your benchmark to estimate the gain?

Commits
-------

683f0f7 [Serializer] Improve ObjectNormalizer performance
This commit is contained in:
Fabien Potencier 2015-11-28 11:40:26 +01:00
commit 2659c8e3a8
2 changed files with 76 additions and 37 deletions

View File

@ -26,6 +26,8 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
*/
class ObjectNormalizer extends AbstractNormalizer
{
private static $attributesCache = array();
/**
* @var PropertyAccessorInterface
*/
@ -58,42 +60,7 @@ class ObjectNormalizer extends AbstractNormalizer
}
$data = array();
$attributes = $this->getAllowedAttributes($object, $context, true);
// If not using groups, detect manually
if (false === $attributes) {
$attributes = array();
// methods
$reflClass = new \ReflectionClass($object);
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
if (
!$reflMethod->isStatic() &&
!$reflMethod->isConstructor() &&
!$reflMethod->isDestructor() &&
0 === $reflMethod->getNumberOfRequiredParameters()
) {
$name = $reflMethod->getName();
if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) {
// getters and hassers
$attributes[lcfirst(substr($name, 3))] = true;
} elseif (strpos($name, 'is') === 0) {
// issers
$attributes[lcfirst(substr($name, 2))] = true;
}
}
}
// properties
foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
if (!$reflProperty->isStatic()) {
$attributes[$reflProperty->getName()] = true;
}
}
$attributes = array_keys($attributes);
}
$attributes = $this->getAttributes($object, $context);
foreach ($attributes as $attribute) {
if (in_array($attribute, $this->ignoredAttributes)) {
@ -162,4 +129,64 @@ class ObjectNormalizer extends AbstractNormalizer
return $object;
}
/**
* Gets and caches attributes for this class and context.
*
* @param object $object
* @param array $context
*
* @return array
*/
private function getAttributes($object, array $context)
{
$key = sprintf('%s-%s', get_class($object), serialize($context));
if (isset(self::$attributesCache[$key])) {
return self::$attributesCache[$key];
}
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
if (false !== $allowedAttributes) {
return self::$attributesCache[$key] = $allowedAttributes;
}
// If not using groups, detect manually
$attributes = array();
// methods
$reflClass = new \ReflectionClass($object);
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
if (
$reflMethod->getNumberOfRequiredParameters() !== 0 ||
$reflMethod->isStatic() ||
$reflMethod->isConstructor() ||
$reflMethod->isDestructor()
) {
continue;
}
$name = $reflMethod->getName();
if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) {
// getters and hassers
$attributes[lcfirst(substr($name, 3))] = true;
} elseif (strpos($name, 'is') === 0) {
// issers
$attributes[lcfirst(substr($name, 2))] = true;
}
}
// properties
foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
if ($reflProperty->isStatic()) {
continue;
}
$attributes[$reflProperty->getName()] = true;
}
return self::$attributesCache[$key] = array_keys($attributes);
}
}

View File

@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ObjectNormalizerTest
* @var ObjectNormalizer
*/
private $normalizer;
/**
@ -239,6 +239,18 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($obj, $normalized);
}
public function testNormalizeNoPropertyInGroup()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->normalizer = new ObjectNormalizer($classMetadataFactory);
$this->normalizer->setSerializer($this->serializer);
$obj = new GroupDummy();
$obj->setFoo('foo');
$this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
}
public function testGroupsNormalizeWithNameConverter()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));