Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaskRunner
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 13
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 runNamedTask
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 loadExtensions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 findNamedTask
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 isSkipped
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isComplete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 runTask
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 addTaskStartListener
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addTaskEndListener
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSkippedTasks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentTaskName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 dumpTaskList
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Installer\Task;
4
5use MediaWiki\Status\Status;
6
7/**
8 * @internal For use by the installer
9 */
10class TaskRunner {
11    /** @var TaskList|Task[] */
12    private $tasks;
13    /** @var TaskFactory */
14    private $taskFactory;
15    /** @var string */
16    private $profile;
17    /** @var callable[] */
18    private $taskStartListeners;
19    /** @var callable[] */
20    private $taskEndListeners;
21    /** @var array<string,bool> */
22    private $skippedTasks = [];
23    /** @var array<string,bool> */
24    private $completedTasks = [];
25    /** @var string|null */
26    private $currentTaskName;
27
28    public function __construct( TaskList $tasks, TaskFactory $taskFactory, string $profile ) {
29        $this->tasks = $tasks;
30        $this->taskFactory = $taskFactory;
31        $this->profile = $profile;
32    }
33
34    /**
35     * Run all non-skipped tasks and return a merged Status
36     *
37     * @return Status
38     */
39    public function execute() {
40        $overallStatus = Status::newGood();
41        $overallStatus->merge( $this->loadExtensions() );
42        if ( !$overallStatus->isOK() ) {
43            return $overallStatus;
44        }
45
46        /** @var Task $task */
47        foreach ( $this->tasks as $task ) {
48            if ( $this->isSkipped( $task ) || $this->isComplete( $task ) ) {
49                continue;
50            }
51
52            $status = $this->runTask( $task );
53            $overallStatus->merge( $status );
54
55            // If we've hit some sort of fatal, we need to bail.
56            // Callback already had a chance to do output above.
57            if ( !$status->isOK() ) {
58                break;
59            }
60        }
61        return $overallStatus;
62    }
63
64    /**
65     * Run a single specified task (and its scheduled providers)
66     *
67     * @param string $name
68     * @return Status
69     */
70    public function runNamedTask( string $name ) {
71        $mainTask = $this->findNamedTask( $name );
72        if ( !$mainTask ) {
73            throw new \RuntimeException( "Can't find task named \"$name\"" );
74        }
75        $deps = (array)$mainTask->getDependencies();
76
77        $status = Status::newGood();
78        foreach ( $this->tasks as $subTask ) {
79            if ( array_intersect( (array)$subTask->getProvidedNames(), $deps ) ) {
80                $status->merge( $this->runTask( $subTask ) );
81            }
82        }
83        $status->merge( $this->runTask( $mainTask ) );
84
85        return $status;
86    }
87
88    /**
89     * Run the extensions provider (if it is registered) and load any extension tasks.
90     *
91     * @return Status
92     */
93    public function loadExtensions() {
94        $task = $this->findNamedTask( 'extensions' );
95        if ( $task ) {
96            $status = $this->runTask( $task );
97            if ( $status->isOK() ) {
98                $this->taskFactory->registerExtensionTasks( $this->tasks, $this->profile );
99            }
100        } else {
101            $status = Status::newGood();
102        }
103        return $status;
104    }
105
106    /**
107     * @param string $name
108     * @return Task|null
109     */
110    private function findNamedTask( string $name ): ?Task {
111        foreach ( $this->tasks as $task ) {
112            if ( $this->isSkipped( $task ) ) {
113                continue;
114            }
115
116            if ( $name === $task->getName() ) {
117                return $task;
118            }
119        }
120        return null;
121    }
122
123    /**
124     * Determine whether a task is skipped
125     *
126     * @param Task $task
127     * @return bool
128     */
129    private function isSkipped( Task $task ) {
130        return $task->isSkipped() || isset( $this->skippedTasks[$task->getName()] );
131    }
132
133    /**
134     * Determine whether a task has already completed
135     *
136     * @param Task $task
137     * @return bool
138     */
139    private function isComplete( Task $task ) {
140        return isset( $this->completedTasks[$task->getName()] );
141    }
142
143    /**
144     * Run a task and call the listeners
145     *
146     * @param Task $task
147     * @return Status
148     */
149    private function runTask( Task $task ) {
150        $this->currentTaskName = $task->getName();
151        foreach ( $this->taskStartListeners as $listener ) {
152            $listener( $task );
153        }
154
155        $status = $task->execute();
156
157        $this->completedTasks[$task->getName()] = true;
158        foreach ( $this->taskEndListeners as $listener ) {
159            $listener( $task, $status );
160        }
161        return $status;
162    }
163
164    /**
165     * Add a callback to be called before each task is executed. The callback
166     * takes one parameter: the task object.
167     *
168     * @param callable $listener
169     */
170    public function addTaskStartListener( callable $listener ) {
171        $this->taskStartListeners[] = $listener;
172    }
173
174    /**
175     * Add a callback to be called after each task completes. The callback
176     * takes two parameters: the task object and the Status returned by the
177     * task.
178     *
179     * @param callable $listener
180     */
181    public function addTaskEndListener( callable $listener ) {
182        $this->taskEndListeners[] = $listener;
183    }
184
185    /**
186     * Set a list of task names to be skipped
187     *
188     * @param string[] $taskNames
189     */
190    public function setSkippedTasks( array $taskNames ) {
191        $this->skippedTasks = array_fill_keys( $taskNames, true );
192    }
193
194    /**
195     * Get the name of the last task to start execution. This is valid during
196     * callbacks and after execute() returns.
197     *
198     * @return string|null
199     */
200    public function getCurrentTaskName(): ?string {
201        return $this->currentTaskName;
202    }
203
204    /**
205     * Provide a summary of the tasks to be executed, for debugging.
206     *
207     * @return string
208     */
209    public function dumpTaskList(): string {
210        $ret = '';
211        $i = 0;
212        foreach ( $this->tasks as $task ) {
213            $ret .= ( ++$i ) . '. ' . $task->getName();
214            if ( $task->isSkipped() ) {
215                $ret .= ' [SKIPPED]';
216            }
217            $ret .= "\n";
218        }
219        return $ret;
220    }
221}