8000 [Serializer] Add PSR-6 adapter by dunglas · Pull Request #17446 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Serializer] Add PSR-6 adapter #17446

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

Closed
wants to merge 4 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
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;
}
}
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

What about making this trait internal?

Copy link
Member Author

Choose a reason for hiding this comment

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

It can be useful for a userland MetadataFactory (like a Doctrine Cache one) what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case they could use the ClassUtils of doctrine/common.
IMO the behaviour of this trait is easily reproductible and should not be accessible publicly (at least in a trait which is not related directly to class management).

{
/**
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -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');
Copy link
Contributor

Choose a reason for hiding this comment

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

::class should be used

$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');
}
}
Original file line number Diff line number Diff line change
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
6062
Original file line number Diff line number Diff line change
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