Skip to content

Commit

Permalink
[Serializer] Add PSR-6 adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas authored and fabpot committed Jan 27, 2016
1 parent c5a7886 commit 4bf9d05
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 35 deletions.
@@ -0,0 +1,68 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Mapping\Factory;

use Psr\Cache\CacheItemPoolInterface;

/**
* Caches metadata using a PSR-6 implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CacheClassMetadataFactory implements ClassMetadataFactoryInterface
{
use ClassResolverTrait;

/**
* @var ClassMetadataFactoryInterface
*/
private $decorated;

/**
* @var CacheItemPoolInterface
*/
private $cacheItemPool;

public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemPoolInterface $cacheItemPool)
{
$this->decorated = $decorated;
$this->cacheItemPool = $cacheItemPool;
}

/**
* {@inheritdoc}
*/
public function getMetadataFor($value)
{
$class = $this->getClass($value);
// Key cannot contain backslashes according to PSR-6
$key = strtr($class, '\\', '_');

$item = $this->cacheItemPool->getItem($key);
if ($item->isHit()) {
return $item->get();
}

$metadata = $this->decorated->getMetadataFor($value);
$this->cacheItemPool->save($item->set($metadata));

return $metadata;
}

/**
* {@inheritdoc}
*/
public function hasMetadataFor($value)
{
return $this->decorated->hasMetadataFor($value);
}
}
Expand Up @@ -23,6 +23,8 @@
*/
class ClassMetadataFactory implements ClassMetadataFactoryInterface
{
use ClassResolverTrait;

/**
* @var LoaderInterface
*/
Expand All @@ -44,6 +46,10 @@ public function __construct(LoaderInterface $loader, Cache $cache = null)
{
$this->loader = $loader;
$this->cache = $cache;

if (null !== $cache) {
@trigger_error(sprintf('Passing a Doctrine Cache instance as 2nd parameter of the "%s" constructor is deprecated. This parameter will be removed in Symfony 4.0. Use the "%s" class instead.', __CLASS__, CacheClassMetadataFactory::class), E_USER_DEPRECATED);
}
}

/**
Expand All @@ -52,9 +58,6 @@ public function __construct(LoaderInterface $loader, Cache $cache = null)
public function getMetadataFor($value)
{
$class = $this->getClass($value);
if (!$class) {
throw new InvalidArgumentException(sprintf('Cannot create metadata for non-objects. Got: "%s"', gettype($value)));
}

if (isset($this->loadedClasses[$class])) {
return $this->loadedClasses[$class];
Expand All @@ -64,10 +67,6 @@ public function getMetadataFor($value)
return $this->loadedClasses[$class];
}

if (!class_exists($class) && !interface_exists($class)) {
throw new InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $class));
}

$classMetadata = new ClassMetadata($class);
$this->loader->loadClassMetadata($classMetadata);

Expand Down Expand Up @@ -95,24 +94,14 @@ public function getMetadataFor($value)
*/
public function hasMetadataFor($value)
{
$class = $this->getClass($value);

return class_exists($class) || interface_exists($class);
}
try {
$this->getClass($value);

/**
* Gets a class name for a given class or instance.
*
* @param mixed $value
*
* @return string|bool
*/
private function getClass($value)
{
if (!is_object($value) && !is_string($value)) {
return false;
return true;
} catch (InvalidArgumentException $invalidArgumentException) {
// Return false in case of exception
}

return ltrim(is_object($value) ? get_class($value) : $value, '\\');
return false;
}
}
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Mapping\Factory;

use Symfony\Component\Serializer\Exception\InvalidArgumentException;

/**
* Resolves a class name.
*
* @internal
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
trait ClassResolverTrait
{
/**
* Gets a class name for a given class or instance.
*
* @param mixed $value
*
* @return string
*
* @throws InvalidArgumentException If the class does not exists
*/
private function getClass($value)
{
if (is_string($value)) {
if (!class_exists($value) && !interface_exists($value)) {
throw new InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $value));
}

return ltrim($value, '\\');
}

if (!is_object($value)) {
throw new InvalidArgumentException(sprintf('Cannot create metadata for non-objects. Got: "%s"', gettype($value)));
}

return get_class($value);
}
}
@@ -0,0 +1,65 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Mapping\Factory;

use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CacheMetadataFactoryTest extends \PHPUnit_Framework_TestCase
{
public function testGetMetadataFor()
{
$metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Dummy');

$decorated = $this->getMock('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface');
$decorated
->expects($this->once())
->method('getMetadataFor')
->will($this->returnValue($metadata))
;

$factory = new CacheClassMetadataFactory($decorated, new ArrayAdapter());

$this->assertEquals($metadata, $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Dummy'));
// The second call should retrieve the value from the cache
$this->assertEquals($metadata, $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Dummy'));
}

public function testHasMetadataFor()
{
$decorated = $this->getMock('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface');
$decorated
->expects($this->once())
->method('hasMetadataFor')
->will($this->returnValue(true))
;

$factory = new CacheClassMetadataFactory($decorated, new ArrayAdapter());

$this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Dummy'));
}

/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
*/
public function testInvalidClassThrowsException()
{
$decorated = $this->getMock('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface');
$factory = new CacheClassMetadataFactory($decorated, new ArrayAdapter());

$factory->getMetadataFor('Not\Exist');
}
}
Expand Up @@ -25,7 +25,7 @@ class ClassMetadataFactoryTest extends \PHPUnit_Framework_TestCase
public function testInterface()
{
$classMetadata = new ClassMetadataFactory(new LoaderChain(array()));
$this->assertInstanceOf('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory', $classMetadata);
$this->assertInstanceOf('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface', $classMetadata);
}

public function testGetMetadataFor()
Expand All @@ -45,6 +45,9 @@ public function testHasMetadataFor()
$this->assertFalse($factory->hasMetadataFor('Dunglas\Entity'));
}

/**
* @group legacy
*/
public function testCacheExists()
{
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
Expand All @@ -58,17 +61,14 @@ public function testCacheExists()
$this->assertEquals('foo', $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy'));
}

/**
* @group legacy
*/
public function testCacheNotExists()
{
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
$cache
->method('fetch')
->will($this->returnValue(false))
;

$cache
->method('save')
;
$cache->method('fetch')->will($this->returnValue(false));
$cache->method('save');

$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()), $cache);
$metadata = $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
Expand Down
8 changes: 5 additions & 3 deletions src/Symfony/Component/Serializer/composer.json
Expand Up @@ -23,16 +23,18 @@
"symfony/config": "~2.8|~3.0",
"symfony/property-access": "~2.8|~3.0",
"symfony/http-foundation": "~2.8|~3.0",
"symfony/cache": "~3.1",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.0"
},
"suggest": {
"doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
"doctrine/cache": "For using the default cached annotation reader and metadata cache.",
"psr/cache-implementation": "For using the metadata cache.",
"symfony/yaml": "For using the default YAML mapping loader.",
"symfony/config": "For using the XML mapping loader.",
"symfony/property-access": "For using the ObjectNormalizer.",
"symfony/http-foundation": "To use the DataUriNormalizer."
"symfony/http-foundation": "To use the DataUriNormalizer.",
"doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
"doctrine/cache": "For using the default cached annotation reader and metadata cache."
},
"autoload": {
"psr-4": { "Symfony\\Component\\Serializer\\": "" },
Expand Down

0 comments on commit 4bf9d05

Please sign in to comment.