MediaWiki master
StatusValue.php
Go to the documentation of this file.
1<?php
7use Wikimedia\Assert\Assert;
11
41class StatusValue implements Stringable {
42
47 protected $ok = true;
48
55 protected $errors = [];
56
58 public $value;
59
61 public $success = [];
62
64 public $successCount = 0;
65
67 public $failCount = 0;
68
71
75 public function __construct() {
76 }
77
86 public static function newFatal( $message, ...$parameters ): static {
87 $result = new static();
88 $result->fatal( $message, ...$parameters );
89 return $result;
90 }
91
97 public static function newGood( $value = null ): static {
98 $result = new static();
99 $result->value = $value;
100 return $result;
101 }
102
133 public static function cast( StatusValue $sv ) {
134 if ( $sv instanceof static ) {
135 return $sv;
136 }
137
138 $result = new static();
139 $result->ok = $sv->ok;
140 $result->errors = $sv->errors;
141 $result->value = $sv->value;
142 $result->successCount = $sv->successCount;
143 $result->failCount = $sv->failCount;
144 $result->success = $sv->success;
145 $result->statusData = $sv->statusData;
146
147 return $result;
148 }
149
161 public function splitByErrorType() {
162 $errorsOnlyStatusValue = static::newGood();
163 $warningsOnlyStatusValue = static::newGood();
164 $warningsOnlyStatusValue->setResult( true, $this->getValue() );
165 $errorsOnlyStatusValue->setResult( $this->isOK(), $this->getValue() );
166
167 foreach ( $this->errors as $item ) {
168 if ( $item['type'] === 'warning' ) {
169 $warningsOnlyStatusValue->errors[] = $item;
170 } else {
171 $errorsOnlyStatusValue->errors[] = $item;
172 }
173 }
174
175 return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
176 }
177
184 public function isGood() {
185 return $this->ok && !$this->errors;
186 }
187
193 public function isOK() {
194 return $this->ok;
195 }
196
200 public function getValue() {
201 return $this->value;
202 }
203
213 public function getErrors() {
214 return $this->errors;
215 }
216
223 public function setOK( $ok ) {
224 $this->ok = $ok;
225 return $this;
226 }
227
237 public function setResult( $ok, $value = null ) {
238 $this->ok = (bool)$ok;
239 $this->value = $value;
240 return $this;
241 }
242
260 private function addError( array $newError ) {
261 [ 'type' => $newType, 'message' => $newKey, 'params' => $newParams ] = $newError;
262 if ( $newKey instanceof MessageSpecifier ) {
263 Assert::parameter( $newParams === [],
264 '$parameters', "must be empty when using a MessageSpecifier" );
265 $newParams = $newKey->getParams();
266 $newKey = $newKey->getKey();
267 }
268
269 foreach ( $this->errors as [ 'type' => &$type, 'message' => $key, 'params' => $params ] ) {
270 if ( $key instanceof MessageSpecifier ) {
271 $params = $key->getParams();
272 $key = $key->getKey();
273 }
274
275 // This uses loose equality as we must support equality between MessageParam objects
276 // (e.g. ScalarParam), including when they are created separate and not by-ref equal.
277 if ( $newKey === $key && $newParams == $params ) {
278 if ( $type === 'warning' && $newType === 'error' ) {
279 $type = 'error';
280 }
281 return $this;
282 }
283 }
284
285 $this->errors[] = $newError;
286
287 return $this;
288 }
289
299 public function warning( $message, ...$parameters ) {
300 return $this->addError( [
301 'type' => 'warning',
302 'message' => $message,
303 'params' => $parameters
304 ] );
305 }
306
317 public function error( $message, ...$parameters ) {
318 return $this->addError( [
319 'type' => 'error',
320 'message' => $message,
321 'params' => $parameters
322 ] );
323 }
324
335 public function fatal( $message, ...$parameters ) {
336 $this->ok = false;
337 return $this->error( $message, ...$parameters );
338 }
339
347 public function merge( $other, $overwriteValue = false ) {
348 if ( $this->statusData !== null && $other->statusData !== null ) {
349 throw new RuntimeException( "Status cannot be merged, because they both have \$statusData" );
350 } else {
351 $this->statusData ??= $other->statusData;
352 }
353
354 foreach ( $other->errors as $error ) {
355 $this->addError( $error );
356 }
357 $this->ok = $this->ok && $other->ok;
358 if ( $overwriteValue ) {
359 $this->value = $other->value;
360 }
361 $this->successCount += $other->successCount;
362 $this->failCount += $other->failCount;
363
364 return $this;
365 }
366
379 public function getErrorsByType( $type ) {
380 $result = [];
381 foreach ( $this->errors as $error ) {
382 if ( $error['type'] === $type ) {
383 $result[] = $error;
384 }
385 }
386
387 return $result;
388 }
389
401 public function getMessages( ?string $type = null ): array {
402 Assert::parameter( $type === null || $type === 'warning' || $type === 'error',
403 '$type', "must be null, 'warning', or 'error'" );
404 $result = [];
405 foreach ( $this->errors as $error ) {
406 if ( $type === null || $error['type'] === $type ) {
407 [ 'message' => $key, 'params' => $params ] = $error;
408 if ( $key instanceof MessageSpecifier ) {
409 $result[] = $key;
410 } else {
411 $result[] = new MessageValue( $key, $params );
412 }
413 }
414 }
415
416 return $result;
417 }
418
426 public function hasMessage( string $message ) {
427 foreach ( $this->errors as [ 'message' => $key ] ) {
428 if ( ( $key instanceof MessageSpecifier && $key->getKey() === $message ) ||
429 $key === $message
430 ) {
431 return true;
432 }
433 }
434
435 return false;
436 }
437
445 public function hasMessagesExcept( string ...$messages ) {
446 foreach ( $this->errors as [ 'message' => $key ] ) {
447 if ( $key instanceof MessageSpecifier ) {
448 $key = $key->getKey();
449 }
450 if ( !in_array( $key, $messages, true ) ) {
451 return true;
452 }
453 }
454
455 return false;
456 }
457
468 public function replaceMessage( string $source, $dest ) {
469 $replaced = false;
470
471 foreach ( $this->errors as [ 'message' => &$message, 'params' => &$params ] ) {
472 if ( $message === $source ||
473 ( $message instanceof MessageSpecifier && $message->getKey() === $source )
474 ) {
475 $message = $dest;
476 if ( $dest instanceof MessageSpecifier ) {
477 // 'params' will be ignored now, so remove them from the internal array
478 $params = [];
479 }
480 $replaced = true;
481 }
482 }
483
484 return $replaced;
485 }
486
493 public function __toString() {
494 $status = $this->isOK() ? "OK" : "Error";
495 if ( count( $this->errors ) ) {
496 $errorcount = "collected " . ( count( $this->errors ) ) . " message(s) on the way";
497 } else {
498 $errorcount = "no errors detected";
499 }
500 if ( $this->value !== null ) {
501 $valstr = get_debug_type( $this->value ) . " value set";
502 } else {
503 $valstr = "no value set";
504 }
505 $out = sprintf( "<%s, %s, %s>",
506 $status,
507 $errorcount,
508 $valstr
509 );
510 if ( count( $this->errors ) > 0 ) {
511 $hdr = sprintf( "+-%'-8s-+-%'-25s-+-%'-36s-+\n", "", "", "" );
512 $out .= "\n" . $hdr;
513 foreach ( $this->errors as [ 'type' => $type, 'message' => $key, 'params' => $params ] ) {
514 if ( $key instanceof MessageSpecifier ) {
515 $params = $key->getParams();
516 $key = $key->getKey();
517 }
518
519 $keyChunks = mb_str_split( $key, 25 );
520 $paramsChunks = mb_str_split( $this->flattenParams( $params, " | " ), 36 );
521
522 // array_map(null,...) is like Python's zip()
523 foreach ( array_map( null, [ $type ], $keyChunks, $paramsChunks )
524 as [ $typeChunk, $keyChunk, $paramsChunk ]
525 ) {
526 $out .= sprintf( "| %-8s | %-25s | %-36s |\n",
527 $typeChunk,
528 $keyChunk,
529 $paramsChunk
530 );
531 }
532 }
533 $out .= $hdr;
534 }
535
536 return $out;
537 }
538
545 private function flattenParams( array $params, string $joiner = ', ' ): string {
546 $ret = [];
547 foreach ( $params as $p ) {
548 if ( is_array( $p ) ) {
549 $r = '[ ' . self::flattenParams( $p ) . ' ]';
550 } elseif ( $p instanceof MessageSpecifier ) {
551 $r = '{ ' . $p->getKey() . ': ' . self::flattenParams( $p->getParams() ) . ' }';
552 } elseif ( $p instanceof MessageParam ) {
553 $r = $p->dump();
554 } else {
555 $r = (string)$p;
556 }
557
558 $ret[] = mb_strlen( $r ) > 100 ? mb_substr( $r, 0, 99 ) . "..." : $r;
559 }
560 return implode( $joiner, $ret );
561 }
562
571 protected function getStatusArray( $type = false ) {
572 $result = [];
573
574 foreach ( $this->getErrors() as $error ) {
575 if ( !$type || $error['type'] === $type ) {
576 if ( $error['message'] instanceof MessageSpecifier ) {
577 $result[] = [ $error['message']->getKey(), ...$error['message']->getParams() ];
578 } else {
579 $result[] = [ $error['message'], ...$error['params'] ];
580 }
581 }
582 }
583
584 return $result;
585 }
586}
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.
static cast(StatusValue $sv)
Succinct helper method to wrap a StatusValue in some other specific subclass.
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 with one of the types from {.
Value object representing a message for i18n.
getKey()
Returns the message key.
$source
array $params
The job parameters.