Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
87.04% |
47 / 54 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
JobFactory | |
87.04% |
47 / 54 |
|
33.33% |
1 / 3 |
15.49 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
newJob | |
96.67% |
29 / 30 |
|
0.00% |
0 / 1 |
7 | |||
needsTitle | |
72.73% |
16 / 22 |
|
0.00% |
0 / 1 |
7.99 |
1 | <?php |
2 | |
3 | namespace MediaWiki\JobQueue; |
4 | |
5 | use Closure; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Page\PageReference; |
8 | use MediaWiki\Title\Title; |
9 | use Wikimedia\ObjectFactory\ObjectFactory; |
10 | |
11 | /** |
12 | * @since 1.40 |
13 | */ |
14 | class JobFactory { |
15 | |
16 | private ObjectFactory $objectFactory; |
17 | |
18 | /** @var array<array|callable|string> Object specs, see ObjectFactory */ |
19 | private array $jobObjectSpecs; |
20 | |
21 | /** |
22 | * @param ObjectFactory $objectFactory |
23 | * @param array<array|callable|string> $jobObjectSpecs Object specs, see ObjectFactory |
24 | */ |
25 | public function __construct( ObjectFactory $objectFactory, array $jobObjectSpecs ) { |
26 | $this->objectFactory = $objectFactory; |
27 | $this->jobObjectSpecs = $jobObjectSpecs; |
28 | } |
29 | |
30 | /** |
31 | * Create the appropriate object to handle a specific job. |
32 | * |
33 | * @note For backwards compatibility with Job::factory, |
34 | * this method also supports an alternative signature: |
35 | * @code |
36 | * newJob( |
37 | * string $command, |
38 | * PageReference $page, |
39 | * array $params |
40 | * ) |
41 | * @endcode |
42 | * |
43 | * @param string $command Job command |
44 | * @param array $params Job parameters |
45 | * |
46 | * @return Job |
47 | * @throws InvalidArgumentException |
48 | */ |
49 | public function newJob( string $command, $params = [] ): Job { |
50 | if ( !isset( $this->jobObjectSpecs[ $command ] ) ) { |
51 | throw new InvalidArgumentException( "Invalid job command '{$command}'" ); |
52 | } |
53 | |
54 | $spec = $this->jobObjectSpecs[ $command ]; |
55 | $needsTitle = $this->needsTitle( $command, $spec ); |
56 | |
57 | // TODO: revisit support for old method signature |
58 | if ( $params instanceof PageReference ) { |
59 | // Backwards compatibility for old signature ($command, $title, $params) |
60 | $title = Title::newFromPageReference( $params ); |
61 | $params = func_num_args() >= 3 ? func_get_arg( 2 ) : []; |
62 | } elseif ( isset( $params['namespace'] ) && isset( $params['title'] ) ) { |
63 | // Handle job classes that take title as constructor parameter. |
64 | // If a newer classes like GenericParameterJob uses these parameters, |
65 | // then this happens in Job::__construct instead. |
66 | $title = Title::makeTitle( |
67 | $params['namespace'], |
68 | $params['title'] |
69 | ); |
70 | } else { |
71 | // Default title for job classes not implementing GenericParameterJob. |
72 | // This must be a valid title because it not directly passed to |
73 | // our Job constructor, but rather its subclasses which may expect |
74 | // to be able to use it. |
75 | $title = Title::makeTitle( |
76 | NS_SPECIAL, |
77 | 'Blankpage' |
78 | ); |
79 | } |
80 | |
81 | if ( $needsTitle ) { |
82 | $args = [ $title, $params ]; |
83 | } else { |
84 | $args = [ $params ]; |
85 | } |
86 | |
87 | /** @var Job $job */ |
88 | $job = $this->objectFactory->createObject( |
89 | $spec, |
90 | [ |
91 | 'allowClassName' => true, |
92 | 'allowCallable' => true, |
93 | 'extraArgs' => $args, |
94 | 'assertClass' => Job::class |
95 | ] |
96 | ); |
97 | |
98 | // TODO: create a setter, marked @internal |
99 | $job->command = $command; |
100 | return $job; |
101 | } |
102 | |
103 | /** |
104 | * Determines whether the job class needs a Title to be passed |
105 | * as the first parameter to the constructor. |
106 | * |
107 | * @param string $command |
108 | * @param string|array|Closure $spec |
109 | * |
110 | * @return bool |
111 | */ |
112 | private function needsTitle( string $command, $spec ): bool { |
113 | if ( is_callable( $spec ) ) { |
114 | $needsTitle = true; |
115 | } elseif ( is_array( $spec ) ) { |
116 | if ( isset( $spec['needsPage'] ) ) { |
117 | $needsTitle = $spec['needsPage']; |
118 | } elseif ( isset( $spec['class'] ) ) { |
119 | $needsTitle = !is_subclass_of( $spec['class'], |
120 | GenericParameterJob::class ); |
121 | } elseif ( isset( $spec['factory'] ) ) { |
122 | $needsTitle = true; |
123 | } else { |
124 | throw new InvalidArgumentException( |
125 | "Invalid job specification for '{$command}': " . |
126 | "must contain the 'class' or 'factory' key." |
127 | ); |
128 | } |
129 | } elseif ( is_string( $spec ) ) { |
130 | $needsTitle = !is_subclass_of( $spec, |
131 | GenericParameterJob::class ); |
132 | } else { |
133 | throw new InvalidArgumentException( |
134 | "Invalid job specification for '{$command}': " . |
135 | "must be a callable, an object spec array, or a class name" |
136 | ); |
137 | } |
138 | |
139 | return $needsTitle; |
140 | } |
141 | } |