diff --git a/app/code/Magento/CacheInvalidate/Model/Observer.php b/app/code/Magento/CacheInvalidate/Model/Observer.php new file mode 100644 index 0000000000000000000000000000000000000000..6deb4f50aac511b57fc7bf5cf920a6f172bad351 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/Model/Observer.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CacheInvalidate\Model; + +/** + * Class Observer + */ +class Observer +{ + /** + * Application config object + * + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + protected $_config; + + /** + * @var \Magento\PageCache\Helper\Data + */ + protected $_helper; + + /** + * @var \Magento\Framework\HTTP\Adapter\Curl + */ + protected $_curlAdapter; + + /** + * Constructor + * + * @param \Magento\PageCache\Model\Config $config + * @param \Magento\PageCache\Helper\Data $helper + * @param \Magento\Framework\HTTP\Adapter\Curl $curlAdapter + */ + public function __construct( + \Magento\PageCache\Model\Config $config, + \Magento\PageCache\Helper\Data $helper, + \Magento\Framework\HTTP\Adapter\Curl $curlAdapter + ) { + $this->_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 0000000000000000000000000000000000000000..6cca6ffec03e45b5b7c09ba7d6e8a6543e14eb00 --- /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 0000000000000000000000000000000000000000..1abf5d45605acb48f111f7126ab58dcae9c9e540 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Model/ObserverTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CacheInvalidate\Test\Unit\Model; + +class ObserverTest extends \PHPUnit_Framework_TestCase +{ + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\CacheInvalidate\Model\Observer */ + protected $_model; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Event\Observer */ + protected $_observerMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\HTTP\Adapter\Curl */ + protected $_curlMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\PageCache\Model\Config */ + protected $_configMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\PageCache\Helper\Data */ + protected $_helperMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Object\ */ + protected $_observerObject; + + /** + * Set up all mocks and data for test + */ + public function setUp() + { + $this->_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 0000000000000000000000000000000000000000..3fe142a52e825fe6bae3ab6d56e798302d3ba3c3 --- /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 0000000000000000000000000000000000000000..d4ba3665ee66fb4e1e1eee75045302c199bf2985 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/etc/events.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd"> + <event name="clean_cache_by_tags"> + <observer name="invalidate_varnish" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="adminhtml_cache_flush_system"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="clean_media_cache_after"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="clean_catalog_images_cache_after"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="assigned_theme_changed"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="catalogrule_after_apply"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="adminhtml_cache_refresh_type"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="adminhtml_cache_flush_all"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="assign_theme_to_stores_after"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="flushAllCache" /> + </event> + <event name="controller_action_postdispatch_adminhtml_system_currency_saveRates"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="controller_action_postdispatch_adminhtml_system_config_save"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="controller_action_postdispatch_adminhtml_catalog_product_action_attribute_save"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="controller_action_postdispatch_adminhtml_catalog_product_massStatus"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> + <event name="controller_action_postdispatch_adminhtml_system_currencysymbol_save"> + <observer name="flush_varnish_pagecache" instance="Magento\CacheInvalidate\Model\Observer" method="invalidateVarnish" /> + </event> +</config> diff --git a/app/code/Magento/CacheInvalidate/etc/module.xml b/app/code/Magento/CacheInvalidate/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..ca87df877a43a93eb2be450fa10b0013d099eff9 --- /dev/null +++ b/app/code/Magento/CacheInvalidate/etc/module.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> + <module name="Magento_CacheInvalidate" setup_version="2.0.0"> + <sequence> + <module name="Magento_Store"/> + </sequence> + </module> +</config> diff --git a/composer.json b/composer.json index c8721c87609c6a84b5b35d2abd2e15b2f0fddbdb..907d23ff711529e697064e42d0eab2a6c2465669 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 02e531e791e517c23da1202be3be61491553ec1d..1850375542413cf443c72fd68f36fd6fe6b21d7f 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",