10use InvalidArgumentException;
15use UnexpectedValueException;
20use Wikimedia\Timestamp\TimestampFormat as TS;
162 $this->maxSize = $maxSize;
171 $this->errorFormatter = $formatter;
192 self::META_TYPE =>
'assoc',
256 $last = array_pop(
$path );
257 $ret = &$this->path(
$path,
'dummy' );
258 if ( !isset( $ret[$last] ) ) {
260 } elseif ( is_array( $ret[$last] ) ) {
287 public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
288 if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
289 $value = self::validateValue( $value );
292 if ( $name ===
null ) {
293 if ( $flags & self::ADD_ON_TOP ) {
294 array_unshift( $arr, $value );
301 $exists = isset( $arr[$name] );
302 if ( !$exists || ( $flags & self::OVERRIDE ) ) {
303 if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
304 $arr = [ $name => $value ] + $arr;
306 $arr[$name] = $value;
308 } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
309 $conflicts = array_intersect_key( $arr[$name], $value );
310 if ( !$conflicts || ( $flags & self::IGNORE_CONFLICT_KEYS ) ) {
311 $arr[$name] += $value;
313 $keys = implode(
', ', array_keys( $conflicts ) );
314 throw new RuntimeException(
315 "Conflicting keys ($keys) when attempting to merge element $name"
318 } elseif ( $value !== $arr[$name] ) {
319 throw new RuntimeException(
320 "Attempting to add element $name=$value, existing value is {$arr[$name]}"
330 private static function validateValue( $value ) {
331 if ( is_object( $value ) ) {
334 if ( is_callable( [ $value,
'serializeForApiResult' ] ) ) {
336 $value = $value->serializeForApiResult();
337 if ( is_object( $value ) ) {
338 throw new UnexpectedValueException(
339 get_class( $oldValue ) .
'::serializeForApiResult() returned an object of class ' .
347 return self::validateValue( $value );
348 }
catch ( Exception $ex ) {
349 throw new UnexpectedValueException(
350 get_class( $oldValue ) .
'::serializeForApiResult() returned an invalid value: ' .
356 } elseif ( $value instanceof ScalarParam || $value instanceof ListParam ) {
359 $value = $value->getType() === ParamType::TEXT ? $value->getValue() : $value->toJsonArray();
360 if ( $value instanceof MessageValue ) {
361 $value = $value->toJsonArray();
363 } elseif ( $value instanceof MessageValue ) {
366 $value = $value->toJsonArray();
367 } elseif ( is_callable( [ $value,
'__toString' ] ) ) {
368 $value = (string)$value;
370 $value = (array)$value + [ self::META_TYPE =>
'assoc' ];
374 if ( is_string( $value ) ) {
376 static $contentLanguage =
null;
377 if ( !$contentLanguage ) {
380 $value = $contentLanguage->normalize( $value );
381 } elseif ( is_array( $value ) ) {
382 foreach ( $value as $k => $v ) {
383 $value[$k] = self::validateValue( $v );
385 } elseif ( $value !==
null && !is_scalar( $value ) ) {
386 $type = get_debug_type( $value );
387 throw new InvalidArgumentException(
"Cannot add $type to ApiResult" );
388 } elseif ( is_float( $value ) && !is_finite( $value ) ) {
389 throw new InvalidArgumentException(
'Cannot add non-finite floats to ApiResult' );
412 $arr = &$this->path(
$path, ( $flags & self::ADD_ON_TOP ) ?
'prepend' :
'append' );
414 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
417 $value = self::validateValue( $value );
420 $newsize = $this->size + self::size( $value );
421 if ( $this->maxSize !==
false && $newsize > $this->maxSize ) {
422 $this->errorFormatter->addWarning(
427 $this->size = $newsize;
442 if ( isset( $arr[$name] ) ) {
444 unset( $arr[$name] );
461 if ( $name ===
null ) {
463 throw new InvalidArgumentException(
'Cannot remove the data root' );
465 $name = array_pop(
$path );
468 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
469 $newsize = $this->size - self::size( $ret );
470 $this->size = max( $newsize, 0 );
485 if ( $name ===
null ) {
486 throw new InvalidArgumentException(
'Content value must be named' );
503 if ( $name ===
null ) {
504 throw new InvalidArgumentException(
'Content value must be named' );
519 $this->
addValue(
'limits', $moduleName, $limit,
520 self::OVERRIDE | self::NO_SIZE_CHECK );
538 if ( isset( $arr[self::META_CONTENT] ) &&
539 isset( $arr[$arr[self::META_CONTENT]] ) &&
540 !( $flags & self::OVERRIDE )
542 throw new RuntimeException(
543 "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
544 ' is already set as the content element'
559 $arr = &$this->path(
$path, ( $flags & self::ADD_ON_TOP ) ?
'prepend' :
'append' );
571 if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
586 $arr = &$this->path(
$path );
598 if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
611 $arr = &$this->path(
$path );
622 if ( !is_string( $tag ) ) {
623 throw new InvalidArgumentException(
'Bad tag name' );
635 $arr = &$this->path(
$path );
647 if ( !is_string( $tag ) ) {
648 throw new InvalidArgumentException(
'Bad tag name' );
651 foreach ( $arr as $k => &$v ) {
652 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
666 $arr = &$this->path(
$path );
681 if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
696 $arr = &$this->path(
$path );
708 if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
721 $arr = &$this->path(
$path );
733 public static function setArrayType( array &$arr, $type, $kvpKeyName =
null ) {
734 if ( !in_array( $type, [
735 'default',
'array',
'assoc',
'kvp',
'BCarray',
'BCassoc',
'BCkvp'
737 throw new InvalidArgumentException(
'Bad type' );
740 if ( is_string( $kvpKeyName ) ) {
753 $arr = &$this->path(
$path );
766 foreach ( $arr as $k => &$v ) {
767 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
781 $arr = &$this->path(
$path );
798 return str_starts_with( $key,
'_' );
811 $strip = $transforms[
'Strip'] ??
'none';
812 if ( $strip ===
'base' ) {
813 $transforms[
'Strip'] =
'none';
815 $transformTypes = $transforms[
'Types'] ??
null;
816 if ( $transformTypes !==
null && !is_array( $transformTypes ) ) {
817 throw new InvalidArgumentException( __METHOD__ .
':Value for "Types" must be an array' );
823 if ( isset( $transforms[
'Custom'] ) ) {
824 if ( !is_callable( $transforms[
'Custom'] ) ) {
825 throw new InvalidArgumentException( __METHOD__ .
': Value for "Custom" must be callable' );
827 $transforms[
'Custom']( $data, $metadata );
830 if ( ( isset( $transforms[
'BC'] ) || $transformTypes !==
null ) &&
831 isset( $metadata[self::META_TYPE] ) && $metadata[
self::META_TYPE] ===
'BCkvp' &&
832 !isset( $metadata[self::META_KVP_KEY_NAME] )
834 throw new UnexpectedValueException(
'Type "BCkvp" used without setting ' .
835 'ApiResult::META_KVP_KEY_NAME metadata item' );
840 if ( isset( $transforms[
'BC'] ) ) {
841 if ( !is_array( $transforms[
'BC'] ) ) {
842 throw new InvalidArgumentException( __METHOD__ .
':Value for "BC" must be an array' );
844 if ( !in_array(
'nobool', $transforms[
'BC'],
true ) ) {
845 $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
846 ? array_fill_keys( $metadata[self::META_BC_BOOLS],
true )
850 if ( !in_array(
'no*', $transforms[
'BC'],
true ) &&
851 isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !==
'*'
854 $data[
'*'] = $data[$k];
859 if ( !in_array(
'nosub', $transforms[
'BC'],
true ) &&
860 isset( $metadata[self::META_BC_SUBELEMENTS] )
862 foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
863 if ( isset( $data[$k] ) ) {
866 self::META_CONTENT =>
'*',
867 self::META_TYPE =>
'assoc',
873 if ( isset( $metadata[self::META_TYPE] ) ) {
874 switch ( $metadata[self::META_TYPE] ) {
887 $defaultType =
'array';
889 foreach ( $data as $k => &$v ) {
891 if ( $boolKeys !==
null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
898 if ( is_string( $k ) ) {
899 $defaultType =
'assoc';
900 } elseif ( $k > $maxKey ) {
913 $keepMetadata = &$metadata;
917 $keepMetadata = array_intersect_key( $metadata, [
918 self::META_INDEXED_TAG_NAME => 1,
919 self::META_SUBELEMENTS => 1,
923 throw new InvalidArgumentException( __METHOD__ .
': Unknown value for "Strip"' );
927 if ( $transformTypes ===
null ) {
928 return $data + $keepMetadata;
931 if ( $defaultType ===
'array' && $maxKey !== count( $data ) - 1 ) {
932 $defaultType =
'assoc';
936 $type = $defaultType;
937 if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !==
'default' ) {
940 if ( ( $type ===
'kvp' || $type ===
'BCkvp' ) &&
941 empty( $transformTypes[
'ArmorKVP'] )
944 } elseif ( $type ===
'BCarray' ) {
946 } elseif ( $type ===
'BCassoc' ) {
954 $data += $keepMetadata;
955 return empty( $transformTypes[
'AssocAsObject'] ) ? $data : (object)$data;
967 uksort( $data,
static function ( $a, $b ):
int {
970 if ( is_numeric( $a ) xor is_numeric( $b ) ) {
971 return (
string)$a <=> (
string)$b;
977 $data = array_values( $data );
979 return $data + $keepMetadata;
984 $valKey = isset( $transforms[
'BC'] ) ?
'*' :
'value';
985 $assocAsObject = !empty( $transformTypes[
'AssocAsObject'] );
986 $merge = !empty( $metadata[self::META_KVP_MERGE] );
989 foreach ( $data as $k => $v ) {
990 if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
992 if ( isset( $vArr[self::META_TYPE] ) ) {
994 } elseif ( is_object( $v ) ) {
995 $mergeType =
'assoc';
997 $keys = array_keys( $vArr );
998 sort( $keys, SORT_NUMERIC );
999 $mergeType = ( $keys === array_keys( $keys ) ) ?
'array' :
'assoc';
1004 if ( $mergeType ===
'assoc' ) {
1009 if ( $strip ===
'none' ) {
1017 if ( $strip ===
'none' ) {
1019 self::META_PRESERVE_KEYS => [ $key ],
1020 self::META_CONTENT => $valKey,
1021 self::META_TYPE =>
'assoc',
1025 $ret[] = $assocAsObject ? (object)$item : $item;
1029 return $ret + $keepMetadata;
1032 throw new UnexpectedValueException(
"Unknown type '$type'" );
1047 if ( is_array( $data ) || is_object( $data ) ) {
1048 $isObj = is_object( $data );
1050 $data = (array)$data;
1052 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1053 ? (array)$data[self::META_PRESERVE_KEYS]
1055 foreach ( $data as $k => $v ) {
1056 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys,
true ) ) {
1058 } elseif ( is_array( $v ) || is_object( $v ) ) {
1063 $data = (object)$data;
1081 if ( !is_array( $metadata ) ) {
1084 if ( is_array( $data ) || is_object( $data ) ) {
1085 $isObj = is_object( $data );
1087 $data = (array)$data;
1089 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1090 ? (array)$data[self::META_PRESERVE_KEYS]
1092 foreach ( $data as $k => $v ) {
1093 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys,
true ) ) {
1099 $data = (object)$data;
1111 private static function size( $value ) {
1113 if ( is_array( $value ) ) {
1114 foreach ( $value as $k => $v ) {
1115 if ( !self::isMetadataKey( $k ) ) {
1116 $s += self::size( $v );
1119 } elseif ( is_scalar( $value ) ) {
1120 $s = strlen( $value );
1137 private function &path(
$path, $create =
'append' ) {
1139 $ret = &$this->data;
1140 foreach (
$path as $i => $k ) {
1141 if ( !isset( $ret[$k] ) ) {
1142 switch ( $create ) {
1147 $ret = [ $k => [] ] + $ret;
1153 $fail = implode(
'.', array_slice(
$path, 0, $i + 1 ) );
1154 throw new InvalidArgumentException(
"Path $fail does not exist" );
1157 if ( !is_array( $ret[$k] ) ) {
1158 $fail = implode(
'.', array_slice(
$path, 0, $i + 1 ) );
1159 throw new InvalidArgumentException(
"Path $fail is not an array" );
1179 foreach ( $vars as $k => $v ) {
1180 if ( is_array( $v ) || is_object( $v ) ) {
1182 } elseif ( is_bool( $v ) ) {
1186 if ( is_string( $k ) ) {
1188 } elseif ( $k > $maxKey ) {
1192 if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1200 $keys = array_diff( array_keys( $vars ), [
1201 self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1202 self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1206 self::META_TYPE =>
'kvp',
1207 self::META_KVP_KEY_NAME =>
'key',
1208 self::META_PRESERVE_KEYS => $keys,
1209 self::META_BC_BOOLS => $bools,
1210 self::META_INDEXED_TAG_NAME =>
'var',
1214 self::META_TYPE =>
'array',
1215 self::META_BC_BOOLS => $bools,
1216 self::META_INDEXED_TAG_NAME =>
'value',
1232 ->getReplicaDatabase()
1235 if ( $expiry ===
'' || $expiry ===
null || $expiry ===
false ||
1258class_alias( ApiResult::class,
'ApiResult' );
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfIsInfinity( $str)
Determine input string is represents as infinity.