jenkins-bot has submitted this change and it was merged.
Change subject: Cache created objects within the mapper
......................................................................
Cache created objects within the mapper
Change-Id: Idc7c496683ecd03ff91d9276374cef738157b094
---
M Flow.php
M container.php
M includes/Data/ObjectManager.php
A tests/CachingObjectMapperTest.php
4 files changed, 155 insertions(+), 18 deletions(-)
Approvals:
Matthias Mullie: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Flow.php b/Flow.php
index 7bbd1fc..cba4b95 100755
--- a/Flow.php
+++ b/Flow.php
@@ -125,6 +125,7 @@
$wgAutoloadClasses['Flow\Data\BasicDbStorage'] = $dir .
'includes/Data/ObjectManager.php';
$wgAutoloadClasses['Flow\Data\ObjectMapper'] = $dir .
'includes/Data/ObjectManager.php';
$wgAutoloadClasses['Flow\Data\BasicObjectMapper'] = $dir .
'includes/Data/ObjectManager.php';
+$wgAutoloadClasses['Flow\Data\CachingObjectMapper'] = $dir .
'includes/Data/ObjectManager.php';
$wgAutoloadClasses['Flow\Data\BufferedCache'] = $dir .
'includes/Data/ObjectManager.php';
$wgAutoloadClasses['Flow\Data\LocalBufferedCache'] = $dir .
'includes/Data/ObjectManager.php';
$wgAutoloadClasses['Flow\Data\SortArrayByKeys'] = $dir .
'includes/Data/ObjectManager.php';
diff --git a/container.php b/container.php
index cbec8d8..8e70d2d 100644
--- a/container.php
+++ b/container.php
@@ -113,6 +113,7 @@
use Flow\Data\BufferedCache;
use Flow\Data\LocalBufferedCache;
use Flow\Data\BasicObjectMapper;
+use Flow\Data\CachingObjectMapper;
use Flow\Data\BasicDbStorage;
use Flow\Data\PostRevisionStorage;
use Flow\Data\HeaderRevisionStorage;
@@ -140,16 +141,17 @@
} );
// Per wiki workflow definitions (types of workflows)
$c['storage.definition'] = $c->share( function( $c ) {
+ $primaryKey = array( 'definition_id' );
$cache = $c['memcache.buffered'];
- $mapper = BasicObjectMapper::model( 'Flow\\Model\\Definition' );
+ $mapper = CachingObjectMapper::model( 'Flow\\Model\\Definition',
$primaryKey );
$storage = new BasicDbStorage(
// factory and table
$c['db.factory'], 'flow_definition',
// pk
- array( 'definition_id' )
+ $primaryKey
);
$indexes = array(
- new UniqueFeatureIndex( $cache, $storage, 'flow_definition:pk',
array( 'definition_id' ) ),
+ new UniqueFeatureIndex( $cache, $storage, 'flow_definition:pk',
$primaryKey ),
new UniqueFeatureIndex( $cache, $storage,
'flow_definition:name', array( 'definition_wiki', 'definition_name' ) ),
);
@@ -157,15 +159,16 @@
} );
// Individual workflow instances
$c['storage.workflow'] = $c->share( function( $c ) {
+ $primaryKey = array( 'workflow_id' );
$cache = $c['memcache.buffered'];
- $mapper = BasicObjectMapper::model( 'Flow\\Model\\Workflow' );
+ $mapper = CachingObjectMapper::model( 'Flow\\Model\\Workflow',
$primaryKey );
$storage = new BasicDbStorage(
// factory and table
$c['db.factory'], 'flow_workflow',
// pk
- array( 'workflow_id' )
+ $primaryKey
);
- $pk = new UniqueFeatureIndex( $cache, $storage, 'flow_workflow:pk',
array( 'workflow_id' ) );
+ $pk = new UniqueFeatureIndex( $cache, $storage, 'flow_workflow:pk',
$primaryKey );
$indexes = array(
$pk,
// This is actually a unique index, but it wants the shallow
functionality.
@@ -203,14 +206,20 @@
$c['storage.board_history'] = $c->share( function( $c ) {
$cache = $c['memcache.buffered'];
$mapper = new BasicObjectMapper(
- function( $rev ) {
- return $rev->toStorageRow( $rev );
+ function( $rev ) use( $c ) {
+ if ( $rev instanceof PostRevision ) {
+ return $c['storage.post.mapper']->toStorageRow(
$rev );
+ } elseif ( $rev instanceof Header ) {
+ return
$c['storage.header.mapper']->toStorageRow( $rev );
+ } else {
+ throw new \Flow\Exception\InvalidDataException(
'Invalid class for board history entry: ' . get_class( $rev ), 'fail-load-data'
);
+ }
},
- function( array $row, $obj = null ) {
+ function( array $row, $obj = null ) use( $c ) {
if ( $row['rev_type'] === 'header' ) {
- return Header::fromStorageRow( $row, $obj );
+ return
$c['storage.header.mapper']->fromStorageRow( $row, $obj );
} elseif ( $row['rev_type'] === 'post' ) {
- return PostRevision::fromStorageRow( $row, $obj
);
+ return
$c['storage.post.mapper']->fromStorageRow( $row, $obj );
} else {
throw new \Flow\Exception\InvalidDataException(
'Invalid rev_type for board history entry: ' . $row['rev_type'],
'fail-load-data' );
}
@@ -244,11 +253,13 @@
),
);
} );
+$c['storage.header.mapper'] = $c->share( function( $c ) {
+ return CachingObjectMapper::model( 'Flow\\Model\\Header', array(
'rev_id' ) );
+} );
$c['storage.header'] = $c->share( function( $c ) {
global $wgFlowExternalStore;
$cache = $c['memcache.buffered'];
- $mapper = BasicObjectMapper::model( 'Flow\\Model\\Header' );
$storage = new HeaderRevisionStorage( $c['db.factory'],
$wgFlowExternalStore );
$pk = new UniqueFeatureIndex(
@@ -272,7 +283,7 @@
),
);
- return new ObjectManager( $mapper, $storage, $indexes,
$c['storage.header.lifecycle-handlers'] );
+ return new ObjectManager( $c['storage.header.mapper'], $storage,
$indexes, $c['storage.header.lifecycle-handlers'] );
} );
// List of topic workflows and their owning discussion workflow
@@ -281,13 +292,14 @@
// Would also need object mapper adjustments to return array
// of two objects.
$c['storage.topic_list'] = $c->share( function( $c ) {
+ $primaryKey = array( 'topic_list_id', 'topic_id' );
$cache = $c['memcache.buffered'];
- $mapper = BasicObjectMapper::model( 'Flow\\Model\\TopicListEntry' );
+ $mapper = CachingObjectMapper::model( 'Flow\\Model\\TopicListEntry',
$primaryKey );
$storage = new BasicDbStorage(
// factory and table
$c['db.factory'], 'flow_topic_list',
// pk
- array( 'topic_list_id', 'topic_id' )
+ $primaryKey
);
$indexes = array(
new TopKIndex(
@@ -322,12 +334,13 @@
$c['collection.cache'],
);
} );
-
+$c['storage.post.mapper'] = $c->share( function( $c ) {
+ return CachingObjectMapper::model( 'Flow\\Model\\PostRevision', array(
'rev_id' ) );
+} );
$c['storage.post'] = $c->share( function( $c ) {
global $wgFlowExternalStore;
$cache = $c['memcache.buffered'];
$treeRepo = $c['repository.tree'];
- $mapper = BasicObjectMapper::model( 'Flow\\Model\\PostRevision' );
$storage = new PostRevisionStorage( $c['db.factory'],
$wgFlowExternalStore, $treeRepo );
$pk = new UniqueFeatureIndex( $cache, $storage, 'flow_revision:v4:pk',
array( 'rev_id' ) );
$indexes = array(
@@ -364,7 +377,7 @@
) ),
);
- return new ObjectManager( $mapper, $storage, $indexes,
$c['storage.post.lifecycle-handlers'] );
+ return new ObjectManager( $c['storage.post.mapper'], $storage,
$indexes, $c['storage.post.lifecycle-handlers'] );
} );
// Storage implementation for user subscriptions, separate from
storage.user_subs so it
// can be used in storage.user_subs.user_index as well.
diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index 761a5e1..787a611 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -724,6 +724,11 @@
* );
*/
class BasicObjectMapper implements ObjectMapper {
+
+ protected $toStorageRow;
+
+ protected $fromStorageRow;
+
public function __construct( $toStorageRow, $fromStorageRow ) {
$this->toStorageRow = $toStorageRow;
$this->fromStorageRow = $fromStorageRow;
@@ -736,11 +741,98 @@
public function toStorageRow( $object ) {
return call_user_func( $this->toStorageRow, $object );
}
+
public function fromStorageRow( array $row, $object = null ) {
return call_user_func( $this->fromStorageRow, $row, $object );
}
}
+class CachingObjectMapper implements ObjectMapper {
+
+ protected $toStorageRow;
+
+ protected $fromStorageRow;
+
+ protected $loaded;
+
+ public function __construct( $toStorageRow, $fromStorageRow, array
$primaryKey ) {
+ $this->toStorageRow = $toStorageRow;
+ $this->fromStorageRow = $fromStorageRow;
+ ksort( $primaryKey );
+ $this->primaryKey = $primaryKey;
+ $this->clear();
+ }
+
+ static public function model( $className, array $primaryKey ) {
+ return new self(
+ array( $className, 'toStorageRow' ),
+ array( $className, 'fromStorageRow' ),
+ $primaryKey
+ );
+ }
+
+ public function toStorageRow( $object ) {
+ $row = call_user_func( $this->toStorageRow, $object );
+ $pk = ObjectManager::splitFromRow( $row, $this->primaryKey );
+ if ( $pk === null ) {
+ // new object may not have pk yet, calling code
+ // should call self::fromStorageRow with $object to load
+ // db assigned pk and store obj in $this->loaded
+ } elseif ( !isset( $this->loaded[$pk] ) ) {
+ // first time this id has been seen
+ $this->loaded[$pk] = $object;
+ } elseif ( $this->loaded[$pk] !== $object ) {
+ // loaded object of this id is not same object
+ $class = get_class( $object );
+ $id = json_encode( $pk );
+ throw new \InvalidArgumentException( "Duplicate
'$class' objects for id $id" );
+ }
+ return $row;
+ }
+
+ public function fromStorageRow( array $row, $object = null ) {
+ $pk = ObjectManager::splitFromRow( $row, $this->primaryKey );
+ if ( $pk === null ) {
+ throw new \InvalidArgumentException( 'Storage row has
no pk' );
+ } elseif ( !isset( $this->loaded[$pk] ) ) {
+ // unserialize the object
+ return $this->loaded[$pk] = call_user_func(
$this->fromStorageRow, $row, $object );
+ } elseif ( $object === null ) {
+ // provide previously loaded object
+ return $this->loaded[$pk];
+ } elseif ( $object !== $this->loaded[$pk] ) {
+ // loaded object of this id is not same object
+ $class = get_class( $object );
+ $id = json_encode( $pk );
+ throw new \InvalidArgumentException( "Duplicate
'$class' objects for id $id" );
+ } else {
+ // object was provided, load $row into $object
+ // we already know $this->loaded[$pk] === $object
+ return call_user_func( $this->fromStorageRow, $row,
$object );
+ }
+ }
+
+ /**
+ * @param array|string $pk
+ * @return object
+ * @throws \InvalidArgumentException
+ * @throws \OutOfBoundsException
+ */
+ public function get( $pk ) {
+ $pk = (array)$pk;
+ ksort( $pk );
+ if ( array_keys( $pk ) !== $this->primaryKey ) {
+ throw new \InvalidArgumentException;
+ }
+ return $this->loaded[$pk];
+ }
+
+ public function clear() {
+ $this->loaded = new MultiDimArray;
+ }
+}
+
+
/**
* Base class for all WritableObjectStorage implementers
* which use a database as the backing store.
diff --git a/tests/CachingObjectMapperTest.php
b/tests/CachingObjectMapperTest.php
new file mode 100644
index 0000000..ebdef84
--- /dev/null
+++ b/tests/CachingObjectMapperTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Flow\Tests;
+
+use Flow\Data\CachingObjectMapper;
+
+class CachingObjectManagerTest extends \MediaWikiTestCase {
+
+ public function testReturnsSameObject() {
+ $mapper = $this->createMapper();
+ $object = $mapper->fromStorageRow( array( 'id' => 1 ) );
+ $this->assertSame( $object, $mapper->fromStorageRow( array(
'id' => 1 ) ) );
+ }
+
+ public function testAllowsNullPkOnPut() {
+ $this->createMapper()->toStorageRow( (object)array( 'id' =>
null ) );
+ $this->assertTrue( true );
+ }
+
+ protected function createMapper() {
+ $toStorageRow = function( $object ) { return (array)$object; };
+ $fromStorageRow = function( array $row, $object ) {
+ if ( $object === null ) {
+ return (object)$row;
+ } else {
+ return (object)( $row + (array)$object );
+ }
+ };
+ return new CachingObjectMapper( $toStorageRow, $fromStorageRow,
array( 'id' ) );
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/113527
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Idc7c496683ecd03ff91d9276374cef738157b094
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: EBernhardson <[email protected]>
Gerrit-Reviewer: Bsitu <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits