diff --git a/app/code/Magento/CacheInvalidate/Model/Observer.php b/app/code/Magento/CacheInvalidate/Model/Observer.php new file mode 100644 index 0000000000000..6deb4f50aac51 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/Model/Observer.php @@ -0,0 +1,99 @@ +_config = $config; + $this->_helper = $helper; + $this->_curlAdapter = $curlAdapter; + } + + /** + * If Varnish caching is enabled it collects array of tags + * of incoming object and asks to clean cache. + * + * @param \Magento\Framework\Event\Observer $observer + * @return void + */ + public function invalidateVarnish(\Magento\Framework\Event\Observer $observer) + { + if ($this->_config->getType() == \Magento\PageCache\Model\Config::VARNISH && $this->_config->isEnabled()) { + $object = $observer->getEvent()->getObject(); + if ($object instanceof \Magento\Framework\Object\IdentityInterface) { + $tags = []; + $pattern = "((^|,)%s(,|$))"; + foreach ($object->getIdentities() as $tag) { + $tags[] = sprintf($pattern, preg_replace("~_\\d+$~", '', $tag)); + $tags[] = sprintf($pattern, $tag); + } + $this->sendPurgeRequest(implode('|', array_unique($tags))); + } + } + } + + /** + * Flash Varnish cache + * + * @param \Magento\Framework\Event\Observer $observer + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function flushAllCache(\Magento\Framework\Event\Observer $observer) + { + if ($this->_config->getType() == \Magento\PageCache\Model\Config::VARNISH && $this->_config->isEnabled()) { + $this->sendPurgeRequest('.*'); + } + } + + /** + * Send curl purge request + * to invalidate cache by tags pattern + * + * @param string $tagsPattern + * @return void + */ + protected function sendPurgeRequest($tagsPattern) + { + $headers = ["X-Magento-Tags-Pattern: {$tagsPattern}"]; + $this->_curlAdapter->setOptions([CURLOPT_CUSTOMREQUEST => 'PURGE']); + $this->_curlAdapter->write('', $this->_helper->getUrl('*'), '1.1', $headers); + $this->_curlAdapter->read(); + $this->_curlAdapter->close(); + } +} diff --git a/app/code/Magento/CacheInvalidate/README.md b/app/code/Magento/CacheInvalidate/README.md new file mode 100644 index 0000000000000..6cca6ffec03e4 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/README.md @@ -0,0 +1,2 @@ +The CacheInvalidate module is used to invalidate the Varnish cache if it is configured. +It listens for events that request the cache to be flushed or cause the cache to be invalid, then sends Varnish a purge request using cURL. \ No newline at end of file diff --git a/app/code/Magento/CacheInvalidate/Test/Unit/Model/ObserverTest.php b/app/code/Magento/CacheInvalidate/Test/Unit/Model/ObserverTest.php new file mode 100644 index 0000000000000..1abf5d45605ac --- /dev/null +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Model/ObserverTest.php @@ -0,0 +1,138 @@ +_configMock = $this->getMock( + 'Magento\PageCache\Model\Config', + ['getType', 'isEnabled'], + [], + '', + false + ); + $this->_helperMock = $this->getMock('Magento\PageCache\Helper\Data', ['getUrl'], [], '', false); + $this->_curlMock = $this->getMock( + '\Magento\Framework\HTTP\Adapter\Curl', + ['setOptions', 'write', 'read', 'close'], + [], + '', + false + ); + $this->_model = new \Magento\CacheInvalidate\Model\Observer( + $this->_configMock, + $this->_helperMock, + $this->_curlMock + ); + $this->_observerMock = $this->getMock( + 'Magento\Framework\Event\Observer', + ['getEvent'], + [], + '', + false + ); + $this->_observerObject = $this->getMock('\Magento\Store\Model\Store', [], [], '', false); + } + + /** + * Test case for cache invalidation + */ + public function testInvalidateVarnish() + { + $tags = ['cache_1', 'cache_group']; + $pattern = '((^|,)cache(,|$))|((^|,)cache_1(,|$))|((^|,)cache_group(,|$))'; + + $this->_configMock->expects($this->once())->method('isEnabled')->will($this->returnValue(true)); + $this->_configMock->expects( + $this->once() + )->method( + 'getType' + )->will( + $this->returnValue(\Magento\PageCache\Model\Config::VARNISH) + ); + $eventMock = $this->getMock('Magento\Framework\Event', ['getObject'], [], '', false); + $eventMock->expects($this->once())->method('getObject')->will($this->returnValue($this->_observerObject)); + $this->_observerMock->expects($this->once())->method('getEvent')->will($this->returnValue($eventMock)); + $this->_observerObject->expects($this->once())->method('getIdentities')->will($this->returnValue($tags)); + $this->sendPurgeRequest($pattern); + + $this->_model->invalidateVarnish($this->_observerMock); + } + + /** + * Test case for flushing all the cache + */ + public function testFlushAllCache() + { + $this->_configMock->expects($this->once())->method('isEnabled')->will($this->returnValue(true)); + $this->_configMock->expects( + $this->once() + )->method( + 'getType' + )->will( + $this->returnValue(\Magento\PageCache\Model\Config::VARNISH) + ); + + $this->sendPurgeRequest('.*'); + $this->_model->flushAllCache($this->_observerMock); + } + + /** + * @param string $tags + */ + protected function sendPurgeRequest($tags) + { + $url = 'http://mangento.index.php'; + $httpVersion = '1.1'; + $headers = ["X-Magento-Tags-Pattern: {$tags}"]; + $this->_helperMock->expects( + $this->any() + )->method( + 'getUrl' + )->with( + $this->equalTo('*'), + [] + )->will( + $this->returnValue($url) + ); + $this->_curlMock->expects($this->once())->method('setOptions')->with([CURLOPT_CUSTOMREQUEST => 'PURGE']); + $this->_curlMock->expects( + $this->once() + )->method( + 'write' + )->with( + $this->equalTo(''), + $this->equalTo($url), + $httpVersion, + $this->equalTo($headers) + ); + $this->_curlMock->expects($this->once())->method('read'); + $this->_curlMock->expects($this->once())->method('close'); + } +} diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json new file mode 100644 index 0000000000000..3fe142a52e825 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-cache-invalidate", + "description": "N/A", + "require": { + "php": "~5.5.0|~5.6.0", + "magento/module-page-cache": "0.74.0-beta4", + "magento/framework": "0.74.0-beta4", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-module", + "version": "0.74.0-beta4", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "extra": { + "map": [ + [ + "*", + "Magento/CacheInvalidate" + ] + ] + } +} diff --git a/app/code/Magento/CacheInvalidate/etc/events.xml b/app/code/Magento/CacheInvalidate/etc/events.xml new file mode 100644 index 0000000000000..d4ba3665ee66f --- /dev/null +++ b/app/code/Magento/CacheInvalidate/etc/events.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/CacheInvalidate/etc/module.xml b/app/code/Magento/CacheInvalidate/etc/module.xml new file mode 100644 index 0000000000000..ca87df877a43a --- /dev/null +++ b/app/code/Magento/CacheInvalidate/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/composer.json b/composer.json index c8721c87609c6..907d23ff71152 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,7 @@ "magento/module-backend": "self.version", "magento/module-backup": "self.version", "magento/module-bundle": "self.version", + "magento/module-cache-invalidate": "self.version", "magento/module-captcha": "self.version", "magento/module-catalog": "self.version", "magento/module-catalog-import-export": "self.version", diff --git a/composer.lock b/composer.lock index 02e531e791e51..1850375542413 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "974fa7d59a25f8a31c70ea53068944cd", + "hash": "35d05640e3dc260b7a5b09310611194a", "packages": [ { "name": "composer/composer", @@ -2007,16 +2007,16 @@ }, { "name": "fabpot/php-cs-fixer", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "14f802b2c00b672676d55612e3c0d03465b69bf6" + "reference": "a574ba148953fea1f78428d4b7c6843e759711f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/14f802b2c00b672676d55612e3c0d03465b69bf6", - "reference": "14f802b2c00b672676d55612e3c0d03465b69bf6", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/a574ba148953fea1f78428d4b7c6843e759711f3", + "reference": "a574ba148953fea1f78428d4b7c6843e759711f3", "shasum": "" }, "require": { @@ -2056,7 +2056,7 @@ } ], "description": "A script to automatically fix Symfony Coding Standard", - "time": "2015-04-09 19:10:26" + "time": "2015-04-13 21:33:33" }, { "name": "league/climate", @@ -2272,16 +2272,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.0.15", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "34cc484af1ca149188d0d9e91412191e398e0b67" + "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/34cc484af1ca149188d0d9e91412191e398e0b67", - "reference": "34cc484af1ca149188d0d9e91412191e398e0b67", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/934fd03eb6840508231a7f73eb8940cf32c3b66c", + "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c", "shasum": "" }, "require": { @@ -2330,7 +2330,7 @@ "testing", "xunit" ], - "time": "2015-01-24 10:06:35" + "time": "2015-04-11 04:35:00" }, { "name": "phpunit/php-file-iterator",