Skip to content

Commit 127538e

Browse files
committed
feature #16917 [PropertyInfo] Cache support (dunglas)
This PR was squashed before being merged into the 3.1-dev branch (closes #16917). Discussion ---------- [PropertyInfo] Cache support | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #16893 | License | MIT | Doc PR | todo Replace #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 #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 <fabien@symfony.com>
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 <dunglas@gmail.com>
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 <fabien@symfony.com>
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 <dunglas@gmail.com>
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 <fabien@symfony.com>
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 <dunglas@gmail.com>
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 <dunglas@gmail.com>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy