aboutsummaryrefslogtreecommitdiffstats
path: root/includes/jobqueue/JobFactory.php
blob: 9471fdcb6b25ca4c953c7c54b2ede25e22dcc19d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?php

namespace MediaWiki\JobQueue;

use Closure;
use InvalidArgumentException;
use MediaWiki\Page\PageReference;
use MediaWiki\Title\Title;
use Wikimedia\ObjectFactory\ObjectFactory;

/**
 * @since 1.40
 */
class JobFactory {

	private ObjectFactory $objectFactory;

	/** @var array<array|callable|string> Object specs, see ObjectFactory */
	private array $jobObjectSpecs;

	/**
	 * @param ObjectFactory $objectFactory
	 * @param array<array|callable|string> $jobObjectSpecs Object specs, see ObjectFactory
	 */
	public function __construct( ObjectFactory $objectFactory, array $jobObjectSpecs ) {
		$this->objectFactory = $objectFactory;
		$this->jobObjectSpecs = $jobObjectSpecs;
	}

	/**
	 * Create the appropriate object to handle a specific job.
	 *
	 * @note For backwards compatibility with Job::factory,
	 * this method also supports an alternative signature:
	 * @code
	 *   newJob(
	 *     string $command,
	 *     PageReference $page,
	 *     array $params
	 *   )
	 * @endcode
	 *
	 * @param string $command Job command
	 * @param array $params Job parameters
	 *
	 * @return Job
	 * @throws InvalidArgumentException
	 */
	public function newJob( string $command, $params = [] ): Job {
		if ( !isset( $this->jobObjectSpecs[ $command ] ) ) {
			throw new InvalidArgumentException( "Invalid job command '{$command}'" );
		}

		$spec = $this->jobObjectSpecs[ $command ];
		$needsTitle = $this->needsTitle( $command, $spec );

		// TODO: revisit support for old method signature
		if ( $params instanceof PageReference ) {
			// Backwards compatibility for old signature ($command, $title, $params)
			$title = Title::newFromPageReference( $params );
			$params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
		} elseif ( isset( $params['namespace'] ) && isset( $params['title'] ) ) {
			// Handle job classes that take title as constructor parameter.
			// If a newer classes like GenericParameterJob uses these parameters,
			// then this happens in Job::__construct instead.
			$title = Title::makeTitle(
				$params['namespace'],
				$params['title']
			);
		} else {
			// Default title for job classes not implementing GenericParameterJob.
			// This must be a valid title because it not directly passed to
			// our Job constructor, but rather its subclasses which may expect
			// to be able to use it.
			$title = Title::makeTitle(
				NS_SPECIAL,
				'Blankpage'
			);
		}

		if ( $needsTitle ) {
			$args = [ $title, $params ];
		} else {
			$args = [ $params ];
		}

		/** @var Job $job */
		$job = $this->objectFactory->createObject(
			$spec,
			[
				'allowClassName' => true,
				'allowCallable' => true,
				'extraArgs' => $args,
				'assertClass' => Job::class
			]
		);

		// TODO: create a setter, marked @internal
		$job->command = $command;
		return $job;
	}

	/**
	 * Determines whether the job class needs a Title to be passed
	 * as the first parameter to the constructor.
	 *
	 * @param string $command
	 * @param string|array|Closure $spec
	 *
	 * @return bool
	 */
	private function needsTitle( string $command, $spec ): bool {
		if ( is_callable( $spec ) ) {
			$needsTitle = true;
		} elseif ( is_array( $spec ) ) {
			if ( isset( $spec['needsPage'] ) ) {
				$needsTitle = $spec['needsPage'];
			} elseif ( isset( $spec['class'] ) ) {
				$needsTitle = !is_subclass_of( $spec['class'],
					GenericParameterJob::class );
			} elseif ( isset( $spec['factory'] ) ) {
				$needsTitle = true;
			} else {
				throw new InvalidArgumentException(
					"Invalid job specification for '{$command}': " .
					"must contain the 'class' or 'factory' key."
				);
			}
		} elseif ( is_string( $spec ) ) {
			$needsTitle = !is_subclass_of( $spec,
				GenericParameterJob::class );
		} else {
			throw new InvalidArgumentException(
				"Invalid job specification for '{$command}': " .
				"must be a callable, an object spec array, or a class name"
			);
		}

		return $needsTitle;
	}
}