Allow callback functions for creating jobs
authorLucas Werkmeister <lucas.werkmeister@wikimedia.de>
Thu, 4 May 2017 16:00:28 +0000 (18:00 +0200)
committerLucas Werkmeister <lucas.werkmeister@wikimedia.de>
Tue, 9 May 2017 17:26:13 +0000 (19:26 +0200)
$wgJobClasses can now specify a factory function for creating a job,
instead of a class to be instantiated directly. This makes it possible
to inject services in a job constructor, and register a factory function
that calls the constructor with default services.

This follows Ieb85493a7765 and Ia2107dc5af78, which introduced factory
functions for API modules and special pages.

Change-Id: I0461e59da2a8fa6681e3b1fcdfc38bfed7f3ac32

RELEASE-NOTES-1.30
docs/extension.schema.v1.json
docs/extension.schema.v2.json
includes/DefaultSettings.php
includes/jobqueue/Job.php
tests/phpunit/includes/jobqueue/JobTest.php

index cdf8ba4..5370c6d 100644 (file)
@@ -12,6 +12,11 @@ production.
   case.
 * $wgShellLocale now affects LC_ALL rather than only LC_CTYPE. See
   documentation of $wgShellLocale for details.
+* $wgJobClasses may now specify callback functions
+  as an alternative to plain class names.
+  This is intended for extensions that want control
+  over the instantiation of their jobs,
+  to allow for proper dependency injection.
 
 === New features in 1.30 ===
 * …
index 3d6eda9..f241cf5 100644 (file)
                },
                "JobClasses": {
                        "type": "object",
-                       "description": "Job types this extension implements (mapping of job type to class name)"
+                       "description": "Job types this extension implements (mapping of job type to class name or factory function)"
                },
                "LogTypes": {
                        "type": "array",
index a2fdf65..137dd51 100644 (file)
                },
                "JobClasses": {
                        "type": "object",
-                       "description": "Job types this extension implements (mapping of job type to class name)"
+                       "description": "Job types this extension implements (mapping of job type to class name or factory function)"
                },
                "LogTypes": {
                        "type": "array",
index 7c18fcc..a6bc7e5 100644 (file)
@@ -7332,8 +7332,10 @@ $wgServiceWiringFiles = [
 ];
 
 /**
- * Maps jobs to their handling classes; extensions
- * can add to this to provide custom jobs
+ * Maps jobs to their handlers; extensions
+ * can add to this to provide custom jobs.
+ * A job handler should either be a class name to be instantiated,
+ * or (since 1.30) a callback to use for creating the job object.
  */
 $wgJobClasses = [
        'refreshLinks' => 'RefreshLinksJob',
index f814cee..703e485 100644 (file)
@@ -69,12 +69,22 @@ abstract class Job implements IJobSpecification {
                global $wgJobClasses;
 
                if ( isset( $wgJobClasses[$command] ) ) {
-                       $class = $wgJobClasses[$command];
-
-                       $job = new $class( $title, $params );
-                       $job->command = $command;
+                       $handler = $wgJobClasses[$command];
+
+                       if ( is_callable( $handler ) ) {
+                               $job = call_user_func( $handler, $title, $params );
+                       } elseif ( class_exists( $handler ) ) {
+                               $job = new $handler( $title, $params );
+                       } else {
+                               $job = null;
+                       }
 
-                       return $job;
+                       if ( $job instanceof Job ) {
+                               $job->command = $command;
+                               return $job;
+                       } else {
+                               throw new InvalidArgumentException( "Cannot instantiate job '$command': bad spec!" );
+                       }
                }
 
                throw new InvalidArgumentException( "Invalid job command '{$command}'" );
index 1deb7aa..00c47c8 100644 (file)
@@ -91,4 +91,41 @@ class JobTest extends MediaWikiTestCase {
                return $mock;
        }
 
+       /**
+        * @dataProvider provideTestJobFactory
+        *
+        * @param mixed $handler
+        *
+        * @covers Job::factory
+        */
+       public function testJobFactory( $handler ) {
+               $this->mergeMWGlobalArrayValue( 'wgJobClasses', [ 'testdummy' => $handler ] );
+
+               $job = Job::factory( 'testdummy', Title::newMainPage(), [] );
+               $this->assertInstanceOf( NullJob::class, $job );
+
+               $job2 = Job::factory( 'testdummy', Title::newMainPage(), [] );
+               $this->assertInstanceOf( NullJob::class, $job2 );
+               $this->assertNotSame( $job, $job2, 'should not reuse instance' );
+       }
+
+       public function provideTestJobFactory() {
+               return [
+                       'class name' => [ 'NullJob' ],
+                       'closure' => [ function( Title $title, array $params ) {
+                               return new NullJob( $title, $params );
+                       } ],
+                       'function' => [ [ $this, 'newNullJob' ] ],
+                       'static function' => [ self::class . '::staticNullJob' ]
+               ];
+       }
+
+       public function newNullJob( Title $title, array $params ) {
+               return new NullJob( $title, $params );
+       }
+
+       public static function staticNullJob( Title $title, array $params ) {
+               return new NullJob( $title, $params );
+       }
+
 }