Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
93.24% |
69 / 74 |
|
90.32% |
28 / 31 |
CRAP | |
0.00% |
0 / 1 |
| MessageValue | |
93.24% |
69 / 74 |
|
90.32% |
28 / 31 |
45.62 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| new | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| newFromSpecifier | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| params | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| textParamsOfType | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
3.33 | |||
| listParamsOfType | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
3.33 | |||
| textParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| numParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| longDurationParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| shortDurationParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| expiryParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| dateTimeParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| dateParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| timeParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| userGroupParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| sizeParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| bitrateParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| rawParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| plaintextParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| commaListParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| semicolonListParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| pipeListParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| textListParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| dump | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| isSameAs | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| toJsonArray | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
| jsonClassHintFor | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| newFromJsonArray | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
| hint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Wikimedia\Message; |
| 4 | |
| 5 | use Wikimedia\Assert\Assert; |
| 6 | use Wikimedia\JsonCodec\Hint; |
| 7 | use Wikimedia\JsonCodec\JsonCodecable; |
| 8 | use Wikimedia\JsonCodec\JsonCodecableTrait; |
| 9 | |
| 10 | /** |
| 11 | * Value object representing a message for i18n. |
| 12 | * |
| 13 | * A MessageValue holds a key and an array of parameters. It can be converted |
| 14 | * to a string in a particular language using formatters obtained from an |
| 15 | * IMessageFormatterFactory. |
| 16 | * |
| 17 | * MessageValues are pure value objects and are newable and (de)serializable. |
| 18 | * |
| 19 | * @newable |
| 20 | */ |
| 21 | class MessageValue implements MessageSpecifier, JsonCodecable { |
| 22 | use JsonCodecableTrait; |
| 23 | |
| 24 | private readonly string $key; |
| 25 | |
| 26 | /** @var list<MessageParam> */ |
| 27 | private array $params = []; |
| 28 | |
| 29 | /** |
| 30 | * @stable to call |
| 31 | * |
| 32 | * @param string $key |
| 33 | * @param (MessageParam|MessageSpecifier|string|int|float)[] $params Values that are not instances |
| 34 | * of MessageParam are wrapped using ParamType::TEXT. |
| 35 | */ |
| 36 | public function __construct( string $key, array $params = [] ) { |
| 37 | $this->key = $key; |
| 38 | $this->params( ...$params ); |
| 39 | Assert::invariant( array_is_list( $this->params ), "should be list" ); |
| 40 | } |
| 41 | |
| 42 | /** |
| 43 | * Static constructor for easier chaining of `->params()` methods |
| 44 | * @param string $key |
| 45 | * @param (MessageParam|MessageSpecifier|string|int|float)[] $params |
| 46 | * @return MessageValue |
| 47 | */ |
| 48 | public static function new( string $key, array $params = [] ): MessageValue { |
| 49 | return new MessageValue( $key, $params ); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Convert from any MessageSpecifier to a MessageValue. |
| 54 | * |
| 55 | * When the given object is an instance of MessageValue, the same object is returned. |
| 56 | * |
| 57 | * @since 1.43 |
| 58 | * @param MessageSpecifier $spec |
| 59 | * @return MessageValue |
| 60 | */ |
| 61 | public static function newFromSpecifier( MessageSpecifier $spec ): MessageValue { |
| 62 | if ( $spec instanceof MessageValue ) { |
| 63 | return $spec; |
| 64 | } |
| 65 | return new MessageValue( $spec->getKey(), $spec->getParams() ); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Get the message key |
| 70 | */ |
| 71 | public function getKey(): string { |
| 72 | return $this->key; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Get the parameter array |
| 77 | * |
| 78 | * @return MessageParam[] |
| 79 | */ |
| 80 | public function getParams(): array { |
| 81 | return $this->params; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Chainable mutator which adds text parameters and MessageParam parameters |
| 86 | * |
| 87 | * @param MessageParam|MessageSpecifier|string|int|float ...$values |
| 88 | * @return $this |
| 89 | */ |
| 90 | public function params( ...$values ): MessageValue { |
| 91 | foreach ( $values as $value ) { |
| 92 | if ( $value instanceof MessageParam ) { |
| 93 | $this->params[] = $value; |
| 94 | } else { |
| 95 | $this->params[] = new ScalarParam( ParamType::TEXT, $value ); |
| 96 | } |
| 97 | } |
| 98 | return $this; |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Chainable mutator which adds text parameters with a common type |
| 103 | * |
| 104 | * @param string|ParamType $type One of the ParamType constants |
| 105 | * @param MessageSpecifier|string|int|float ...$values Scalar values |
| 106 | * @return $this |
| 107 | */ |
| 108 | public function textParamsOfType( string|ParamType $type, ...$values ): MessageValue { |
| 109 | if ( is_string( $type ) ) { |
| 110 | wfDeprecated( __METHOD__ . ' with string type', '1.45' ); |
| 111 | $type = ParamType::from( $type ); |
| 112 | } |
| 113 | foreach ( $values as $value ) { |
| 114 | $this->params[] = new ScalarParam( $type, $value ); |
| 115 | } |
| 116 | return $this; |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Chainable mutator which adds list parameters with a common type |
| 121 | * |
| 122 | * @param string|ListType $listType One of the ListType constants |
| 123 | * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value |
| 124 | * is an array of items suitable to pass as $params to ListParam::__construct() |
| 125 | * @return $this |
| 126 | */ |
| 127 | public function listParamsOfType( string|ListType $listType, ...$values ): MessageValue { |
| 128 | if ( is_string( $listType ) ) { |
| 129 | wfDeprecated( __METHOD__ . ' with string listType', '1.45' ); |
| 130 | $listType = ListType::from( $listType ); |
| 131 | } |
| 132 | foreach ( $values as $value ) { |
| 133 | $this->params[] = new ListParam( $listType, $value ); |
| 134 | } |
| 135 | return $this; |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Chainable mutator which adds parameters of type text (ParamType::TEXT). |
| 140 | * |
| 141 | * @param MessageSpecifier|string|int|float ...$values |
| 142 | * @return $this |
| 143 | */ |
| 144 | public function textParams( ...$values ): MessageValue { |
| 145 | return $this->textParamsOfType( ParamType::TEXT, ...$values ); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Chainable mutator which adds numeric parameters (ParamType::NUM). |
| 150 | * |
| 151 | * @param int|float ...$values |
| 152 | * @return $this |
| 153 | */ |
| 154 | public function numParams( ...$values ): MessageValue { |
| 155 | return $this->textParamsOfType( ParamType::NUM, ...$values ); |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Chainable mutator which adds parameters which are a duration specified |
| 160 | * in seconds (ParamType::DURATION_LONG). |
| 161 | * |
| 162 | * This is similar to shorDurationParams() except that the result will be |
| 163 | * more verbose. |
| 164 | * |
| 165 | * @param int|float ...$values |
| 166 | * @return $this |
| 167 | */ |
| 168 | public function longDurationParams( ...$values ): MessageValue { |
| 169 | return $this->textParamsOfType( ParamType::DURATION_LONG, ...$values ); |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Chainable mutator which adds parameters which are a duration specified |
| 174 | * in seconds (ParamType::DURATION_SHORT). |
| 175 | * |
| 176 | * This is similar to longDurationParams() except that the result will be more |
| 177 | * compact. |
| 178 | * |
| 179 | * @param int|float ...$values |
| 180 | * @return $this |
| 181 | */ |
| 182 | public function shortDurationParams( ...$values ): MessageValue { |
| 183 | return $this->textParamsOfType( ParamType::DURATION_SHORT, ...$values ); |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Chainable mutator which adds parameters which are an expiry timestamp (ParamType::EXPIRY). |
| 188 | * |
| 189 | * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library, |
| 190 | * or "infinity" |
| 191 | * @return $this |
| 192 | */ |
| 193 | public function expiryParams( ...$values ): MessageValue { |
| 194 | return $this->textParamsOfType( ParamType::EXPIRY, ...$values ); |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Chainable mutator which adds parameters which are a date-time timestamp (ParamType::DATETIME). |
| 199 | * |
| 200 | * @since 1.36 |
| 201 | * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library. |
| 202 | * @return $this |
| 203 | */ |
| 204 | public function dateTimeParams( ...$values ): MessageValue { |
| 205 | return $this->textParamsOfType( ParamType::DATETIME, ...$values ); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Chainable mutator which adds parameters which are a date timestamp (ParamType::DATE). |
| 210 | * |
| 211 | * @since 1.36 |
| 212 | * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library. |
| 213 | * @return $this |
| 214 | */ |
| 215 | public function dateParams( ...$values ): MessageValue { |
| 216 | return $this->textParamsOfType( ParamType::DATE, ...$values ); |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Chainable mutator which adds parameters which are a time timestamp (ParamType::TIME). |
| 221 | * |
| 222 | * @since 1.36 |
| 223 | * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library. |
| 224 | * @return $this |
| 225 | */ |
| 226 | public function timeParams( ...$values ): MessageValue { |
| 227 | return $this->textParamsOfType( ParamType::TIME, ...$values ); |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Chainable mutator which adds parameters which are a user group (ParamType::GROUP). |
| 232 | * |
| 233 | * @since 1.38 |
| 234 | * @param string ...$values User Groups |
| 235 | * @return $this |
| 236 | */ |
| 237 | public function userGroupParams( ...$values ): MessageValue { |
| 238 | return $this->textParamsOfType( ParamType::GROUP, ...$values ); |
| 239 | } |
| 240 | |
| 241 | /** |
| 242 | * Chainable mutator which adds parameters which are a number of bytes (ParamType::SIZE). |
| 243 | * |
| 244 | * @param int ...$values |
| 245 | * @return $this |
| 246 | */ |
| 247 | public function sizeParams( ...$values ): MessageValue { |
| 248 | return $this->textParamsOfType( ParamType::SIZE, ...$values ); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Chainable mutator which adds parameters which are a number of bits per |
| 253 | * second (ParamType::BITRATE). |
| 254 | * |
| 255 | * @param int|float ...$values |
| 256 | * @return $this |
| 257 | */ |
| 258 | public function bitrateParams( ...$values ): MessageValue { |
| 259 | return $this->textParamsOfType( ParamType::BITRATE, ...$values ); |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Chainable mutator which adds "raw" parameters (ParamType::RAW). |
| 264 | * |
| 265 | * Raw parameters are substituted after formatter processing. The caller is responsible |
| 266 | * for ensuring that the value will be safe for the intended output format, and |
| 267 | * documenting what that intended output format is. |
| 268 | * |
| 269 | * @param string ...$values |
| 270 | * @return $this |
| 271 | */ |
| 272 | public function rawParams( ...$values ): MessageValue { |
| 273 | return $this->textParamsOfType( ParamType::RAW, ...$values ); |
| 274 | } |
| 275 | |
| 276 | /** |
| 277 | * Chainable mutator which adds plaintext parameters (ParamType::PLAINTEXT). |
| 278 | * |
| 279 | * Plaintext parameters are substituted after formatter processing. The value |
| 280 | * will be escaped by the formatter as appropriate for the target output format |
| 281 | * so as to be represented as plain text rather than as any sort of markup. |
| 282 | * |
| 283 | * @param string ...$values |
| 284 | * @return $this |
| 285 | */ |
| 286 | public function plaintextParams( ...$values ): MessageValue { |
| 287 | return $this->textParamsOfType( ParamType::PLAINTEXT, ...$values ); |
| 288 | } |
| 289 | |
| 290 | /** |
| 291 | * Chainable mutator which adds comma lists (ListType::COMMA). |
| 292 | * |
| 293 | * The list parameters thus created are formatted as a comma-separated list, |
| 294 | * or some local equivalent. |
| 295 | * |
| 296 | * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value |
| 297 | * is an array of items suitable to pass as $params to ListParam::__construct() |
| 298 | * @return $this |
| 299 | */ |
| 300 | public function commaListParams( ...$values ): MessageValue { |
| 301 | return $this->listParamsOfType( ListType::COMMA, ...$values ); |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * Chainable mutator which adds semicolon lists (ListType::SEMICOLON). |
| 306 | * |
| 307 | * The list parameters thus created are formatted as a semicolon-separated |
| 308 | * list, or some local equivalent. |
| 309 | * |
| 310 | * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value |
| 311 | * is an array of items suitable to pass as $params to ListParam::__construct() |
| 312 | * @return $this |
| 313 | */ |
| 314 | public function semicolonListParams( ...$values ): MessageValue { |
| 315 | return $this->listParamsOfType( ListType::SEMICOLON, ...$values ); |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Chainable mutator which adds pipe lists (ListType::PIPE). |
| 320 | * |
| 321 | * The list parameters thus created are formatted as a pipe ("|") -separated |
| 322 | * list, or some local equivalent. |
| 323 | * |
| 324 | * @param (MessageParam|MessageSpecifier|string|int|float)[] ...$values Each value |
| 325 | * is an array of items suitable to pass as $params to ListParam::__construct() |
| 326 | * @return $this |
| 327 | */ |
| 328 | public function pipeListParams( ...$values ): MessageValue { |
| 329 | return $this->listParamsOfType( ListType::PIPE, ...$values ); |
| 330 | } |
| 331 | |
| 332 | /** |
| 333 | * Chainable mutator which adds natural-language lists (ListType::AND). |
| 334 | * |
| 335 | * The list parameters thus created, when formatted, are joined as in natural |
| 336 | * language. In English, this means a comma-separated list, with the last |
| 337 | * two elements joined with "and". |
| 338 | * |
| 339 | * @param (MessageParam|string)[] ...$values |
| 340 | * @return $this |
| 341 | */ |
| 342 | public function textListParams( ...$values ): MessageValue { |
| 343 | return $this->listParamsOfType( ListType::AND, ...$values ); |
| 344 | } |
| 345 | |
| 346 | /** |
| 347 | * Dump the object for testing/debugging |
| 348 | * |
| 349 | * @return string |
| 350 | */ |
| 351 | public function dump(): string { |
| 352 | $contents = ''; |
| 353 | foreach ( $this->params as $param ) { |
| 354 | $contents .= $param->dump(); |
| 355 | } |
| 356 | return '<message key="' . htmlspecialchars( $this->key ) . '">' . |
| 357 | $contents . '</message>'; |
| 358 | } |
| 359 | |
| 360 | public function isSameAs( MessageValue $mv ): bool { |
| 361 | return $this->key === $mv->key && |
| 362 | count( $this->params ) === count( $mv->params ) && |
| 363 | array_all( |
| 364 | $this->params, |
| 365 | static fn ( $v, $k ) => $v->isSameAs( $mv->params[$k] ) |
| 366 | ); |
| 367 | } |
| 368 | |
| 369 | public function toJsonArray(): array { |
| 370 | // WARNING: When changing how this class is serialized, follow the instructions |
| 371 | // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>! |
| 372 | return [ |
| 373 | 'key' => $this->key, |
| 374 | 'params' => array_map( |
| 375 | /** |
| 376 | * Serialize trivial parameters as scalar values to minimize the footprint. Full |
| 377 | * round-trip compatibility is guaranteed via the constructor and {@see params}. |
| 378 | */ |
| 379 | static fn ( $p ) => ( |
| 380 | $p->getType() === ParamType::TEXT && |
| 381 | is_scalar( $p->getValue() ) |
| 382 | ) ? $p->getValue() : $p, |
| 383 | $this->params |
| 384 | ), |
| 385 | ]; |
| 386 | } |
| 387 | |
| 388 | /** @inheritDoc */ |
| 389 | public static function jsonClassHintFor( string $keyName ) { |
| 390 | // Reduce serialization overhead by eliminating the type information |
| 391 | // when 'params' consists of MessageParam instances |
| 392 | if ( $keyName === 'params' ) { |
| 393 | return Hint::build( |
| 394 | MessageParam::class, Hint::INHERITED, |
| 395 | Hint::LIST, Hint::USE_SQUARE |
| 396 | ); |
| 397 | } |
| 398 | return null; |
| 399 | } |
| 400 | |
| 401 | public static function newFromJsonArray( array $json ): MessageValue { |
| 402 | // WARNING: When changing how this class is serialized, follow the instructions |
| 403 | // at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>! |
| 404 | // Support use of [MessageValue::class, Hint::INHERITED] for |
| 405 | // DataMessageValue as well: |
| 406 | if ( isset( $json['code'] ) ) { |
| 407 | return DataMessageValue::newFromJsonArray( $json ); |
| 408 | } |
| 409 | return new self( $json['key'], $json['params'] ); |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * If you are serializing a MessageValue (or a DataMessageValue), use |
| 414 | * this JsonCodec hint to suppress unnecessary type information. |
| 415 | */ |
| 416 | public static function hint(): Hint { |
| 417 | return Hint::build( self::class, Hint::INHERITED ); |
| 418 | } |
| 419 | } |