MediaWiki master
StatusValue.php
Go to the documentation of this file.
1<?php
21use Wikimedia\Assert\Assert;
25
52class StatusValue implements Stringable {
53
58 protected $ok = true;
59
66 protected $errors = [];
67
69 public $value;
70
72 public $success = [];
73
75 public $successCount = 0;
76
78 public $failCount = 0;
79
82
92 public static function newFatal( $message, ...$parameters ) {
93 $result = new static();
94 $result->fatal( $message, ...$parameters );
95 return $result;
96 }
97
104 public static function newGood( $value = null ) {
105 $result = new static();
106 $result->value = $value;
107 return $result;
108 }
109
121 public function splitByErrorType() {
122 $errorsOnlyStatusValue = static::newGood();
123 $warningsOnlyStatusValue = static::newGood();
124 $warningsOnlyStatusValue->setResult( true, $this->getValue() );
125 $errorsOnlyStatusValue->setResult( $this->isOK(), $this->getValue() );
126
127 foreach ( $this->errors as $item ) {
128 if ( $item['type'] === 'warning' ) {
129 $warningsOnlyStatusValue->errors[] = $item;
130 } else {
131 $errorsOnlyStatusValue->errors[] = $item;
132 }
133 }
134
135 return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
136 }
137
144 public function isGood() {
145 return $this->ok && !$this->errors;
146 }
147
153 public function isOK() {
154 return $this->ok;
155 }
156
160 public function getValue() {
161 return $this->value;
162 }
163
173 public function getErrors() {
174 return $this->errors;
175 }
176
183 public function setOK( $ok ) {
184 $this->ok = $ok;
185 return $this;
186 }
187
195 public function setResult( $ok, $value = null ) {
196 $this->ok = (bool)$ok;
197 $this->value = $value;
198 return $this;
199 }
200
218 private function addError( array $newError ) {
219 [ 'type' => $newType, 'message' => $newKey, 'params' => $newParams ] = $newError;
220 if ( $newKey instanceof MessageSpecifier ) {
221 Assert::parameter( $newParams === [],
222 '$parameters', "must be empty when using a MessageSpecifier" );
223 $newParams = $newKey->getParams();
224 $newKey = $newKey->getKey();
225 }
226
227 foreach ( $this->errors as [ 'type' => &$type, 'message' => $key, 'params' => $params ] ) {
228 if ( $key instanceof MessageSpecifier ) {
229 $params = $key->getParams();
230 $key = $key->getKey();
231 }
232
233 // This uses loose equality as we must support equality between MessageParam objects
234 // (e.g. ScalarParam), including when they are created separate and not by-ref equal.
235 if ( $newKey === $key && $newParams == $params ) {
236 if ( $type === 'warning' && $newType === 'error' ) {
237 $type = 'error';
238 }
239 return $this;
240 }
241 }
242
243 $this->errors[] = $newError;
244
245 return $this;
246 }
247
257 public function warning( $message, ...$parameters ) {
258 return $this->addError( [
259 'type' => 'warning',
260 'message' => $message,
261 'params' => $parameters
262 ] );
263 }
264
275 public function error( $message, ...$parameters ) {
276 return $this->addError( [
277 'type' => 'error',
278 'message' => $message,
279 'params' => $parameters
280 ] );
281 }
282
293 public function fatal( $message, ...$parameters ) {
294 $this->ok = false;
295 return $this->error( $message, ...$parameters );
296 }
297
305 public function merge( $other, $overwriteValue = false ) {
306 if ( $this->statusData !== null && $other->statusData !== null ) {
307 throw new RuntimeException( "Status cannot be merged, because they both have \$statusData" );
308 } else {
309 $this->statusData ??= $other->statusData;
310 }
311
312 foreach ( $other->errors as $error ) {
313 $this->addError( $error );
314 }
315 $this->ok = $this->ok && $other->ok;
316 if ( $overwriteValue ) {
317 $this->value = $other->value;
318 }
319 $this->successCount += $other->successCount;
320 $this->failCount += $other->failCount;
321
322 return $this;
323 }
324
337 public function getErrorsByType( $type ) {
338 $result = [];
339 foreach ( $this->errors as $error ) {
340 if ( $error['type'] === $type ) {
341 $result[] = $error;
342 }
343 }
344
345 return $result;
346 }
347
359 public function getMessages( ?string $type = null ): array {
360 Assert::parameter( $type === null || $type === 'warning' || $type === 'error',
361 '$type', "must be null, 'warning', or 'error'" );
362 $result = [];
363 foreach ( $this->errors as $error ) {
364 if ( $type === null || $error['type'] === $type ) {
365 [ 'message' => $key, 'params' => $params ] = $error;
366 if ( $key instanceof MessageSpecifier ) {
367 $result[] = $key;
368 } else {
369 $result[] = new MessageValue( $key, $params );
370 }
371 }
372 }
373
374 return $result;
375 }
376
384 public function hasMessage( string $message ) {
385 foreach ( $this->errors as [ 'message' => $key ] ) {
386 if ( ( $key instanceof MessageSpecifier && $key->getKey() === $message ) ||
387 $key === $message
388 ) {
389 return true;
390 }
391 }
392
393 return false;
394 }
395
403 public function hasMessagesExcept( string ...$messages ) {
404 foreach ( $this->errors as [ 'message' => $key ] ) {
405 if ( $key instanceof MessageSpecifier ) {
406 $key = $key->getKey();
407 }
408 if ( !in_array( $key, $messages, true ) ) {
409 return true;
410 }
411 }
412
413 return false;
414 }
415
426 public function replaceMessage( string $source, $dest ) {
427 $replaced = false;
428
429 foreach ( $this->errors as [ 'message' => &$message, 'params' => &$params ] ) {
430 if ( $message === $source ||
431 ( $message instanceof MessageSpecifier && $message->getKey() === $source )
432 ) {
433 $message = $dest;
434 if ( $dest instanceof MessageSpecifier ) {
435 // 'params' will be ignored now, so remove them from the internal array
436 $params = [];
437 }
438 $replaced = true;
439 }
440 }
441
442 return $replaced;
443 }
444
451 public function __toString() {
452 $status = $this->isOK() ? "OK" : "Error";
453 if ( count( $this->errors ) ) {
454 $errorcount = "collected " . ( count( $this->errors ) ) . " message(s) on the way";
455 } else {
456 $errorcount = "no errors detected";
457 }
458 if ( isset( $this->value ) ) {
459 $valstr = get_debug_type( $this->value ) . " value set";
460 } else {
461 $valstr = "no value set";
462 }
463 $out = sprintf( "<%s, %s, %s>",
464 $status,
465 $errorcount,
466 $valstr
467 );
468 if ( count( $this->errors ) > 0 ) {
469 $hdr = sprintf( "+-%'-8s-+-%'-25s-+-%'-36s-+\n", "", "", "" );
470 $out .= "\n" . $hdr;
471 foreach ( $this->errors as [ 'type' => $type, 'message' => $key, 'params' => $params ] ) {
472 if ( $key instanceof MessageSpecifier ) {
473 $params = $key->getParams();
474 $key = $key->getKey();
475 }
476
477 $keyChunks = mb_str_split( $key, 25 );
478 $paramsChunks = mb_str_split( $this->flattenParams( $params, " | " ), 36 );
479
480 // array_map(null,...) is like Python's zip()
481 foreach ( array_map( null, [ $type ], $keyChunks, $paramsChunks )
482 as [ $typeChunk, $keyChunk, $paramsChunk ]
483 ) {
484 $out .= sprintf( "| %-8s | %-25s | %-36s |\n",
485 $typeChunk,
486 $keyChunk,
487 $paramsChunk
488 );
489 }
490 }
491 $out .= $hdr;
492 }
493
494 return $out;
495 }
496
503 private function flattenParams( array $params, string $joiner = ', ' ): string {
504 $ret = [];
505 foreach ( $params as $p ) {
506 if ( is_array( $p ) ) {
507 $r = '[ ' . self::flattenParams( $p ) . ' ]';
508 } elseif ( $p instanceof MessageSpecifier ) {
509 $r = '{ ' . $p->getKey() . ': ' . self::flattenParams( $p->getParams() ) . ' }';
510 } elseif ( $p instanceof MessageParam ) {
511 $r = $p->dump();
512 } else {
513 $r = (string)$p;
514 }
515
516 $ret[] = mb_strlen( $r ) > 100 ? mb_substr( $r, 0, 99 ) . "..." : $r;
517 }
518 return implode( $joiner, $ret );
519 }
520
529 protected function getStatusArray( $type = false ) {
530 $result = [];
531
532 foreach ( $this->getErrors() as $error ) {
533 if ( !$type || $error['type'] === $type ) {
534 if ( $error['message'] instanceof MessageSpecifier ) {
535 $result[] = [ $error['message']->getKey(), ...$error['message']->getParams() ];
536 } else {
537 $result[] = [ $error['message'], ...$error['params'] ];
538 }
539 }
540 }
541
542 return $result;
543 }
544}
array $params
The job parameters.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
hasMessagesExcept(string ... $messages)
Returns true if any other message than the specified ones is present as a warning or error.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
int $failCount
Counter for batch operations.
array[] $errors
getErrors()
Get the list of errors.
getMessages(?string $type=null)
Returns a list of error messages, optionally only those of the given type.
splitByErrorType()
Splits this StatusValue object into two new StatusValue objects, one which contains only the error me...
setOK( $ok)
Change operation status.
hasMessage(string $message)
Returns true if the specified message is present as a warning or error.
getStatusArray( $type=false)
Returns a list of status messages of the given type (or all if false)
replaceMessage(string $source, $dest)
If the specified source message exists, replace it with the specified destination message,...
isOK()
Returns whether the operation completed.
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
setResult( $ok, $value=null)
Change operation result.
merge( $other, $overwriteValue=false)
Merge another status object into this one.
mixed $statusData
arbitrary extra data about the operation
__toString()
Returns a string representation of the status for debugging.
error( $message,... $parameters)
Add an error, do not set fatal flag This can be used for non-fatal errors.
getErrorsByType( $type)
Returns a list of status messages of the given type.
warning( $message,... $parameters)
Add a new warning.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
static newGood( $value=null)
Factory function for good results.
int $successCount
Counter for batch operations.
Value object representing a message parameter that consists of a list of values.
Value object representing a message for i18n.
getKey()
Returns the message key.
$source