MediaWiki  master
Job.php
Go to the documentation of this file.
1 <?php
30 abstract class Job implements RunnableJob {
32  public $command;
33 
35  public $params;
36 
38  public $metadata = [];
39 
41  protected $title;
42 
44  protected $removeDuplicates = false;
45 
47  protected $error;
48 
50  protected $teardownCallbacks = [];
51 
53  protected $executionFlags = 0;
54 
63  public static function factory( $command, $params = [] ) {
64  global $wgJobClasses;
65 
66  if ( $params instanceof Title ) {
67  // Backwards compatibility for old signature ($command, $title, $params)
68  $title = $params;
69  $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
70  } elseif ( isset( $params['namespace'] ) && isset( $params['title'] ) ) {
71  // Handle job classes that take title as constructor parameter.
72  // If a newer classes like GenericParameterJob uses these parameters,
73  // then this happens in Job::__construct instead.
74  $title = Title::makeTitle( $params['namespace'], $params['title'] );
75  } else {
76  // Default title for job classes not implementing GenericParameterJob.
77  // This must be a valid title because it not directly passed to
78  // our Job constructor, but rather it's subclasses which may expect
79  // to be able to use it.
80  $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
81  }
82 
83  if ( isset( $wgJobClasses[$command] ) ) {
84  $handler = $wgJobClasses[$command];
85 
86  if ( is_callable( $handler ) ) {
87  $job = call_user_func( $handler, $title, $params );
88  } elseif ( class_exists( $handler ) ) {
89  if ( is_subclass_of( $handler, GenericParameterJob::class ) ) {
90  $job = new $handler( $params );
91  } else {
92  $job = new $handler( $title, $params );
93  }
94  } else {
95  $job = null;
96  }
97 
98  if ( $job instanceof Job ) {
99  $job->command = $command;
100 
101  return $job;
102  } else {
103  throw new InvalidArgumentException(
104  "Could not instantiate job '$command': bad spec!"
105  );
106  }
107  }
108 
109  throw new InvalidArgumentException( "Invalid job command '{$command}'" );
110  }
111 
116  public function __construct( $command, $params = null ) {
117  if ( $params instanceof Title ) {
118  // Backwards compatibility for old signature ($command, $title, $params)
119  $title = $params;
120  $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
121  } else {
122  // Newer jobs may choose to not have a top-level title (e.g. GenericParameterJob)
123  $title = null;
124  }
125 
126  if ( !is_array( $params ) ) {
127  throw new InvalidArgumentException( '$params must be an array' );
128  }
129 
130  if (
131  $title &&
132  !isset( $params['namespace'] ) &&
133  !isset( $params['title'] )
134  ) {
135  // When constructing this class for submitting to the queue,
136  // normalise the $title arg of old job classes as part of $params.
137  $params['namespace'] = $title->getNamespace();
138  $params['title'] = $title->getDBkey();
139  }
140 
141  $this->command = $command;
142  $this->params = $params + [ 'requestId' => WebRequest::getRequestId() ];
143 
144  if ( $this->title === null ) {
145  // Set this field for access via getTitle().
146  $this->title = ( isset( $params['namespace'] ) && isset( $params['title'] ) )
147  ? Title::makeTitle( $params['namespace'], $params['title'] )
148  // GenericParameterJob classes without namespace/title params
149  // should not use getTitle(). Set an invalid title as placeholder.
150  : Title::makeTitle( NS_SPECIAL, '' );
151  }
152  }
153 
154  public function hasExecutionFlag( $flag ) {
155  return ( $this->executionFlags & $flag ) === $flag;
156  }
157 
161  public function getType() {
162  return $this->command;
163  }
164 
168  final public function getTitle() {
169  return $this->title;
170  }
171 
175  public function getParams() {
176  return $this->params;
177  }
178 
184  public function getMetadata( $field = null ) {
185  if ( $field === null ) {
186  return $this->metadata;
187  }
188 
189  return $this->metadata[$field] ?? null;
190  }
191 
198  public function setMetadata( $field, $value ) {
199  $old = $this->getMetadata( $field );
200  if ( $value === null ) {
201  unset( $this->metadata[$field] );
202  } else {
203  $this->metadata[$field] = $value;
204  }
205 
206  return $old;
207  }
208 
213  public function getReleaseTimestamp() {
214  return isset( $this->params['jobReleaseTimestamp'] )
215  ? wfTimestampOrNull( TS_UNIX, $this->params['jobReleaseTimestamp'] )
216  : null;
217  }
218 
223  public function getQueuedTimestamp() {
224  return isset( $this->metadata['timestamp'] )
225  ? wfTimestampOrNull( TS_UNIX, $this->metadata['timestamp'] )
226  : null;
227  }
228 
229  public function getRequestId() {
230  return $this->params['requestId'] ?? null;
231  }
232 
233  public function getReadyTimestamp() {
234  return $this->getReleaseTimestamp() ?: $this->getQueuedTimestamp();
235  }
236 
248  public function ignoreDuplicates() {
250  }
251 
252  public function allowRetries() {
253  return true;
254  }
255 
256  public function workItemCount() {
257  return 1;
258  }
259 
269  public function getDeduplicationInfo() {
270  $info = [
271  'type' => $this->getType(),
272  'params' => $this->getParams()
273  ];
274  if ( is_array( $info['params'] ) ) {
275  // Identical jobs with different "root" jobs should count as duplicates
276  unset( $info['params']['rootJobSignature'] );
277  unset( $info['params']['rootJobTimestamp'] );
278  // Likewise for jobs with different delay times
279  unset( $info['params']['jobReleaseTimestamp'] );
280  // Identical jobs from different requests should count as duplicates
281  unset( $info['params']['requestId'] );
282  // Queues pack and hash this array, so normalize the order
283  ksort( $info['params'] );
284  }
285 
286  return $info;
287  }
288 
308  public static function newRootJobParams( $key ) {
309  return [
310  'rootJobIsSelf' => true,
311  'rootJobSignature' => sha1( $key ),
312  'rootJobTimestamp' => wfTimestampNow()
313  ];
314  }
315 
321  public function getRootJobParams() {
322  return [
323  'rootJobSignature' => $this->params['rootJobSignature'] ?? null,
324  'rootJobTimestamp' => $this->params['rootJobTimestamp'] ?? null
325  ];
326  }
327 
333  public function hasRootJobParams() {
334  return isset( $this->params['rootJobSignature'] )
335  && isset( $this->params['rootJobTimestamp'] );
336  }
337 
342  public function isRootJob() {
343  return $this->hasRootJobParams() && !empty( $this->params['rootJobIsSelf'] );
344  }
345 
352  protected function addTeardownCallback( $callback ) {
353  $this->teardownCallbacks[] = $callback;
354  }
355 
356  public function teardown( $status ) {
357  foreach ( $this->teardownCallbacks as $callback ) {
358  call_user_func( $callback, $status );
359  }
360  }
361 
362  public function toString() {
363  $paramString = '';
364  if ( $this->params ) {
365  foreach ( $this->params as $key => $value ) {
366  if ( $paramString != '' ) {
367  $paramString .= ' ';
368  }
369  if ( is_array( $value ) ) {
370  $filteredValue = [];
371  foreach ( $value as $k => $v ) {
372  $json = FormatJson::encode( $v );
373  if ( $json === false || mb_strlen( $json ) > 512 ) {
374  $filteredValue[$k] = gettype( $v ) . '(...)';
375  } else {
376  $filteredValue[$k] = $v;
377  }
378  }
379  if ( count( $filteredValue ) <= 10 ) {
380  $value = FormatJson::encode( $filteredValue );
381  } else {
382  $value = "array(" . count( $value ) . ")";
383  }
384  } elseif ( is_object( $value ) && !method_exists( $value, '__toString' ) ) {
385  $value = "object(" . get_class( $value ) . ")";
386  }
387 
388  $flatValue = (string)$value;
389  if ( mb_strlen( $value ) > 1024 ) {
390  $flatValue = "string(" . mb_strlen( $value ) . ")";
391  }
392 
393  $paramString .= "$key={$flatValue}";
394  }
395  }
396 
397  $metaString = '';
398  foreach ( $this->metadata as $key => $value ) {
399  if ( is_scalar( $value ) && mb_strlen( $value ) < 1024 ) {
400  $metaString .= ( $metaString ? ",$key=$value" : "$key=$value" );
401  }
402  }
403 
404  $s = $this->command;
405  if ( is_object( $this->title ) ) {
406  $s .= " {$this->title->getPrefixedDBkey()}";
407  }
408  if ( $paramString != '' ) {
409  $s .= " $paramString";
410  }
411  if ( $metaString != '' ) {
412  $s .= " ($metaString)";
413  }
414 
415  return $s;
416  }
417 
418  protected function setLastError( $error ) {
419  $this->error = $error;
420  }
421 
422  public function getLastError() {
423  return $this->error;
424  }
425 }
getType()
Definition: Job.php:161
callable [] $teardownCallbacks
Definition: Job.php:50
getParams()
Definition: Job.php:175
hasRootJobParams()
Definition: Job.php:333
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:309
getTitle()
Definition: Job.php:168
hasExecutionFlag( $flag)
Definition: Job.php:154
string $error
Text for error that occurred last.
Definition: Job.php:47
__construct( $command, $params=null)
Definition: Job.php:116
getQueuedTimestamp()
Definition: Job.php:223
allowRetries()
Definition: Job.php:252
Class to both describe a background job and handle jobs.
Definition: Job.php:30
getReadyTimestamp()
Definition: Job.php:233
string $command
Definition: Job.php:32
const NS_SPECIAL
Definition: Defines.php:49
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
getReleaseTimestamp()
Definition: Job.php:213
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
$wgJobClasses
Maps jobs to their handlers; extensions can add to this to provide custom jobs.
ignoreDuplicates()
Whether the queue should reject insertion of this job if a duplicate exists.
Definition: Job.php:248
static newRootJobParams( $key)
Get "root job" parameters for a task.
Definition: Job.php:308
setLastError( $error)
Definition: Job.php:418
getDBkey()
Get the main part with underscores.
Definition: Title.php:1016
addTeardownCallback( $callback)
Definition: Job.php:352
getRootJobParams()
Definition: Job.php:321
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
getRequestId()
Definition: Job.php:229
setMetadata( $field, $value)
Definition: Job.php:198
Job that has a run() method and metadata accessors for JobQueue::pop() and JobQueue::ack() ...
Definition: RunnableJob.php:35
toString()
Definition: Job.php:362
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1040
static factory( $command, $params=[])
Create the appropriate object to handle a specific job.
Definition: Job.php:63
bool $removeDuplicates
Expensive jobs may set this to true.
Definition: Job.php:44
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
teardown( $status)
Definition: Job.php:356
int $executionFlags
Bitfield of JOB_* class constants.
Definition: Job.php:53
array $metadata
Additional queue metadata.
Definition: Job.php:38
if(count( $args)< 1) $job
getMetadata( $field=null)
Definition: Job.php:184
array $params
Array of job parameters.
Definition: Job.php:35
isRootJob()
Definition: Job.php:342
getLastError()
Definition: Job.php:422
getDeduplicationInfo()
Subclasses may need to override this to make duplication detection work.
Definition: Job.php:269
workItemCount()
Definition: Job.php:256
Title $title
Definition: Job.php:41