diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 7a220077e5..b2fc68dc1c 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1479,6 +1479,16 @@ class Request return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE')); } + /** + * Checks whether the method is cacheable or not. + * + * @return bool + */ + public function isMethodCacheable() + { + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + /** * Returns the request body content. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index f90a368775..0c54c16b29 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1948,6 +1948,32 @@ class RequestTest extends \PHPUnit_Framework_TestCase array('CONNECT', false), ); } + + /** + * @dataProvider methodCacheableProvider + */ + public function testMethodCacheable($method, $chacheable) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($chacheable, $request->isMethodCacheable()); + } + + public function methodCacheableProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', false), + array('PATCH', false), + array('DELETE', false), + array('PURGE', false), + array('OPTIONS', false), + array('TRACE', false), + array('CONNECT', false), + ); + } } class RequestContentProxy extends Request diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 253deb143a..efb08d693c 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -204,7 +204,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface if (!$request->isMethodSafe()) { $response = $this->invalidate($request, $catch); - } elseif ($request->headers->has('expect')) { + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { $response = $this->pass($request, $catch); } else { $response = $this->lookup($request, $catch); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index 68d2b88ed6..7751f6e0cc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -1264,6 +1264,21 @@ class HttpCacheTest extends HttpCacheTestCase $this->assertNull($this->response->getETag()); $this->assertNull($this->response->getLastModified()); } + + public function testDoesNotCacheOptionsRequest() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get'); + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options'); + $this->request('OPTIONS', '/'); + $this->assertHttpKernelIsCalled(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertSame('get', $this->response->getContent()); + } } class TestKernel implements HttpKernelInterface diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 3ad45f979f..7346264627 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.3.9", "symfony/event-dispatcher": "~2.6,>=2.6.7|~3.0.0", - "symfony/http-foundation": "~2.7.15|~2.8.8|~3.0.8", + "symfony/http-foundation": "~2.7.20|~2.8.13|~3.0.8", "symfony/debug": "~2.6,>=2.6.2", "psr/log": "~1.0" }, diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php index ac71be8285..d12701cb44 100644 --- a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php @@ -58,8 +58,14 @@ class PropertyMetadata extends MemberMetadata */ protected function newReflectionMember($objectOrClassName) { + $originalClass = is_string($objectOrClassName) ? $objectOrClassName : get_class($objectOrClassName); + while (!property_exists($objectOrClassName, $this->getName())) { $objectOrClassName = get_parent_class($objectOrClassName); + + if (false === $objectOrClassName) { + throw new ValidatorException(sprintf('Property "%s" does not exist in class "%s".', $this->getName(), $originalClass)); + } } $member = new \ReflectionProperty($objectOrClassName, $this->getName()); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php index f411d950e1..e0ff920fe1 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php @@ -42,4 +42,14 @@ class PropertyMetadataTest extends \PHPUnit_Framework_TestCase $this->assertTrue($metadata->isPublic($entity)); $this->assertEquals('Overridden data', $metadata->getPropertyValue($entity)); } + + public function testGetPropertyValueFromRemovedProperty() + { + $entity = new Entity('foobar'); + $metadata = new PropertyMetadata(self::CLASSNAME, 'internal'); + $metadata->name = 'test'; + + $this->setExpectedException('Symfony\Component\Validator\Exception\ValidatorException'); + $metadata->getPropertyValue($entity); + } }