Matthias Mullie has uploaded a new change for review.
https://gerrit.wikimedia.org/r/111366
Change subject: Add *Revisionable classes
......................................................................
Add *Revisionable classes
In their current state, they should help in efficiently fetching certain
revisions of a certain post/header.
I would like to see them be "expanded" to hold stuff that we currently do in
revisions, but is not really revision-specific (e.g. getChildren, getCreator...)
I'll use this to change/fix some permissions in a follow-up patch, which should
make it easier to load previous & most recent revisions.
Change-Id: If53f0b4a1367f4cd7087dc0fad3326436f3a6d9f
---
M Flow.php
A includes/Model/AbstractRevisionable.php
A includes/Model/HeaderRevisionable.php
A includes/Model/LocalCacheAbstractRevisionable.php
A includes/Model/PostRevisionable.php
5 files changed, 432 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow
refs/changes/66/111366/1
diff --git a/Flow.php b/Flow.php
index 44fb6e8..13ada32 100755
--- a/Flow.php
+++ b/Flow.php
@@ -87,6 +87,10 @@
$wgAutoloadClasses['Flow\Model\TopicListEntry'] = $dir .
'includes/Model/TopicListEntry.php';
$wgAutoloadClasses['Flow\Model\Workflow'] = $dir .
'includes/Model/Workflow.php';
$wgAutoloadClasses['Flow\Model\UUID'] = "$dir/includes/Model/UUID.php";
+$wgAutoloadClasses['Flow\Model\AbstractRevisionable'] = $dir .
'includes/Model/AbstractRevisionable.php';
+$wgAutoloadClasses['Flow\Model\LocalCacheAbstractRevisionable'] = $dir .
'includes/Model/LocalCacheAbstractRevisionable.php';
+$wgAutoloadClasses['Flow\Model\PostRevisionable'] = $dir .
'includes/Model/PostRevisionable.php';
+$wgAutoloadClasses['Flow\Model\HeaderRevisionable'] = $dir .
'includes/Model/HeaderRevisionable.php';
// Helpers for templating
$wgAutoloadClasses['Flow\View\PostActionMenu'] =
"$dir/includes/View/PostActionMenu.php";
diff --git a/includes/Model/AbstractRevisionable.php
b/includes/Model/AbstractRevisionable.php
new file mode 100644
index 0000000..8ca6e33
--- /dev/null
+++ b/includes/Model/AbstractRevisionable.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace Flow\Model;
+
+use Flow\Container;
+use Flow\Exception\InvalidDataException;
+
+abstract class AbstractRevisionable {
+ /**
+ * Id of the revisionable object.
+ *
+ * @var UUID
+ */
+ protected $uuid;
+
+ /**
+ * @var \Flow\Data\ObjectManager
+ */
+ protected $storage;
+
+ /**
+ * Array of revisions for this object.
+ *
+ * @var array
+ */
+ protected $revisions = array();
+
+ /**
+ * Returns the revision class name for this specific object (e.g.
Header,
+ * PostRevision)
+ *
+ * @return string
+ */
+ abstract public function getRevisionClass();
+
+ /**
+ * Returns the DB column that holds the revision hierarchy, where all
+ * revisions are mapped to a shared object id.
+ * E.g. a post can have multiple revisions, all of which have their own
id;
+ * but they're identifiable as revisions of the same post because they
share
+ * a common postId (in tree_rev_descendant_id)
+ *
+ * @return string
+ */
+ abstract public function getIdColumn();
+
+ /**
+ * Returns the object's UUID, given an AbstractRevision (not to be
confused
+ * with that single revision's UUID!)
+ *
+ * @param AbstractRevision $revision
+ * @return UUID
+ */
+ abstract public function getId( AbstractRevision $revision );
+
+ /**
+ * Use the static methods to load an object from a given revision.
+ *
+ * @see AbstractRevisionable::newFromId
+ * @see AbstractRevisionable::newFromRevision
+ * @see AbstractRevisionable::newFromRevisionId
+ *
+ * @param AbstractRevision[optional] $revision
+ */
+ protected function __construct( UUID $uuid ) {
+ $this->uuid = $uuid ;
+ $this->storage = Container::get( 'storage' )->getStorage(
$this->getRevisionClass() );
+ }
+
+ /**
+ * Instantiate a new object based on its id.
+ *
+ * @param UUID $uuid
+ * @return AbstractRevisionable
+ */
+ public static function newFromId( UUID $uuid ) {
+ return new static( $uuid );
+ }
+
+ /**
+ * Instantiate a new object based off of an AbstractRevision object.
+ *
+ * @param AbstractRevision $revision
+ * @return AbstractRevisionable
+ */
+ public static function newFromRevision( AbstractRevision $revision ) {
+ // create bogus object to access getId()
+ $object = new static( UUID::create() );
+ $uuid = $object->getId( $revision );
+ return static::newFromId( $uuid );
+ }
+
+ /**
+ * Instantiate a new object based off of a revision's UUID.
+ *
+ * @param UUID $uuid
+ * @return AbstractRevisionable
+ */
+ public static function newFromRevisionId( UUID $uuid ) {
+ // create bogus object to access getStorage()
+ $object = new static( UUID::create() );
+ $revision = $object->getStorage()->get( $uuid );
+ return static::newFromRevision( $revision );
+ }
+
+ /**
+ * @return \Flow\Data\ObjectManager
+ */
+ public function getStorage() {
+ return $this->storage;
+ }
+
+ /**
+ * Returns all revisions.
+ *
+ * @return AbstractRevision
+ */
+ public function getAllRevisions() {
+ if ( !$this->revisions ) {
+ $this->revisions = $this->storage->find(
+ array( $this->getIdColumn() => $this->uuid ),
+ array( 'sort' => 'rev_id', 'order' => 'DESC' )
+ );
+
+ if ( !$this->revisions ) {
+ throw new InvalidDataException( 'Revisions for
' . $this->uuid . ' could not be found', 'invalid-revision-id' );
+ }
+ }
+
+ return $this->revisions;
+ }
+
+ /**
+ * Returns the revision with the given id.
+ *
+ * @param UUID $uuid
+ * @return AbstractRevision|null null if there is no such revision
+ */
+ public function getRevision( UUID $uuid ) {
+ // make sure all revisions have been loaded
+ $this->getAllRevisions();
+
+ if ( !isset( $this->revisions[$uuid->toHex()] ) ) {
+ return null;
+ }
+
+ // find requested id, based on given revision
+ return $this->revisions[$uuid->toHex()];
+ }
+
+ /**
+ * Returns the oldest revision.
+ *
+ * @return AbstractRevision
+ */
+ public function getFirstRevision() {
+ $revisions = (array) $this->getAllRevisions();
+ return array_shift( $revisions );
+ }
+
+ /**
+ * Returns the most recent revision.
+ *
+ * @return AbstractRevision
+ */
+ public function getLastRevision() {
+ $revisions = (array) $this->getAllRevisions();
+ return array_pop( $revisions );
+ }
+
+ /**
+ * Given a certain revision, returns the previous revision.
+ *
+ * @param AbstractRevision $revision
+ * @return AbstractRevision|null null if there is no previous revision
+ */
+ public function getPreviousRevision( AbstractRevision $revision ) {
+ return $this->getRevision( $revision->getPrevRevisionId() );
+ }
+
+ /**
+ * Given a certain revision, returns the next revision.
+ *
+ * @param AbstractRevision $revision
+ * @return AbstractRevision|null null if there is no next revision
+ */
+ public function getNextRevision( AbstractRevision $revision ) {
+ // make sure all revisions have been loaded
+ $this->getAllRevisions();
+
+ // find requested id, based on given revision
+ $ids = array_keys( $this->revisions );
+ $current = array_search( $revision->getRevisionId()->getHex(),
$ids );
+ $next = $current + 1;
+
+ if ( $next >= count( $ids ) ) {
+ return null;
+ }
+
+ return $this->getRevision( UUID::create( $ids[$next] ) );
+ }
+
+ /**
+ * Pass-through to call latest revision's methods.
+ *
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ */
+ public function __call( $name, $arguments ) {
+ return $this->getLastRevision()->{$name}( $arguments );
+ }
+
+ /**
+ * Pass-through to get latest revision's properties.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get( $name ) {
+ return $this->getLastRevision()->{$name};
+ }
+
+ /**
+ * Pass-through to set latest revision's properties.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return mixed
+ */
+ public function __set( $name, $value ) {
+ return $this->getLastRevision()->{$name} = $value;
+ }
+}
diff --git a/includes/Model/HeaderRevisionable.php
b/includes/Model/HeaderRevisionable.php
new file mode 100644
index 0000000..74db8c3
--- /dev/null
+++ b/includes/Model/HeaderRevisionable.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Flow\Model;
+
+class HeaderRevisionable extends LocalCacheAbstractRevisionable {
+ public function getRevisionClass() {
+ return 'Flow\\Model\\Header';
+ }
+
+ public function getIdColumn() {
+ return 'header_workflow_id';
+ }
+
+ public function getId( AbstractRevision $revision ) {
+ return $revision->getWorkflowId();
+ }
+}
diff --git a/includes/Model/LocalCacheAbstractRevisionable.php
b/includes/Model/LocalCacheAbstractRevisionable.php
new file mode 100644
index 0000000..a7896f0
--- /dev/null
+++ b/includes/Model/LocalCacheAbstractRevisionable.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace Flow\Model;
+
+/**
+ * LocalBufferedCache saves all data that has been requested in an internal
+ * cache (in memory, per request). This provides the opportunity of (trying to)
+ * be smart about what results we fetch.
+ * The class extends the default AbstractRevisionable to make sure not all
+ * revisions are loaded unless we really need them. It could very well be that
+ * perhaps 5 recent revisions have already been loaded in other parts of the
+ * code, and we only need the 3rd most recent, in which case we shouldn't
+ * try to fetch all of them.
+ */
+abstract class LocalCacheAbstractRevisionable extends AbstractRevisionable {
+ /**
+ * Returns all revisions.
+ *
+ * @return AbstractRevision
+ */
+ public function getAllRevisions() {
+ // if we have not yet loaded everything, just clear what we
have and
+ // fetch from cache
+ if ( $this->loaded() ) {
+ $this->revisions = array();
+ }
+
+ return parent::getAllRevisions();
+ }
+
+ /**
+ * Returns the revision with the given id.
+ *
+ * @param UUID $uuid
+ * @return AbstractRevision|null null if there is no such revision
+ */
+ public function getRevision( UUID $uuid ) {
+ // check if fetching last already res
+ if ( isset( $this->revisions[$uuid->getHex() ] ) ) {
+ return $this->revisions[$uuid->getHex() ];
+ }
+
+ /*
+ * The strategy here is to avoid having to call
getAllRevisions(), which
+ * is most likely to have to load (fresh) data that is not yet
in
+ * LocalBufferedCache's internal cache.
+ * To do so, we'll build the $this->revisions array by hand.
Starting at
+ * the most recent revision and going up 1 revision at a time,
checking
+ * if it is already in LocalBufferedCache's cache.
+ * If, however, we can't find the requested revisions (or one
of the
+ * revisions on our way to the requested revision) in the
internal cache
+ * of LocalBufferedCache, we'll just bail and load all
revisions after
+ * all: if we do have to fetch data, might as well do it all in
1 go!
+ */
+ while ( !$this->loaded() ) {
+ // fetch current oldest revision
+ $oldest = $this->getOldestLoaded();
+
+ // fetch that one's preceeding revision id
+ $previousId = $oldest->getPrevRevisionId();
+
+ // check if it's in local storage already
+ if ( $this->getStorage()->got( $previousId ) ) {
+ $revision = $this->getStorage()->get(
$previousId );
+
+ // add this revision to revisions array
+ array_unshift( $this->revisions, $revision );
+
+ // stop iterating if we've found the one we
wanted
+ if ( $previousId->getHex() === $uuid->getHex()
) {
+ break;
+ }
+ } else {
+ // revision not found in local storage: load
all revisions
+ $this->getAllRevisions();
+ break;
+ }
+ }
+
+ if ( !isset( $this->revisions[$uuid->toHex()] ) ) {
+ return null;
+ }
+
+ return $this->revisions[$uuid->toHex()];
+ }
+
+ /**
+ * Returns the most recent revision.
+ *
+ * @return AbstractRevision
+ */
+ public function getLastRevision() {
+ // if $revisions is not empty, it will always have the last
revision,
+ // at the end of the array
+ if ( $this->revisions ) {
+ return end( $this->revisions );
+ }
+
+ $attributes = array( $this->getIdColumn() => $this->uuid );
+ $options = array( 'sort' => 'rev_id', 'limit' => 1, 'order' =>
'DESC' );
+
+ if ( $this->storage->found( $attributes, $options ) ) {
+ // if last revision is already known in local cache,
fetch it
+ return $this->find( $attributes, $options );
+
+ } else {
+ // otherwise, might as well fetch al previous revisions
while we're at
+ // it - saved roundtrips to cache/db
+ unset( $options['limit'] );
+ $this->revisions = $this->storage->find( $attributes,
$options );
+ return end( $this->revisions );
+ }
+ }
+
+ /**
+ * Given a certain revision, returns the next revision.
+ *
+ * @param AbstractRevision $revision
+ * @return AbstractRevision|null null if there is no next revision
+ */
+ public function getNextRevision( AbstractRevision $revision ) {
+ // make sure the given revision is loaded
+ $this->getRevision( $revision->getRevisionId() );
+
+ // find requested id, based on given revision
+ $ids = array_keys( $this->revisions );
+ $current = array_search( $revision->getRevisionId()->getHex(),
$ids );
+ $next = $current + 1;
+
+ if ( $next >= count( $ids ) ) {
+ return null;
+ }
+
+ return $this->getRevision( UUID::create( $ids[$next] ) );
+ }
+
+
+ /**
+ * Returns true if all revisions have been loaded into $this->revisions.
+ *
+ * @return bool
+ */
+ public function loaded() {
+ $first = reset( $this->revisions );
+ return $first && $first->getPrevRevisionId() === null;
+ }
+
+ /**
+ * Returns the oldest revision that has already been fetched via this
class.
+ *
+ * @return AbstractRevision
+ */
+ public function getOldestLoaded() {
+ if ( !$this->revisions ) {
+ return $this->getLastRevision();
+ }
+
+ return reset( $this->revisions );
+ }
+}
diff --git a/includes/Model/PostRevisionable.php
b/includes/Model/PostRevisionable.php
new file mode 100644
index 0000000..228fda1
--- /dev/null
+++ b/includes/Model/PostRevisionable.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Flow\Model;
+
+class PostRevisionable extends LocalCacheAbstractRevisionable {
+ public function getRevisionClass() {
+ return 'Flow\\Model\\PostRevision';
+ }
+
+ public function getIdColumn() {
+ return 'tree_rev_descendant_id';
+ }
+
+ public function getId( AbstractRevision $revision ) {
+ return $revision->getPostId();
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/111366
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If53f0b4a1367f4cd7087dc0fad3326436f3a6d9f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits