diff --git a/lib/internal/Magento/Framework/App/Http/Context.php b/lib/internal/Magento/Framework/App/Http/Context.php index 4146dac725f03a70971e85d6a29b27803e795412..4d1f5e01d10de1188bfc1414bba0fcd045dc4be2 100644 --- a/lib/internal/Magento/Framework/App/Http/Context.php +++ b/lib/internal/Magento/Framework/App/Http/Context.php @@ -27,6 +27,16 @@ class Context */ protected $default = []; + /** + * @param array $data + * @param array $default + */ + public function __construct(array $data = [], array $default = []) + { + $this->data = $data; + $this->default = $default; + } + /** * Data setter * @@ -99,4 +109,17 @@ class Context } return null; } + + /** + * Get data and default data in "key-value" format + * + * @return array + */ + public function toArray() + { + return [ + 'data' => $this->data, + 'default' => $this->default + ]; + } } diff --git a/lib/internal/Magento/Framework/App/PageCache/Kernel.php b/lib/internal/Magento/Framework/App/PageCache/Kernel.php index 8a83592bc322e86c046b68480f815878f3de8855..fc2521ac747780350c8c33114ea8ba399eaa723f 100644 --- a/lib/internal/Magento/Framework/App/PageCache/Kernel.php +++ b/lib/internal/Magento/Framework/App/PageCache/Kernel.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\App\PageCache; -use Magento\Framework\App\ObjectManager; - /** * Builtin cache processor */ @@ -34,19 +32,76 @@ class Kernel */ private $fullPageCache; + /** + * @var \Magento\Framework\Serialize\SerializerInterface + */ + private $serializer; + + /** + * @var \Magento\Framework\App\Http\Context + */ + private $context; + + /** + * @var \Magento\Framework\App\Http\ContextFactory + */ + private $contextFactory; + + /** + * @var \Magento\Framework\App\Response\HttpFactory + */ + private $httpFactory; + /** * @param Cache $cache * @param Identifier $identifier * @param \Magento\Framework\App\Request\Http $request + * @param \Magento\Framework\App\Http\Context|null $context + * @param \Magento\Framework\App\Http\ContextFactory|null $contextFactory + * @param \Magento\Framework\App\Response\HttpFactory|null $httpFactory + * @param \Magento\Framework\Serialize\SerializerInterface|null $serializer */ public function __construct( \Magento\Framework\App\PageCache\Cache $cache, \Magento\Framework\App\PageCache\Identifier $identifier, - \Magento\Framework\App\Request\Http $request + \Magento\Framework\App\Request\Http $request, + \Magento\Framework\App\Http\Context $context = null, + \Magento\Framework\App\Http\ContextFactory $contextFactory = null, + \Magento\Framework\App\Response\HttpFactory $httpFactory = null, + \Magento\Framework\Serialize\SerializerInterface $serializer = null ) { $this->cache = $cache; $this->identifier = $identifier; $this->request = $request; + + if ($context) { + $this->context = $context; + } else { + $this->context = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Http\Context::class + ); + } + if ($contextFactory) { + $this->contextFactory = $contextFactory; + } else { + $this->contextFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Http\ContextFactory::class + ); + } + if ($httpFactory) { + $this->httpFactory = $httpFactory; + } else { + $this->httpFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Response\HttpFactory::class + ); + } + if ($serializer) { + $this->serializer = $serializer; + } else { + $this->serializer = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\Serialize\SerializerInterface::class + ); + } } /** @@ -57,7 +112,12 @@ class Kernel public function load() { if ($this->request->isGet() || $this->request->isHead()) { - return unserialize($this->getCache()->load($this->identifier->getValue())); + $responseData = $this->serializer->unserialize($this->getCache()->load($this->identifier->getValue())); + if (!$responseData) { + return false; + } + + return $this->buildResponse($responseData); } return false; } @@ -84,11 +144,63 @@ class Kernel if (!headers_sent()) { header_remove('Set-Cookie'); } - $this->getCache()->save(serialize($response), $this->identifier->getValue(), $tags, $maxAge); + + $this->getCache()->save( + $this->serializer->serialize($this->getPreparedData($response)), + $this->identifier->getValue(), + $tags, + $maxAge + ); } } } + /** + * Get prepared data for storage in the cache. + * + * @param \Magento\Framework\App\Response\Http $response + * @return array + */ + private function getPreparedData(\Magento\Framework\App\Response\Http $response) + { + return [ + 'content' => $response->getContent(), + 'status_code' => $response->getStatusCode(), + 'headers' => $response->getHeaders()->toArray(), + 'context' => $this->context->toArray() + ]; + + } + + /** + * Build response using response data. + * + * @param array $responseData + * @return \Magento\Framework\App\Response\Http + */ + private function buildResponse($responseData) + { + $context = $this->contextFactory->create( + [ + 'data' => $responseData['context']['data'], + 'default' => $responseData['context']['default'] + ] + ); + + $response = $this->httpFactory->create( + [ + 'context' => $context + ] + ); + $response->setStatusCode($responseData['status_code']); + $response->setContent($responseData['content']); + foreach ($responseData['headers'] as $headerKey => $headerValue) { + $response->setHeader($headerKey, $headerValue, true); + } + + return $response; + } + /** * TODO: Workaround to support backwards compatibility, will rework to use Dependency Injection in MAGETWO-49547 * @@ -97,7 +209,9 @@ class Kernel private function getCache() { if (!$this->fullPageCache) { - $this->fullPageCache = ObjectManager::getInstance()->get(\Magento\PageCache\Model\Cache\Type::class); + $this->fullPageCache = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\PageCache\Model\Cache\Type::class + ); } return $this->fullPageCache; } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php index 05f589e0dc61a9299f717aeab0905f8b205fdafe..2c6b3dda23776efeeea023e2348bb3b751c7908b 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php @@ -64,4 +64,19 @@ class ContextTest extends \PHPUnit_Framework_TestCase ksort($data); $this->assertEquals(sha1(serialize($data)), $this->object->getVaryString()); } + + public function testToArray() + { + $newObject = new \Magento\Framework\App\Http\Context(['key' => 'value']); + + $newObject->setValue('key1', 'value1', 'default1'); + $newObject->setValue('key2', 'value2', 'default2'); + $this->assertEquals( + [ + 'data' => ['key' => 'value', 'key1' => 'value1', 'key2' => 'value2'], + 'default' => ['key1' => 'default1', 'key2' => 'default2'] + ], + $newObject->toArray() + ); + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php index 81b828b3f938c45333a5ac8b096d8b23d013a81a..1a85b481e53c1a5dcddd1ce77212145beaabc134 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php @@ -6,7 +6,12 @@ namespace Magento\Framework\App\Test\Unit\PageCache; use \Magento\Framework\App\PageCache\Kernel; +use \Magento\Framework\App\Http\ContextFactory; +use \Magento\Framework\App\Response\HttpFactory; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class KernelTest extends \PHPUnit_Framework_TestCase { /** @var Kernel */ @@ -27,39 +32,136 @@ class KernelTest extends \PHPUnit_Framework_TestCase /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\PageCache\Model\Cache\Type */ private $fullPageCacheMock; + /** @var \Magento\Framework\App\Response\Http|\PHPUnit_Framework_MockObject_MockObject */ + private $httpResponseMock; + + /** @var ContextFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $contextFactoryMock; + + /** @var HttpFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $httpFactoryMock; + + /** @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $serializer; + + /** @var \Magento\Framework\App\Http\Context|\PHPUnit_Framework_MockObject_MockObject */ + private $contextMock; + /** * Setup */ protected function setUp() { + $headersMock = $this->getMock(\Zend\Http\Headers::class, [], [], '', false); $this->cacheMock = $this->getMock(\Magento\Framework\App\PageCache\Cache::class, [], [], '', false); $this->fullPageCacheMock = $this->getMock(\Magento\PageCache\Model\Cache\Type::class, [], [], '', false); - $this->identifierMock = - $this->getMock(\Magento\Framework\App\PageCache\Identifier::class, [], [], '', false); + $this->contextMock = $this->getMock(\Magento\Framework\App\Http\Context::class, [], [], '', false); + $this->httpResponseMock = $this->getMock(\Magento\Framework\App\Response\Http::class, [], [], '', false); + $this->identifierMock = $this->getMock(\Magento\Framework\App\PageCache\Identifier::class, [], [], '', false); $this->requestMock = $this->getMock(\Magento\Framework\App\Request\Http::class, [], [], '', false); - $this->kernel = new Kernel($this->cacheMock, $this->identifierMock, $this->requestMock); + $this->serializer = $this->getMock(\Magento\Framework\Serialize\SerializerInterface::class, [], [], '', false); + $this->responseMock = $this->getMock(\Magento\Framework\App\Response\Http::class, [], [], '', false); + $this->contextFactoryMock = $this->getMock(ContextFactory::class, ['create'], [], '', false); + $this->httpFactoryMock = $this->getMock(HttpFactory::class, ['create'], [], '', false); + $this->responseMock->expects($this->any())->method('getHeaders')->willReturn($headersMock); + + $this->kernel = new Kernel( + $this->cacheMock, + $this->identifierMock, + $this->requestMock, + $this->contextMock, + $this->contextFactoryMock, + $this->httpFactoryMock, + $this->serializer + ); $reflection = new \ReflectionClass(\Magento\Framework\App\PageCache\Kernel::class); $reflectionProperty = $reflection->getProperty('fullPageCache'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($this->kernel, $this->fullPageCacheMock); + } - $this->responseMock = $this->getMockBuilder( - \Magento\Framework\App\Response\Http::class - )->setMethods( - ['getHeader', 'getHttpResponseCode', 'setNoCacheHeaders', 'clearHeader', '__wakeup'] - )->disableOriginalConstructor()->getMock(); + /** + * @dataProvider dataProviderForResultWithCachedData + * @param string $id + * @param mixed $cache + * @param bool $isGet + * @param bool $isHead + */ + public function testLoadWithCachedData($id, $cache, $isGet, $isHead) + { + $this->serializer->expects($this->once()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + + $this->contextFactoryMock + ->expects($this->once()) + ->method('create') + ->with( + [ + 'data' => ['context_data'], + 'default' => ['context_default_data'] + ] + ) + ->willReturn($this->contextMock); + + $this->httpFactoryMock + ->expects($this->once()) + ->method('create') + ->with(['context' => $this->contextMock]) + ->willReturn($this->httpResponseMock); + + $this->requestMock->expects($this->once())->method('isGet')->will($this->returnValue($isGet)); + $this->requestMock->expects($this->any())->method('isHead')->will($this->returnValue($isHead)); + $this->fullPageCacheMock->expects( + $this->any() + )->method( + 'load' + )->with( + $this->equalTo($id) + )->will( + $this->returnValue(json_encode($cache)) + ); + $this->httpResponseMock->expects($this->once())->method('setStatusCode')->with($cache['status_code']); + $this->httpResponseMock->expects($this->once())->method('setContent')->with($cache['content']); + $this->httpResponseMock->expects($this->once())->method('setHeader')->with(0, 'header', true); + $this->identifierMock->expects($this->any())->method('getValue')->will($this->returnValue($id)); + $this->assertEquals($this->httpResponseMock, $this->kernel->load()); + } + + /** + * @return array + */ + public function dataProviderForResultWithCachedData() + { + $data = [ + 'context' => [ + 'data' => ['context_data'], + 'default' => ['context_default_data'] + ], + 'status_code' => 'status_code', + 'content' => 'content', + 'headers' => ['header'] + ]; + + return [ + ['existing key', $data, true, false], + ['existing key', $data, false, true], + ]; } /** - * @dataProvider loadProvider - * @param mixed $expected + * @dataProvider dataProviderForResultWithoutCachedData * @param string $id * @param mixed $cache * @param bool $isGet * @param bool $isHead */ - public function testLoad($expected, $id, $cache, $isGet, $isHead) + public function testLoadWithoutCachedData($id, $cache, $isGet, $isHead) { $this->requestMock->expects($this->once())->method('isGet')->will($this->returnValue($isGet)); $this->requestMock->expects($this->any())->method('isHead')->will($this->returnValue($isHead)); @@ -70,31 +172,21 @@ class KernelTest extends \PHPUnit_Framework_TestCase )->with( $this->equalTo($id) )->will( - $this->returnValue(serialize($cache)) + $this->returnValue(json_encode($cache)) ); $this->identifierMock->expects($this->any())->method('getValue')->will($this->returnValue($id)); - $this->assertEquals($expected, $this->kernel->load()); + $this->assertEquals(false, $this->kernel->load()); } /** * @return array */ - public function loadProvider() + public function dataProviderForResultWithoutCachedData() { - $data = [1, 2, 3]; return [ - [$data, 'existing key', $data, true, false], - [$data, 'existing key', $data, false, true], - [ - new \Magento\Framework\DataObject($data), - 'existing key', - new \Magento\Framework\DataObject($data), - true, - false - ], - [false, 'existing key', $data, false, false], - [false, 'non existing key', false, true, false], - [false, 'non existing key', false, false, false] + ['existing key', [], false, false], + ['non existing key', false, true, false], + ['non existing key', false, false, false] ]; } @@ -104,6 +196,14 @@ class KernelTest extends \PHPUnit_Framework_TestCase */ public function testProcessSaveCache($httpCode, $at) { + $this->serializer->expects($this->once()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $cacheControlHeader = \Zend\Http\Header\CacheControl::fromString( 'Cache-Control: public, max-age=100, s-maxage=100' );