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
114 public function splitByErrorType() {
115 $errorsOnlyStatusValue = static::newGood();
116 $warningsOnlyStatusValue = static::newGood();
117 $warningsOnlyStatusValue->setResult( true, $this->getValue() );
118 $errorsOnlyStatusValue->setResult( $this->isOK(), $this->getValue() );
119
120 foreach ( $this->errors as $item ) {
121 if ( $item['type'] === 'warning' ) {
122 $warningsOnlyStatusValue->errors[] = $item;
123 } else {
124 $errorsOnlyStatusValue->errors[] = $item;
125 }
126 }
127
128 return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
129 }
130
137 public function isGood() {
138 return $this->ok && !$this->errors;
139 }
140
146 public function isOK() {
147 return $this->ok;
148 }
149
153 public function getValue() {
154 return $this->value;
155 }
156
166 public function getErrors() {
167 return $this->errors;
168 }
169
176 public function setOK( $ok ) {
177 $this->ok = $ok;
178 return $this;
179 }
180
190 public function setResult( $ok, $value = null ) {
191 $this->ok = (bool)$ok;
192 $this->value = $value;
193 return $this;
194 }
195
213 private function addError( array $newError ) {
214 [ 'type' => $newType, 'message' => $newKey, 'params' => $newParams ] = $newError;
215 if ( $newKey instanceof MessageSpecifier ) {
216 Assert::parameter( $newParams === [],
217 '$parameters', "must be empty when using a MessageSpecifier" );
218 $newParams = $newKey->getParams();
219 $newKey = $newKey->getKey();
220 }
221
222 foreach ( $this->errors as [ 'type' => &$type, 'message' => $key, 'params' => $params ] ) {
223 if ( $key instanceof MessageSpecifier ) {
224 $params = $key->getParams();
225 $key = $key->getKey();
226 }
227
228 // This uses loose equality as we must support equality between MessageParam objects
229 // (e.g. ScalarParam), including when they are created separate and not by-ref equal.
230 if ( $newKey === $key && $newParams == $params ) {
231 if ( $type === 'warning' && $newType === 'error' ) {
232 $type = 'error';
233 }
234 return $this;
235 }
236 }
237
238 $this->errors[] = $newError;
239
240 return $this;
241 }
242
252 public function warning( $message, ...$parameters ) {
253 return $this->addError( [
254 'type' => 'warning',
255 'message' => $message,
256 'params' => $parameters
257 ] );
258 }
259
270 public function error( $message, ...$parameters ) {
271 return $this->addError( [
272 'type' => 'error',
273 'message' => $message,
274 'params' => $parameters
275 ] );
276 }
277
288 public function fatal( $message, ...$parameters ) {
289 $this->ok = false;
290 return $this->error( $message, ...$parameters );
291 }
292
300 public function merge( $other, $overwriteValue = false ) {
301 if ( $this->statusData !== null && $other->statusData !== null ) {
302 throw new RuntimeException( "Status cannot be merged, because they both have \$statusData" );
303 } else {
304 $this->statusData ??= $other->statusData;
305 }
306
307 foreach ( $other->errors as $error ) {
308 $this->addError( $error );
309 }
310 $this->ok = $this->ok && $other->ok;
311 if ( $overwriteValue ) {
312 $this->value = $other->value;
313 }
314 $this->successCount += $other->successCount;
315 $this->failCount += $other->failCount;
316
317 return $this;
318 }
319
332 public function getErrorsByType( $type ) {
333 $result = [];
334 foreach ( $this->errors as $error ) {
335 if ( $error['type'] === $type ) {
336 $result[] = $error;
337 }
338 }
339
340 return $result;
341 }
342
354 public function getMessages( ?string $type = null ): array {
355 Assert::parameter( $type === null || $type === 'warning' || $type === 'error',
356 '$type', "must be null, 'warning', or 'error'" );
357 $result = [];
358 foreach ( $this->errors as $error ) {
359 if ( $type === null || $error['type'] === $type ) {
360 [ 'message' => $key, 'params' => $params ] = $error;
361 if ( $key instanceof MessageSpecifier ) {
362 $result[] = $key;
363 } else {
364 $result[] = new MessageValue( $key, $params );
365 }
366 }
367 }
368
369 return $result;
370 }
371
379 public function hasMessage( string $message ) {
380 foreach ( $this->errors as [ 'message' => $key ] ) {
381 if ( ( $key instanceof MessageSpecifier && $key->getKey() === $message ) ||
382 $key === $message
383 ) {
384 return true;
385 }
386 }
387
388 return false;
389 }
390
398 public function hasMessagesExcept( string ...$messages ) {
399 foreach ( $this->errors as [ 'message' => $key ] ) {
400 if ( $key instanceof MessageSpecifier ) {
401 $key = $key->getKey();
402 }
403 if ( !in_array( $key, $messages, true ) ) {
404 return true;
405 }
406 }
407
408 return false;
409 }
410
421 public function replaceMessage( string $source, $dest ) {
422 $replaced = false;
423
424 foreach ( $this->errors as [ 'message' => &$message, 'params' => &$params ] ) {
425 if ( $message === $source ||
426 ( $message instanceof MessageSpecifier && $message->getKey() === $source )
427 ) {
428 $message = $dest;
429 if ( $dest instanceof MessageSpecifier ) {
430 // 'params' will be ignored now, so remove them from the internal array
431 $params = [];
432 }
433 $replaced = true;
434 }
435 }
436
437 return $replaced;
438 }
439
446 public function __toString() {
447 $status = $this->isOK() ? "OK" : "Error";
448 if ( count( $this->errors ) ) {
449 $errorcount = "collected " . ( count( $this->errors ) ) . " message(s) on the way";
450 } else {
451 $errorcount = "no errors detected";
452 }
453 if ( $this->value !== null ) {
454 $valstr = get_debug_type( $this->value ) . " value set";
455 } else {
456 $valstr = "no value set";
457 }
458 $out = sprintf( "<%s, %s, %s>",
459 $status,
460 $errorcount,
461 $valstr
462 );
463 if ( count( $this->errors ) > 0 ) {
464 $hdr = sprintf( "+-%'-8s-+-%'-25s-+-%'-36s-+\n", "", "", "" );
465 $out .= "\n" . $hdr;
466 foreach ( $this->errors as [ 'type' => $type, 'message' => $key, 'params' => $params ] ) {
467 if ( $key instanceof MessageSpecifier ) {
468 $params = $key->getParams();
469 $key = $key->getKey();
470 }
471
472 $keyChunks = mb_str_split( $key, 25 );
473 $paramsChunks = mb_str_split( $this->flattenParams( $params, " | " ), 36 );
474
475 // array_map(null,...) is like Python's zip()
476 foreach ( array_map( null, [ $type ], $keyChunks, $paramsChunks )
477 as [ $typeChunk, $keyChunk, $paramsChunk ]
478 ) {
479 $out .= sprintf( "| %-8s | %-25s | %-36s |\n",
480 $typeChunk,
481 $keyChunk,
482 $paramsChunk
483 );
484 }
485 }
486 $out .= $hdr;
487 }
488
489 return $out;
490 }
491
498 private function flattenParams( array $params, string $joiner = ', ' ): string {
499 $ret = [];
500 foreach ( $params as $p ) {
501 if ( is_array( $p ) ) {
502 $r = '[ ' . self::flattenParams( $p ) . ' ]';
503 } elseif ( $p instanceof MessageSpecifier ) {
504 $r = '{ ' . $p->getKey() . ': ' . self::flattenParams( $p->getParams() ) . ' }';
505 } elseif ( $p instanceof MessageParam ) {
506 $r = $p->dump();
507 } else {
508 $r = (string)$p;
509 }
510
511 $ret[] = mb_strlen( $r ) > 100 ? mb_substr( $r, 0, 99 ) . "..." : $r;
512 }
513 return implode( $joiner, $ret );
514 }
515
524 protected function getStatusArray( $type = false ) {
525 $result = [];
526
527 foreach ( $this->getErrors() as $error ) {
528 if ( !$type || $error['type'] === $type ) {
529 if ( $error['message'] instanceof MessageSpecifier ) {
530 $result[] = [ $error['message']->getKey(), ...$error['message']->getParams() ];
531 } else {
532 $result[] = [ $error['message'], ...$error['params'] ];
533 }
534 }
535 }
536
537 return $result;
538 }
539}
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
array $params
The job parameters.