Skip to content

Commit 127538e

Browse files
committed
feature symfony#16917 [PropertyInfo] Cache support (dunglas)
This PR was squashed before being merged into the 3.1-dev branch (closes symfony#16917). Discussion ---------- [PropertyInfo] Cache support | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#16893 | License | MIT | Doc PR | todo Replace symfony#16893 with several advantages: - Less complex patch - Work for all usages of PropertyInfo (even outside of the Serializer) - Avoid a circular reference between Serializer's CallMetadataFactory and PropertyInfo's SerializerExtractor - Allow @mihai-stancu's symfony#16143 to work as-is Commits ------- 86c20df [PropertyInfo] Cache support
2 parents 712eeed + 86c20df commit 127538e

File tree

6 files changed

+292
-55
lines changed

6 files changed

+292
-55
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
16+
/**
17+
* Adds a PSR-6 cache layer on top of an extractor.
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
22+
{
23+
/**
24+
* @var PropertyInfoExtractorInterface
25+
*/
26+
private $propertyInfoExtractor;
27+
28+
/**
29+
* @var CacheItemPoolInterface
30+
*/
31+
private $cacheItemPool;
32+
33+
/**
34+
* @var array
35+
*/
36+
private $arrayCache = array();
37+
38+
public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool)
39+
{
40+
$this->propertyInfoExtractor = $propertyInfoExtractor;
41+
$this->cacheItemPool = $cacheItemPool;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function isReadable($class, $property, array $context = array())
48+
{
49+
return $this->extract('isReadable', array($class, $property, $context));
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function isWritable($class, $property, array $context = array())
56+
{
57+
return $this->extract('isWritable', array($class, $property, $context));
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function getShortDescription($class, $property, array $context = array())
64+
{
65+
return $this->extract('getShortDescription', array($class, $property, $context));
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
public function getLongDescription($class, $property, array $context = array())
72+
{
73+
return $this->extract('getLongDescription', array($class, $property, $context));
74+
}
75+
76+
/**
77+
* {@inheritdoc}
78+
*/
79+
public function getProperties($class, array $context = array())
80+
{
81+
return $this->extract('getProperties', array($class, $context));
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
public function getTypes($class, $property, array $context = array())
88+
{
89+
return $this->extract('getTypes', array($class, $context));
90+
}
91+
92+
/**
93+
* Retrieves the cached data if applicable or delegates to the decorated extractor.
94+
*
95+
* @param string $method
96+
* @param array $arguments
97+
*
98+
* @return mixed
99+
*/
100+
private function extract($method, array $arguments)
101+
{
102+
try {
103+
$serializedArguments = serialize($arguments);
104+
} catch (\Exception $exception) {
105+
// If arguments are not serializable, skip the cache
106+
return call_user_func_array(array($this->propertyInfoExtractor, $method), $arguments);
107+
}
108+
109+
$key = $this->escape($method.'.'.$serializedArguments);
110+
111+
if (isset($this->arrayCache[$key])) {
112+
return $this->arrayCache[$key];
113+
}
114+
115+
$item = $this->cacheItemPool->getItem($key);
116+
117+
if ($item->isHit()) {
118+
return $this->arrayCache[$key] = $item->get();
119+
}
120+
121+
$value = call_user_func_array(array($this->propertyInfoExtractor, $method), $arguments);
122+
$item->set($value);
123+
$this->cacheItemPool->save($item);
124+
125+
return $this->arrayCache[$key] = $value;
126+
}
127+
128+
/**
129+
* Escapes a key according to PSR-6.
130+
*
131+
* Replaces characters forbidden by PSR-6 and the _ char by the _ char followed by the ASCII
132+
* code of the escaped char.
133+
*
134+
* @param string $key
135+
*
136+
* @return string
137+
*/
138+
private function escape($key)
139+
{
140+
return strtr($key, array(
141+
'{' => '_123',
142+
'}' => '_125',
143+
'(' => '_40',
144+
')' => '_41',
145+
'/' => '_47',
146+
'\\' => '_92',
147+
'@' => '_64',
148+
':' => '_58',
149+
'_' => '_95',
150+
));
151+
}
152+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo\Tests;
13+
14+
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
15+
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
16+
use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor;
17+
use Symfony\Component\PropertyInfo\Type;
18+
19+
/**
20+
* @author Kévin Dunglas <[email protected]>
21+
*/
22+
class AbstractPropertyInfoExtractorTest extends \PHPUnit_Framework_TestCase
23+
{
24+
/**
25+
* @var PropertyInfoExtractor
26+
*/
27+
protected $propertyInfo;
28+
29+
public function setUp()
30+
{
31+
$extractors = array(new NullExtractor(), new DummyExtractor());
32+
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors);
33+
}
34+
35+
public function testInstanceOf()
36+
{
37+
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface', $this->propertyInfo);
38+
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->propertyInfo);
39+
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface', $this->propertyInfo);
40+
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface', $this->propertyInfo);
41+
}
42+
43+
public function testGetShortDescription()
44+
{
45+
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array()));
46+
}
47+
48+
public function testGetLongDescription()
49+
{
50+
$this->assertSame('long', $this->propertyInfo->getLongDescription('Foo', 'bar', array()));
51+
}
52+
53+
public function testGetTypes()
54+
{
55+
$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_INT)), $this->propertyInfo->getTypes('Foo', 'bar', array()));
56+
}
57+
58+
public function testIsReadable()
59+
{
60+
$this->assertTrue($this->propertyInfo->isReadable('Foo', 'bar', array()));
61+
}
62+
63+
public function testIsWritable()
64+
{
65+
$this->assertTrue($this->propertyInfo->isWritable('Foo', 'bar', array()));
66+
}
67+
68+
public function testGetProperties()
69+
{
70+
$this->assertEquals(array('a', 'b'), $this->propertyInfo->getProperties('Foo'));
71+
}
72+
}

src/Symfony/Component/PropertyInfo/Tests/Extractors/SerializerExtractorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\Component\PropertyInfo\PropertyInfo\Tests\Extractors;
12+
namespace Symfony\Component\PropertyInfo\Tests\Extractors;
1313

1414
use Doctrine\Common\Annotations\AnnotationReader;
1515
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo\Tests;
13+
14+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
15+
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
16+
17+
/**
18+
* @author Kévin Dunglas <[email protected]>
19+
*/
20+
class PropertyInfoCacheExtractorTest extends AbstractPropertyInfoExtractorTest
21+
{
22+
public function setUp()
23+
{
24+
parent::setUp();
25+
26+
$this->propertyInfo = new PropertyInfoCacheExtractor($this->propertyInfo, new ArrayAdapter());
27+
}
28+
29+
public function testCache()
30+
{
31+
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array()));
32+
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array()));
33+
}
34+
35+
public function testNotSerializableContext()
36+
{
37+
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array('foo' => function () {})));
38+
}
39+
40+
/**
41+
* @dataProvider escapeDataProvider
42+
*/
43+
public function testEscape($toEscape, $expected)
44+
{
45+
$reflectionMethod = new \ReflectionMethod($this->propertyInfo, 'escape');
46+
$reflectionMethod->setAccessible(true);
47+
48+
$this->assertSame($expected, $reflectionMethod->invoke($this->propertyInfo, $toEscape));
49+
}
50+
51+
public function escapeDataProvider()
52+
{
53+
return array(
54+
array('foo_bar', 'foo_95bar'),
55+
array('foo_95bar', 'foo_9595bar'),
56+
array('foo{bar}', 'foo_123bar_125'),
57+
array('foo(bar)', 'foo_40bar_41'),
58+
array('foo/bar', 'foo_47bar'),
59+
array('foo\bar', 'foo_92bar'),
60+
array('foo@bar', 'foo_64bar'),
61+
array('foo:bar', 'foo_58bar'),
62+
);
63+
}
64+
}

src/Symfony/Component/PropertyInfo/Tests/PropertyInfoExtractorTest.php

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,9 @@
1111

1212
namespace Symfony\Component\PropertyInfo\Tests;
1313

14-
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
15-
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
16-
use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor;
17-
use Symfony\Component\PropertyInfo\Type;
18-
1914
/**
2015
* @author Kévin Dunglas <[email protected]>
2116
*/
22-
class PropertyInfoExtractorTest extends \PHPUnit_Framework_TestCase
17+
class PropertyInfoExtractorTest extends AbstractPropertyInfoExtractorTest
2318
{
24-
/**
25-
* @var PropertyInfoExtractor
26-
*/
27-
private $propertyInfo;
28-
29-
public function setUp()
30-
{
31-
$extractors = array(new NullExtractor(), new DummyExtractor());
32-
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors);
33-
}
34-
35-
public function testInstanceOf()
36-
{
37-
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface', $this->propertyInfo);
38-
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->propertyInfo);
39-
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface', $this->propertyInfo);
40-
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface', $this->propertyInfo);
41-
}
42-
43-
public function testGetShortDescription()
44-
{
45-
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array()));
46-
}
47-
48-
public function testGetLongDescription()
49-
{
50-
$this->assertSame('long', $this->propertyInfo->getLongDescription('Foo', 'bar', array()));
51-
}
52-
53-
public function testGetTypes()
54-
{
55-
$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_INT)), $this->propertyInfo->getTypes('Foo', 'bar', array()));
56-
}
57-
58-
public function testIsReadable()
59-
{
60-
$this->assertTrue($this->propertyInfo->isReadable('Foo', 'bar', array()));
61-
}
62-
63-
public function testIsWritable()
64-
{
65-
$this->assertTrue($this->propertyInfo->isWritable('Foo', 'bar', array()));
66-
}
67-
68-
public function testGetProperties()
69-
{
70-
$this->assertEquals(array('a', 'b'), $this->propertyInfo->getProperties('Foo'));
71-
}
7219
}

src/Symfony/Component/PropertyInfo/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
},
2828
"require-dev": {
2929
"symfony/serializer": "~2.8|~3.0",
30+
"symfony/cache": "~3.1",
3031
"phpdocumentor/reflection": "^1.0.7",
3132
"doctrine/annotations": "~1.0"
3233
},
3334
"conflict": {
3435
"phpdocumentor/reflection": "<1.0.7"
3536
},
3637
"suggest": {
38+
"psr/cache-implementation": "To cache results",
3739
"symfony/doctrine-bridge": "To use Doctrine metadata",
3840
"phpdocumentor/reflection": "To use the PHPDoc",
3941
"symfony/serializer": "To use Serializer metadata"

0 commit comments

Comments
 (0)