161 $this->errorFormatter = $formatter;
182 self::META_TYPE =>
'assoc',
246 $last = array_pop(
$path );
248 if ( !isset( $ret[$last] ) ) {
250 } elseif ( is_array( $ret[$last] ) ) {
277 public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
278 if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
282 if ( $name ===
null ) {
283 if ( $flags & self::ADD_ON_TOP ) {
284 array_unshift( $arr, $value );
286 array_push( $arr, $value );
291 $exists = isset( $arr[$name] );
292 if ( !$exists || ( $flags & self::OVERRIDE ) ) {
293 if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
294 $arr = [ $name => $value ] + $arr;
296 $arr[$name] = $value;
298 } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
299 $conflicts = array_intersect_key( $arr[$name], $value );
301 $arr[$name] += $value;
303 $keys = implode(
', ', array_keys( $conflicts ) );
304 throw new RuntimeException(
305 "Conflicting keys ($keys) when attempting to merge element $name"
309 throw new RuntimeException(
310 "Attempting to add element $name=$value, existing value is {$arr[$name]}"
321 if ( is_object( $value ) ) {
324 if ( is_callable( [ $value,
'serializeForApiResult' ] ) ) {
326 $value = $value->serializeForApiResult();
327 if ( is_object( $value ) ) {
328 throw new UnexpectedValueException(
329 get_class( $oldValue ) .
'::serializeForApiResult() returned an object of class ' .
338 }
catch ( Exception $ex ) {
339 throw new UnexpectedValueException(
340 get_class( $oldValue ) .
'::serializeForApiResult() returned an invalid value: ' .
346 } elseif ( is_callable( [ $value,
'__toString' ] ) ) {
347 $value = (string)$value;
349 $value = (array)$value + [ self::META_TYPE =>
'assoc' ];
352 if ( is_array( $value ) ) {
356 foreach ( $value as $k => $v ) {
360 } elseif ( is_float( $value ) && !is_finite( $value ) ) {
361 throw new InvalidArgumentException(
'Cannot add non-finite floats to ApiResult' );
362 } elseif ( is_string( $value ) ) {
363 $value = MediaWikiServices::getInstance()->getContentLanguage()->normalize( $value );
364 } elseif ( $value !==
null && !is_scalar( $value ) ) {
365 $type = gettype( $value );
366 if ( is_resource( $value ) ) {
367 $type .=
'(' . get_resource_type( $value ) .
')';
369 throw new InvalidArgumentException(
"Cannot add $type to ApiResult" );
392 $arr = &$this->
path(
$path, ( $flags & self::ADD_ON_TOP ) ?
'prepend' :
'append' );
394 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
401 if ( $this->maxSize !==
false && $newsize > $this->maxSize ) {
402 $this->errorFormatter->addWarning(
407 $this->
size = $newsize;
422 if ( isset( $arr[$name] ) ) {
424 unset( $arr[$name] );
441 if ( $name ===
null ) {
443 throw new InvalidArgumentException(
'Cannot remove the data root' );
445 $name = array_pop(
$path );
448 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
450 $this->
size = max( $newsize, 0 );
465 if ( $name ===
null ) {
466 throw new InvalidArgumentException(
'Content value must be named' );
483 if ( $name ===
null ) {
484 throw new InvalidArgumentException(
'Content value must be named' );
499 $this->
addValue(
'limits', $moduleName, $limit,
500 self::OVERRIDE | self::NO_SIZE_CHECK );
518 if ( isset( $arr[self::META_CONTENT] ) &&
519 isset( $arr[$arr[self::META_CONTENT]] ) &&
520 !( $flags & self::OVERRIDE )
522 throw new RuntimeException(
523 "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
524 ' is already set as the content element'
539 $arr = &$this->
path(
$path, ( $flags & self::ADD_ON_TOP ) ?
'prepend' :
'append' );
551 if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
578 if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
602 if ( !is_string( $tag ) ) {
603 throw new InvalidArgumentException(
'Bad tag name' );
627 if ( !is_string( $tag ) ) {
628 throw new InvalidArgumentException(
'Bad tag name' );
631 foreach ( $arr as $k => &$v ) {
632 if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
661 if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
688 if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
714 if ( !in_array(
$type, [
715 'default',
'array',
'assoc',
'kvp',
'BCarray',
'BCassoc',
'BCkvp'
717 throw new InvalidArgumentException(
'Bad type' );
720 if ( is_string( $kvpKeyName ) ) {
746 foreach ( $arr as $k => &$v ) {
747 if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
778 return substr( $key, 0, 1 ) ===
'_';
791 $strip = $transforms[
'Strip'] ??
'none';
792 if ( $strip ===
'base' ) {
793 $transforms[
'Strip'] =
'none';
795 $transformTypes = $transforms[
'Types'] ??
null;
796 if ( $transformTypes !==
null && !is_array( $transformTypes ) ) {
797 throw new InvalidArgumentException( __METHOD__ .
':Value for "Types" must be an array' );
803 if ( isset( $transforms[
'Custom'] ) ) {
804 if ( !is_callable( $transforms[
'Custom'] ) ) {
805 throw new InvalidArgumentException( __METHOD__ .
': Value for "Custom" must be callable' );
807 call_user_func_array( $transforms[
'Custom'], [ &
$data, &$metadata ] );
810 if ( ( isset( $transforms[
'BC'] ) || $transformTypes !==
null ) &&
811 isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] ===
'BCkvp' &&
812 !isset( $metadata[self::META_KVP_KEY_NAME] )
814 throw new UnexpectedValueException(
'Type "BCkvp" used without setting ' .
815 'ApiResult::META_KVP_KEY_NAME metadata item' );
820 if ( isset( $transforms[
'BC'] ) ) {
821 if ( !is_array( $transforms[
'BC'] ) ) {
822 throw new InvalidArgumentException( __METHOD__ .
':Value for "BC" must be an array' );
824 if ( !in_array(
'nobool', $transforms[
'BC'],
true ) ) {
825 $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
826 ? array_flip( $metadata[self::META_BC_BOOLS] )
830 if ( !in_array(
'no*', $transforms[
'BC'],
true ) &&
831 isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !==
'*'
839 if ( !in_array(
'nosub', $transforms[
'BC'],
true ) &&
840 isset( $metadata[self::META_BC_SUBELEMENTS] )
842 foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
843 if ( isset(
$data[$k] ) ) {
846 self::META_CONTENT =>
'*',
847 self::META_TYPE =>
'assoc',
853 if ( isset( $metadata[self::META_TYPE] ) ) {
854 switch ( $metadata[self::META_TYPE] ) {
867 $defaultType =
'array';
869 foreach (
$data as $k => &$v ) {
871 if ( $boolKeys !==
null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
878 if ( is_string( $k ) ) {
879 $defaultType =
'assoc';
880 } elseif ( $k > $maxKey ) {
893 $keepMetadata = &$metadata;
896 $keepMetadata = array_intersect_key( $metadata, [
897 self::META_INDEXED_TAG_NAME => 1,
898 self::META_SUBELEMENTS => 1,
902 throw new InvalidArgumentException( __METHOD__ .
': Unknown value for "Strip"' );
906 if ( $transformTypes !==
null ) {
907 if ( $defaultType ===
'array' && $maxKey !== count(
$data ) - 1 ) {
908 $defaultType =
'assoc';
912 $type = $defaultType;
913 if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !==
'default' ) {
916 if ( (
$type ===
'kvp' ||
$type ===
'BCkvp' ) &&
917 empty( $transformTypes[
'ArmorKVP'] )
920 } elseif (
$type ===
'BCarray' ) {
922 } elseif (
$type ===
'BCassoc' ) {
930 $data += $keepMetadata;
931 return empty( $transformTypes[
'AssocAsObject'] ) ?
$data : (object)
$data;
937 return $data + $keepMetadata;
942 $valKey = isset( $transforms[
'BC'] ) ?
'*' :
'value';
943 $assocAsObject = !empty( $transformTypes[
'AssocAsObject'] );
944 $merge = !empty( $metadata[self::META_KVP_MERGE] );
947 foreach (
$data as $k => $v ) {
948 if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
950 if ( isset( $vArr[self::META_TYPE] ) ) {
952 } elseif ( is_object( $v ) ) {
953 $mergeType =
'assoc';
955 $keys = array_keys( $vArr );
956 sort(
$keys, SORT_NUMERIC );
957 $mergeType = (
$keys === array_keys(
$keys ) ) ?
'array' :
'assoc';
962 if ( $mergeType ===
'assoc' ) {
966 if ( $strip ===
'none' ) {
974 if ( $strip ===
'none' ) {
976 self::META_PRESERVE_KEYS => [ $key ],
977 self::META_CONTENT => $valKey,
978 self::META_TYPE =>
'assoc',
982 $ret[] = $assocAsObject ? (object)$item : $item;
986 return $ret + $keepMetadata;
989 throw new UnexpectedValueException(
"Unknown type '$type'" );
992 return $data + $keepMetadata;
1007 if ( is_array(
$data ) || is_object(
$data ) ) {
1008 $isObj = is_object(
$data );
1012 $preserveKeys = isset(
$data[self::META_PRESERVE_KEYS] )
1013 ? (array)
$data[self::META_PRESERVE_KEYS]
1015 foreach (
$data as $k => $v ) {
1016 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys,
true ) ) {
1018 } elseif ( is_array( $v ) || is_object( $v ) ) {
1041 if ( !is_array( $metadata ) ) {
1044 if ( is_array(
$data ) || is_object(
$data ) ) {
1045 $isObj = is_object(
$data );
1049 $preserveKeys = isset(
$data[self::META_PRESERVE_KEYS] )
1050 ? (array)
$data[self::META_PRESERVE_KEYS]
1052 foreach (
$data as $k => $v ) {
1053 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys,
true ) ) {
1071 private static function size( $value ) {
1073 if ( is_array( $value ) ) {
1074 foreach ( $value as $k => $v ) {
1075 if ( !self::isMetadataKey( $k ) ) {
1079 } elseif ( is_scalar( $value ) ) {
1080 $s = strlen( $value );
1100 foreach (
$path as $i => $k ) {
1101 if ( !isset( $ret[$k] ) ) {
1102 switch ( $create ) {
1107 $ret = [ $k => [] ] + $ret;
1113 $fail = implode(
'.', array_slice(
$path, 0, $i + 1 ) );
1114 throw new InvalidArgumentException(
"Path $fail does not exist" );
1117 if ( !is_array( $ret[$k] ) ) {
1118 $fail = implode(
'.', array_slice(
$path, 0, $i + 1 ) );
1119 throw new InvalidArgumentException(
"Path $fail is not an array" );
1139 foreach ( $vars as $k => $v ) {
1140 if ( is_array( $v ) || is_object( $v ) ) {
1142 } elseif ( is_bool( $v ) ) {
1146 if ( is_string( $k ) ) {
1148 } elseif ( $k > $maxKey ) {
1152 if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1160 $keys = array_diff( array_keys( $vars ), [
1161 self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1162 self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1166 self::META_TYPE =>
'kvp',
1167 self::META_KVP_KEY_NAME =>
'key',
1168 self::META_PRESERVE_KEYS =>
$keys,
1169 self::META_BC_BOOLS => $bools,
1170 self::META_INDEXED_TAG_NAME =>
'var',
1174 self::META_TYPE =>
'array',
1175 self::META_BC_BOOLS => $bools,
1176 self::META_INDEXED_TAG_NAME =>
'value',
1191 if ( $dbInfinity ===
null ) {
1195 if ( $expiry ===
'' || $expiry ===
null || $expiry ===
false ||