]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/libs/StatusValue.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / libs / StatusValue.php
1 <?php
2 /**
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  * http://www.gnu.org/copyleft/gpl.html
17  *
18  * @file
19  */
20
21 /**
22  * Generic operation result class
23  * Has warning/error list, boolean status and arbitrary value
24  *
25  * "Good" means the operation was completed with no warnings or errors.
26  *
27  * "OK" means the operation was partially or wholly completed.
28  *
29  * An operation which is not OK should have errors so that the user can be
30  * informed as to what went wrong. Calling the fatal() function sets an error
31  * message and simultaneously switches off the OK flag.
32  *
33  * The recommended pattern for Status objects is to return a StatusValue
34  * unconditionally, i.e. both on success and on failure -- so that the
35  * developer of the calling code is reminded that the function can fail, and
36  * so that a lack of error-handling will be explicit.
37  *
38  * The use of Message objects should be avoided when serializability is needed.
39  *
40  * @since 1.25
41  */
42 class StatusValue {
43
44         /** @var bool */
45         protected $ok = true;
46
47         /** @var array[] */
48         protected $errors = [];
49
50         /** @var mixed */
51         public $value;
52
53         /** @var bool[] Map of (key => bool) to indicate success of each part of batch operations */
54         public $success = [];
55
56         /** @var int Counter for batch operations */
57         public $successCount = 0;
58
59         /** @var int Counter for batch operations */
60         public $failCount = 0;
61
62         /**
63          * Factory function for fatal errors
64          *
65          * @param string|MessageSpecifier $message Message key or object
66          * @return static
67          */
68         public static function newFatal( $message /*, parameters...*/ ) {
69                 $params = func_get_args();
70                 $result = new static();
71                 call_user_func_array( [ &$result, 'fatal' ], $params );
72                 return $result;
73         }
74
75         /**
76          * Factory function for good results
77          *
78          * @param mixed $value
79          * @return static
80          */
81         public static function newGood( $value = null ) {
82                 $result = new static();
83                 $result->value = $value;
84                 return $result;
85         }
86
87         /**
88          * Splits this StatusValue object into two new StatusValue objects, one which contains only
89          * the error messages, and one that contains the warnings, only. The returned array is
90          * defined as:
91          * [
92          *     0 => object(StatusValue) # the StatusValue with error messages, only
93          *     1 => object(StatusValue) # The StatusValue with warning messages, only
94          * ]
95          *
96          * @return StatusValue[]
97          */
98         public function splitByErrorType() {
99                 $errorsOnlyStatusValue = clone $this;
100                 $warningsOnlyStatusValue = clone $this;
101                 $warningsOnlyStatusValue->ok = true;
102
103                 $errorsOnlyStatusValue->errors = $warningsOnlyStatusValue->errors = [];
104                 foreach ( $this->errors as $item ) {
105                         if ( $item['type'] === 'warning' ) {
106                                 $warningsOnlyStatusValue->errors[] = $item;
107                         } else {
108                                 $errorsOnlyStatusValue->errors[] = $item;
109                         }
110                 };
111
112                 return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
113         }
114
115         /**
116          * Returns whether the operation completed and didn't have any error or
117          * warnings
118          *
119          * @return bool
120          */
121         public function isGood() {
122                 return $this->ok && !$this->errors;
123         }
124
125         /**
126          * Returns whether the operation completed
127          *
128          * @return bool
129          */
130         public function isOK() {
131                 return $this->ok;
132         }
133
134         /**
135          * @return mixed
136          */
137         public function getValue() {
138                 return $this->value;
139         }
140
141         /**
142          * Get the list of errors
143          *
144          * Each error is a (message:string or MessageSpecifier,params:array) map
145          *
146          * @return array[]
147          */
148         public function getErrors() {
149                 return $this->errors;
150         }
151
152         /**
153          * Change operation status
154          *
155          * @param bool $ok
156          */
157         public function setOK( $ok ) {
158                 $this->ok = $ok;
159         }
160
161         /**
162          * Change operation result
163          *
164          * @param bool $ok Whether the operation completed
165          * @param mixed $value
166          */
167         public function setResult( $ok, $value = null ) {
168                 $this->ok = (bool)$ok;
169                 $this->value = $value;
170         }
171
172         /**
173          * Add a new warning
174          *
175          * @param string|MessageSpecifier $message Message key or object
176          */
177         public function warning( $message /*, parameters... */ ) {
178                 $this->errors[] = [
179                         'type' => 'warning',
180                         'message' => $message,
181                         'params' => array_slice( func_get_args(), 1 )
182                 ];
183         }
184
185         /**
186          * Add an error, do not set fatal flag
187          * This can be used for non-fatal errors
188          *
189          * @param string|MessageSpecifier $message Message key or object
190          */
191         public function error( $message /*, parameters... */ ) {
192                 $this->errors[] = [
193                         'type' => 'error',
194                         'message' => $message,
195                         'params' => array_slice( func_get_args(), 1 )
196                 ];
197         }
198
199         /**
200          * Add an error and set OK to false, indicating that the operation
201          * as a whole was fatal
202          *
203          * @param string|MessageSpecifier $message Message key or object
204          */
205         public function fatal( $message /*, parameters... */ ) {
206                 $this->errors[] = [
207                         'type' => 'error',
208                         'message' => $message,
209                         'params' => array_slice( func_get_args(), 1 )
210                 ];
211                 $this->ok = false;
212         }
213
214         /**
215          * Merge another status object into this one
216          *
217          * @param StatusValue $other Other StatusValue object
218          * @param bool $overwriteValue Whether to override the "value" member
219          */
220         public function merge( $other, $overwriteValue = false ) {
221                 $this->errors = array_merge( $this->errors, $other->errors );
222                 $this->ok = $this->ok && $other->ok;
223                 if ( $overwriteValue ) {
224                         $this->value = $other->value;
225                 }
226                 $this->successCount += $other->successCount;
227                 $this->failCount += $other->failCount;
228         }
229
230         /**
231          * Returns a list of status messages of the given type
232          *
233          * Each entry is a map of:
234          *   - message: string message key or MessageSpecifier
235          *   - params: array list of parameters
236          *
237          * @param string $type
238          * @return array[]
239          */
240         public function getErrorsByType( $type ) {
241                 $result = [];
242                 foreach ( $this->errors as $error ) {
243                         if ( $error['type'] === $type ) {
244                                 $result[] = $error;
245                         }
246                 }
247
248                 return $result;
249         }
250
251         /**
252          * Returns true if the specified message is present as a warning or error
253          *
254          * @param string|MessageSpecifier $message Message key or object to search for
255          *
256          * @return bool
257          */
258         public function hasMessage( $message ) {
259                 if ( $message instanceof MessageSpecifier ) {
260                         $message = $message->getKey();
261                 }
262                 foreach ( $this->errors as $error ) {
263                         if ( $error['message'] instanceof MessageSpecifier
264                                 && $error['message']->getKey() === $message
265                         ) {
266                                 return true;
267                         } elseif ( $error['message'] === $message ) {
268                                 return true;
269                         }
270                 }
271
272                 return false;
273         }
274
275         /**
276          * If the specified source message exists, replace it with the specified
277          * destination message, but keep the same parameters as in the original error.
278          *
279          * Note, due to the lack of tools for comparing IStatusMessage objects, this
280          * function will not work when using such an object as the search parameter.
281          *
282          * @param MessageSpecifier|string $source Message key or object to search for
283          * @param MessageSpecifier|string $dest Replacement message key or object
284          * @return bool Return true if the replacement was done, false otherwise.
285          */
286         public function replaceMessage( $source, $dest ) {
287                 $replaced = false;
288
289                 foreach ( $this->errors as $index => $error ) {
290                         if ( $error['message'] === $source ) {
291                                 $this->errors[$index]['message'] = $dest;
292                                 $replaced = true;
293                         }
294                 }
295
296                 return $replaced;
297         }
298
299         /**
300          * @return string
301          */
302         public function __toString() {
303                 $status = $this->isOK() ? "OK" : "Error";
304                 if ( count( $this->errors ) ) {
305                         $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
306                 } else {
307                         $errorcount = "no errors detected";
308                 }
309                 if ( isset( $this->value ) ) {
310                         $valstr = gettype( $this->value ) . " value set";
311                         if ( is_object( $this->value ) ) {
312                                 $valstr .= "\"" . get_class( $this->value ) . "\" instance";
313                         }
314                 } else {
315                         $valstr = "no value set";
316                 }
317                 $out = sprintf( "<%s, %s, %s>",
318                         $status,
319                         $errorcount,
320                         $valstr
321                 );
322                 if ( count( $this->errors ) > 0 ) {
323                         $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
324                         $i = 1;
325                         $out .= "\n";
326                         $out .= $hdr;
327                         foreach ( $this->errors as $error ) {
328                                 if ( $error['message'] instanceof MessageSpecifier ) {
329                                         $key = $error['message']->getKey();
330                                         $params = $error['message']->getParams();
331                                 } elseif ( $error['params'] ) {
332                                         $key = $error['message'];
333                                         $params = $error['params'];
334                                 } else {
335                                         $key = $error['message'];
336                                         $params = [];
337                                 }
338
339                                 $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
340                                         $i,
341                                         $key,
342                                         implode( " ", $params )
343                                 );
344                                 $i += 1;
345                         }
346                         $out .= $hdr;
347                 }
348
349                 return $out;
350         }
351 }