Merge "Rewrite pref cleanup script"
[lhc/web/wiklou.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
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 }