Mwjames has uploaded a new change for review. https://gerrit.wikimedia.org/r/117168
Change subject: Add SG\CacheInvalidator + unit tests ...................................................................... Add SG\CacheInvalidator + unit tests - Split responsibilities of the former SemanticGlossaryCacheHandling - Moved value inpsection into PropertyValueInspector - Removed static behaviour - Added unit test Change-Id: Ic898185210adeafb932bb763cf166770dcf20e3a --- M SemanticGlossary.php A src/CacheInvalidator.php A src/PropertyValueInspector.php A tests/phpunit/CacheInvalidatorTest.php A tests/phpunit/PropertyValueInspectorTest.php 5 files changed, 444 insertions(+), 10 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/SemanticGlossary refs/changes/68/117168/1 diff --git a/SemanticGlossary.php b/SemanticGlossary.php index 8ac90eb..ba932c7 100644 --- a/SemanticGlossary.php +++ b/SemanticGlossary.php @@ -59,20 +59,12 @@ // register class files with the Autoloader $autoloadClasses = array( 'SemanticGlossaryBackend' => $dir . '/SemanticGlossaryBackend.php', - 'SemanticGlossaryCacheHandling' => $dir . '/SemanticGlossaryCacheHandling.php', 'SG\PropertyRegistry' => $dir . '/src/PropertyRegistry.php', + 'SG\CacheInvalidator' => $dir . '/src/CacheInvalidator.php', + 'SG\PropertyValueInspector' => $dir . '/src/PropertyValueInspector.php', ); $GLOBALS[ 'wgAutoloadClasses' ] = array_merge( $GLOBALS[ 'wgAutoloadClasses' ], $autoloadClasses ); - - // register hook handlers - $hooks = array( - 'SMWStore::updateDataBefore' => array( 'SemanticGlossaryCacheHandling::purgeCacheForData' ), // invalidate on update - 'smwDeleteSemanticData' => array( 'SemanticGlossaryCacheHandling::purgeCacheForSubject' ), // invalidate on delete - 'TitleMoveComplete' => array( 'SemanticGlossaryCacheHandling::purgeCacheForTitle' ), // move annotations - ); - - $GLOBALS[ 'wgHooks' ] = array_merge_recursive( $GLOBALS[ 'wgHooks' ], $hooks ); define( 'SG_PROP_GLT', 'Glossary-Term' ); define( 'SG_PROP_GLD', 'Glossary-Definition' ); @@ -88,4 +80,31 @@ return \SG\PropertyRegistry::getInstance()->registerPropertiesAndAliases(); }; + /** + * Invalidate on update + * + * @since 1.0 + */ + $GLOBALS['wgHooks']['SMWStore::updateDataBefore'][] = function ( SMWStore $store, SMWSemanticData $semanticData ) { + return \SG\CacheInvalidator::getInstance()->invalidateOnUpdate( $store, $semanticData ); + }; + + /** + * Invalidate on delete + * + * @since 1.0 + */ + $GLOBALS['wgHooks']['smwDeleteSemanticData'][] = function ( SMWDIWikiPage $subject ) { + return \SG\CacheInvalidator::getInstance()->invalidateOnDelete( smwfGetStore(), $subject ); + }; + + /** + * Invalidate on title move + * + * @since 1.0 + */ + $GLOBALS['wgHooks']['TitleMoveComplete'][] = function ( &$old_title, &$new_title, &$user, $pageid, $redirid ) { + return \SG\CacheInvalidator::getInstance()->invalidateOnMove( $old_title ); + }; + } ); diff --git a/src/CacheInvalidator.php b/src/CacheInvalidator.php new file mode 100644 index 0000000..1a4f161 --- /dev/null +++ b/src/CacheInvalidator.php @@ -0,0 +1,170 @@ +<?php + +namespace SG; + +use LingoParser; +use BagOStuff; +use Title; + +/** + * @ingroup SG + * + * @licence GNU GPL v2+ + * @since 1.0 + * + * @author Stephan Gambke + * @author mwjames + */ +class CacheInvalidator { + + protected static $instance = null; + protected $cache = null; + + /** + * @since 1.0 + * + * @return PropertyRegistry + */ + public static function getInstance() { + + if ( self::$instance === null ) { + + $instance = new self(); + $instance->setCache( $instance->acquireCache() ); + + self::$instance = $instance; + } + + return self::$instance; + } + + /** + * @since 1.0 + */ + public static function clear() { + self::$instance = null; + } + + /** + * @since 1.0 + * + * @param BagOStuff $cache + */ + public function setCache( BagOStuff $cache ) { + $this->cache = $cache; + } + + /** + * @since 1.0 + * + * @param Store $store + * @param SemanticData $semanticData + * + * @return boolean + */ + public function invalidateOnUpdate( \SMWStore $store, \SMWSemanticData $semanticData ) { + + wfProfileIn( __METHOD__ ); + + $this->findAllSubobjects( $store, $semanticData ); + + if ( $this->hasModifiedData( $store, $semanticData ) ) { + $this->purgeCache( $semanticData->getSubject() ); + LingoParser::purgeCache(); + } + + wfProfileOut( __METHOD__ ); + return true; + } + + /** + * @since 1.0 + * + * @param SMWStore $store + * @param SMWDIWikiPage $subject + * @param boolean|true $purgeLingo + * + * @return boolean + */ + public function invalidateOnDelete( \SMWStore $store, \SMWDIWikiPage $subject, $purgeLingo = true ) { + + wfProfileIn( __METHOD__ ); + + $this->findSubobjectsToSubject( $store, $subject ); + $this->purgeCache( $subject ); + + if ( $purgeLingo ) { + LingoParser::purgeCache(); + } + + wfProfileOut( __METHOD__ ); + return true; + } + + /** + * @since 1.0 + * + * @param Title $title + * + * @return boolean + */ + public function invalidateOnMove( Title $title ) { + $this->purgeCache( \SMWDIWikiPage::newFromTitle( $title ) ); + return true; + } + + protected function findAllSubobjects( \SMWStore $store, \SMWSemanticData $semanticData ) { + + $properties = $semanticData->getProperties(); + + if ( array_key_exists( '_SOBJ', $properties ) ) { + foreach ( $semanticData->getPropertyValues( $properties['_SOBJ'] ) as $so ) { + $this->invalidateOnUpdate( $store, $store->getSemanticData( $so ), false ); + } + } + } + + protected function findSubobjectsToSubject( \SMWStore $store, \SMWDIWikiPage $subject ) { + + $properties = $store->getProperties( $subject ); + + if ( array_key_exists( '_SOBJ', $properties ) ) { + foreach ( $store->getPropertyValues( $subject, $properties['_SOBJ'] ) as $so ) { + $this->invalidateOnDelete( $store, $so->getSubject(), false ); + } + } + } + + protected function hasModifiedData( \SMWStore $store, \SMWSemanticData $semanticData ) { + + $propertyValueInspector = new PropertyValueInspector( $store, $semanticData ); + + return $propertyValueInspector->inspect( PropertyRegistry::SG_TERM ) || + $propertyValueInspector->inspect( PropertyRegistry::SG_DEFINITION ) || + $propertyValueInspector->inspect( PropertyRegistry::SG_LINK ) || + $propertyValueInspector->inspect( PropertyRegistry::SG_STYLE ); + } + + protected function purgeCache( \SMWDIWikiPage $subject ) { + wfProfileIn( __METHOD__ ); + + $this->cache->delete( $this->getCacheId( $subject->getSerialization() ) ); + + wfProfileOut( __METHOD__ ); + return true; + } + + private function getCacheId( $identifier ) { + return wfMemcKey( 'ext', 'semanticglossary', $identifier ); + } + + private function acquireCache() { + + if ( isset( $GLOBAL['wgexLingoCacheType'] ) && $GLOBAL['wgexLingoCacheType'] !== null ) { + return wfGetCache( $GLOBAL['wgexLingoCacheType'] ); + } + + return wfGetMainCache(); + } + +} diff --git a/src/PropertyValueInspector.php b/src/PropertyValueInspector.php new file mode 100644 index 0000000..ecf11ad --- /dev/null +++ b/src/PropertyValueInspector.php @@ -0,0 +1,88 @@ +<?php + +namespace SG; + +/** + * @ingroup SG + * + * @licence GNU GPL v2+ + * @since 1.0 + * + * @author Stephan Gambke + */ +class PropertyValueInspector { + + protected $store = null; + protected $semanticData = null; + + /** + * @since 1.0 + * + * @param SMWStore $store + * @param SMWSemanticData $semanticData + */ + public function __construct( \SMWStore $store, \SMWSemanticData $semanticData ) { + $this->store = $store; + $this->semanticData = $semanticData; + } + + /** + * @since 1.0 + * + * @param string $propertId + * + * @return boolean + */ + public function inspect( $propertId ) { + + $properties = $this->semanticData->getProperties(); + + // check if property changed + if ( array_key_exists( $propertId, $properties ) ) { + + $newEntries = $this->semanticData->getPropertyValues( $properties[$propertId] ); + $oldEntries = $this->store->getPropertyValues( + $this->semanticData->getSubject(), + $properties[$propertId] + ); + } else { + + $newEntries = array(); + $oldEntries = $this->store->getPropertyValues( + $this->semanticData->getSubject(), + new \SMWDIProperty( $propertId ) + ); + } + + // Did the number of entries change? + if ( count( $newEntries ) !== count( $oldEntries ) ) { + return true; + } + + // Match each new entry against an old entry + foreach ( $newEntries as $newDi ) { + $found = false; + foreach ( $oldEntries as $oldKey => $oldDi ) { + if ( $newDi->getHash() === $oldDi->getHash() ) { + $found = true; + unset( $oldEntries[$oldKey] ); + break; + } + } + + // If no match was possible... + if ( !$found ) { + return true; + } + } + + // Are there unmatched old entries left? + if ( count( $oldEntries ) > 0 ) { + return true; + } + + // Every new entry matched to exaclty one old entry and vice versa + return false; + } + +} diff --git a/tests/phpunit/CacheInvalidatorTest.php b/tests/phpunit/CacheInvalidatorTest.php new file mode 100644 index 0000000..dd15e0c --- /dev/null +++ b/tests/phpunit/CacheInvalidatorTest.php @@ -0,0 +1,105 @@ +<?php + +namespace SG\Tests; + +use SG\PropertyRegistry; +use SG\CacheInvalidator; + +use HashBagOStuff; + +/** + * @covers \SG\CacheInvalidator + * + * @ingroup Test + * + * @group SG + * @group SGExtension + * + * @licence GNU GPL v2+ + * @since 1.0 + * + * @author mwjames + */ +class CacheInvalidatorTest extends \PHPUnit_Framework_TestCase { + + public function testCanConstruct() { + $this->assertInstanceOf( + '\SG\CacheInvalidator', + CacheInvalidator::getInstance() + ); + } + + public function testInvalidateOnUpdateWithEmptyData() { + + $store = $this->getMockBuilder( 'SMWStore' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $semanticData = $this->getMockBuilder( 'SMWSemanticData' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $instance = new CacheInvalidator; + $instance->setCache( new HashBagOStuff ); + + $this->assertTrue( $instance->invalidateOnUpdate( $store, $semanticData ) ); + } + + public function testInvalidateOnDeleteWithEmptyData() { + + $subject = \SMWDIWikiPage::newFromTitle( \Title::newFromText( __METHOD__ ) ); + + $store = $this->getMockBuilder( 'SMWStore' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $store->expects( $this->once() ) + ->method( 'getProperties' ) + ->with( $this->equalTo( $subject ) ) + ->will( $this->returnValue( array() ) ); + + $instance = new CacheInvalidator; + $instance->setCache( new HashBagOStuff ); + + $this->assertTrue( $instance->invalidateOnDelete( $store, $subject ) ); + } + + public function testInvalidateOnDeleteWithSubobject() { + + $subobject = new \SMWDIProperty( '_SOBJ' ); + $subject = \SMWDIWikiPage::newFromTitle( \Title::newFromText( __METHOD__ ) ); + $newSubject = \SMWDIWikiPage::newFromTitle( \Title::newFromText( 'Subobject' ) ); + + $store = $this->getMockBuilder( 'SMWStore' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $store->expects( $this->once() ) + ->method( 'getProperties' ) + ->with( $this->equalTo( $subject ) ) + ->will( $this->returnValue( array( '_SOBJ' => $subobject ) ) ); + + $store->expects( $this->once() ) + ->method( 'getPropertyValues' ) + ->with( + $this->equalTo( $subject ), + $this->equalTo( $subobject ) ) + ->will( $this->returnValue( $newSubject ) ); + + $instance = new CacheInvalidator; + $instance->setCache( new HashBagOStuff ); + + $this->assertTrue( $instance->invalidateOnDelete( $store, $subject ) ); + } + + public function testInvalidateOnMove() { + + $title = \Title::newFromText( __METHOD__ ); + + $instance = new CacheInvalidator; + $instance->setCache( new HashBagOStuff ); + + $this->assertTrue( $instance->invalidateOnMove( $title ) ); + } + +} diff --git a/tests/phpunit/PropertyValueInspectorTest.php b/tests/phpunit/PropertyValueInspectorTest.php new file mode 100644 index 0000000..2f23ceb --- /dev/null +++ b/tests/phpunit/PropertyValueInspectorTest.php @@ -0,0 +1,52 @@ +<?php + +namespace SG\Tests; + +use SG\PropertyValueInspector; + +/** + * @covers \SG\PropertyValueInspector + * + * @ingroup Test + * + * @group SG + * @group SGExtension + * + * @licence GNU GPL v2+ + * @since 1.0 + * + * @author mwjames + */ +class PropertyValueInspectorTest extends \PHPUnit_Framework_TestCase { + + public function testCanConstruct() { + + $store = $this->getMockBuilder( 'SMWStore' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $semanticData = $this->getMockBuilder( 'SMWSemanticData' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $instance = new PropertyValueInspector( $store, $semanticData ); + + $this->assertInstanceOf( '\SG\PropertyValueInspector', $instance ); + } + + public function testInspectWithoutData() { + + $store = $this->getMockBuilder( 'SMWStore' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $semanticData = $this->getMockBuilder( 'SMWSemanticData' ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $instance = new PropertyValueInspector( $store, $semanticData ); + + $this->assertFalse( $instance->inspect( 'foo' ) ); + } + +} -- To view, visit https://gerrit.wikimedia.org/r/117168 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ic898185210adeafb932bb763cf166770dcf20e3a Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/SemanticGlossary Gerrit-Branch: master Gerrit-Owner: Mwjames <jamesin.hongkon...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits