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 <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits