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