summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php339
1 files changed, 339 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php b/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php
new file mode 100644
index 00000000..f63406ff
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Deferred/CallableUpdate.php
@@ -0,0 +1,339 @@
+<?php
+
+namespace SMW\MediaWiki\Deferred;
+
+use Closure;
+use DeferrableUpdate;
+use DeferredUpdates;
+use Psr\Log\LoggerAwareTrait;
+use SMW\MediaWiki\Database;
+
+/**
+ * @see MWCallableUpdate
+ *
+ * @license GNU GPL v2+
+ * @since 2.4
+ */
+class CallableUpdate implements DeferrableUpdate {
+
+ use LoggerAwareTrait;
+
+ /**
+ * Updates that should run before flushing output buffer
+ */
+ const STAGE_PRESEND = 'pre';
+
+ /**
+ * Updates that should run after flushing output buffer
+ */
+ const STAGE_POSTSEND = 'post';
+
+ /**
+ * @var Closure|callable
+ */
+ protected $callback;
+
+ /**
+ * @var boolean
+ */
+ protected $isDeferrableUpdate = true;
+
+ /**
+ * @var boolean
+ */
+ protected $isCommandLineMode = false;
+
+ /**
+ * @var boolean
+ */
+ private $isPending = false;
+
+ /**
+ * @var string
+ */
+ private $origin = '';
+
+ /**
+ * @var array
+ */
+ private static $pendingUpdates = [];
+
+ /**
+ * @var string|null
+ */
+ private $fingerprint = null;
+
+ /**
+ * @var array
+ */
+ private static $queueList = [];
+
+ /**
+ * @var string
+ */
+ private $stage;
+
+ /**
+ * @since 2.4
+ *
+ * @param callable $callback|null
+ * @param Database|null $connection
+ */
+ public function __construct( callable $callback = null ) {
+
+ if ( $callback === null ) {
+ $callback = [ $this, 'emptyCallback' ];
+ }
+
+ $this->callback = $callback;
+ $this->stage = self::STAGE_POSTSEND;
+ }
+
+ /**
+ * @see https://www.mediawiki.org/wiki/Manual:$wgCommandLineMode
+ * Indicates whether MW is running in command-line mode.
+ *
+ * @since 2.5
+ *
+ * @param boolean $isCommandLineMode
+ */
+ public function isCommandLineMode( $isCommandLineMode ) {
+ $this->isCommandLineMode = $isCommandLineMode;
+ }
+
+ /**
+ * @since 3.0
+ */
+ public function asPresend() {
+ $this->stage = self::STAGE_PRESEND;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @return string
+ */
+ public function getStage() {
+ return $this->stage;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param callable $callback
+ */
+ public function setCallback( callable $callback ) {
+ $this->callback = $callback;
+ }
+
+ /**
+ * @deprecated since 3.0, use DeferredCallableUpdate::isDeferrableUpdate
+ * @since 2.4
+ */
+ public function enabledDeferredUpdate( $enabledDeferredUpdate = true ) {
+ $this->isDeferrableUpdate( $enabledDeferredUpdate );
+ }
+
+ /**
+ * @note Unit/Integration tests in MW 1.26- showed ambiguous behaviour when
+ * run in deferred mode because not all MW operations were supporting late
+ * execution.
+ *
+ * @since 3.0
+ */
+ public function isDeferrableUpdate( $isDeferrableUpdate ) {
+ $this->isDeferrableUpdate = (bool)$isDeferrableUpdate;
+ }
+
+ /**
+ * @note If wgCommandLineMode = true (e.g. MW is in CLI mode) then
+ * DeferredUpdates::addUpdate pushes updates directly into execution mode
+ * which may not be desirable for all update processes therefore hold on to it
+ * by using an internal waitableUpdate list and release them at convenience.
+ *
+ * @since 2.4
+ *
+ * @param booloan $isPending
+ */
+ public function markAsPending( $isPending = false ) {
+ $this->isPending = (bool)$isPending;
+ }
+
+ /**
+ * @note Set a fingerprint allowing it to track and detect duplicate update
+ * requests while being unprocessed.
+ *
+ * @since 2.5
+ *
+ * @param string|null $queue
+ */
+ public function setFingerprint( $fingerprint = null ) {
+ $this->fingerprint = md5( $fingerprint );
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param string|null $queue
+ */
+ public function getFingerprint() {
+ return $this->fingerprint;
+ }
+
+ /**
+ * @since 2.5
+ *
+ * @param string $origin
+ */
+ public function setOrigin( $origin ) {
+ $this->origin = $origin;
+ }
+
+ /**
+ * @see DeferrableCallback::getOrigin
+ *
+ * @since 2.5
+ *
+ * @return string
+ */
+ public function getOrigin() {
+
+ if ( is_string( $this->origin ) ) {
+ $this->origin = [ $this->origin ];
+ }
+
+ return json_encode( $this->origin );
+ }
+
+ /**
+ * @since 2.4
+ */
+ public static function releasePendingUpdates() {
+ foreach ( self::$pendingUpdates as $update ) {
+ DeferredUpdates::addUpdate( $update );
+ }
+
+ self::$pendingUpdates = [];
+ }
+
+ /**
+ * @see DeferrableUpdate::doUpdate
+ *
+ * @since 2.4
+ */
+ public function doUpdate() {
+ call_user_func( $this->callback );
+ unset( self::$queueList[$this->fingerprint] );
+
+ $this->logger->info(
+ [
+ 'DeferrableUpdate',
+ 'Update completed: {origin} (fingerprint:{fingerprint})'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'origin' => $this->getOrigin(),
+ 'fingerprint' => $this->fingerprint
+ ]
+ );
+ }
+
+ /**
+ * @since 2.5
+ */
+ public function pushUpdate() {
+
+ if ( $this->fingerprint !== null && isset( self::$queueList[$this->fingerprint] ) ) {
+ $this->logger->info(
+ [
+ 'DeferrableUpdate',
+ 'Push: {origin} (fingerprint: {fingerprint} is already listed, skip)'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'origin' => $this->getOrigin(),
+ 'fingerprint' => $this->fingerprint
+ ]
+ );
+ return;
+ }
+
+ self::$queueList[$this->fingerprint] = true;
+
+ if ( $this->isPending && $this->isDeferrableUpdate ) {
+
+ $this->logger->info(
+ [
+ 'DeferrableUpdate',
+ 'Push: {origin} (as pending DeferredCallableUpdate)'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'origin' => $this->getOrigin(),
+ 'fingerprint' => $this->fingerprint
+ ]
+ );
+
+ return self::$pendingUpdates[] = $this;
+ }
+
+ if ( !$this->isCommandLineMode && $this->isDeferrableUpdate ) {
+ return $this->addUpdate( $this );
+ }
+
+ $this->doUpdate();
+ }
+
+ protected function addUpdate( $update ) {
+
+ $this->logger->info(
+ [
+ 'DeferrableUpdate',
+ 'Added: {ctx}'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'ctx' => json_encode(
+ $this->getLoggableContext(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
+ )
+ ]
+ );
+ $stage = null;
+
+ if ( $update->getStage() === self::STAGE_POSTSEND && defined( 'DeferredUpdates::POSTSEND' ) ) {
+ $stage = DeferredUpdates::POSTSEND;
+ }
+
+ if ( $update->getStage() === self::STAGE_PRESEND && defined( 'DeferredUpdates::PRESEND' ) ) {
+ $stage = DeferredUpdates::PRESEND;
+ }
+
+ DeferredUpdates::addUpdate( $update, $stage );
+ }
+
+ protected function getLoggableContext() {
+ return [
+ 'origin' => $this->origin,
+ 'fingerprint' => $this->fingerprint,
+ 'stage' => $this->stage
+ ];
+ }
+
+ protected function emptyCallback() {
+ $this->logger->info(
+ [
+ 'DeferrableUpdate',
+ 'Empty callback!'
+ ],
+ [
+ 'role' => 'developer',
+ 'method' => __METHOD__
+ ]
+ );
+ }
+
+}