MediaWiki master
ApiResult.php
Go to the documentation of this file.
1<?php
22
35class ApiResult implements ApiSerializable {
36
41 public const OVERRIDE = 1;
42
49 public const ADD_ON_TOP = 2;
50
58 public const NO_SIZE_CHECK = 4;
59
66 public const NO_VALIDATE = self::NO_SIZE_CHECK | 8;
67
72 public const META_INDEXED_TAG_NAME = '_element';
73
78 public const META_SUBELEMENTS = '_subelements';
79
84 public const META_PRESERVE_KEYS = '_preservekeys';
85
90 public const META_CONTENT = '_content';
91
110 public const META_TYPE = '_type';
111
119 public const META_KVP_KEY_NAME = '_kvpkeyname';
120
129 public const META_KVP_MERGE = '_kvpmerge';
130
136 public const META_BC_BOOLS = '_BC_bools';
137
143 public const META_BC_SUBELEMENTS = '_BC_subelements';
144
145 private $data, $size, $maxSize;
146 private $errorFormatter;
147
151 public function __construct( $maxSize ) {
152 $this->maxSize = $maxSize;
153 $this->reset();
154 }
155
160 public function setErrorFormatter( ApiErrorFormatter $formatter ) {
161 $this->errorFormatter = $formatter;
162 }
163
169 public function serializeForApiResult() {
170 return $this->data;
171 }
172
173 /***************************************************************************/
174 // region Content
180 public function reset() {
181 $this->data = [
182 self::META_TYPE => 'assoc', // Usually what's desired
183 ];
184 $this->size = 0;
185 }
186
240 public function getResultData( $path = [], $transforms = [] ) {
241 $path = (array)$path;
242 if ( !$path ) {
243 return self::applyTransformations( $this->data, $transforms );
244 }
245
246 $last = array_pop( $path );
247 $ret = &$this->path( $path, 'dummy' );
248 if ( !isset( $ret[$last] ) ) {
249 return null;
250 } elseif ( is_array( $ret[$last] ) ) {
251 return self::applyTransformations( $ret[$last], $transforms );
252 } else {
253 return $ret[$last];
254 }
255 }
256
261 public function getSize() {
262 return $this->size;
263 }
264
277 public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
278 if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
279 $value = self::validateValue( $value );
280 }
281
282 if ( $name === null ) {
283 if ( $flags & self::ADD_ON_TOP ) {
284 array_unshift( $arr, $value );
285 } else {
286 $arr[] = $value;
287 }
288 return;
289 }
290
291 $exists = isset( $arr[$name] );
292 if ( !$exists || ( $flags & self::OVERRIDE ) ) {
293 if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
294 $arr = [ $name => $value ] + $arr;
295 } else {
296 $arr[$name] = $value;
297 }
298 } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
299 $conflicts = array_intersect_key( $arr[$name], $value );
300 if ( !$conflicts ) {
301 $arr[$name] += $value;
302 } else {
303 $keys = implode( ', ', array_keys( $conflicts ) );
304 throw new RuntimeException(
305 "Conflicting keys ($keys) when attempting to merge element $name"
306 );
307 }
308 } elseif ( $value !== $arr[$name] ) {
309 throw new RuntimeException(
310 "Attempting to add element $name=$value, existing value is {$arr[$name]}"
311 );
312 }
313 }
314
320 private static function validateValue( $value ) {
321 if ( is_object( $value ) ) {
322 // Note we use is_callable() here instead of instanceof because
323 // ApiSerializable is an informal protocol (see docs there for details).
324 if ( is_callable( [ $value, 'serializeForApiResult' ] ) ) {
325 $oldValue = $value;
326 $value = $value->serializeForApiResult();
327 if ( is_object( $value ) ) {
328 throw new UnexpectedValueException(
329 get_class( $oldValue ) . '::serializeForApiResult() returned an object of class ' .
330 get_class( $value )
331 );
332 }
333
334 // Recursive call instead of fall-through so we can throw a
335 // better exception message.
336 try {
337 return self::validateValue( $value );
338 } catch ( Exception $ex ) {
339 throw new UnexpectedValueException(
340 get_class( $oldValue ) . '::serializeForApiResult() returned an invalid value: ' .
341 $ex->getMessage(),
342 0,
343 $ex
344 );
345 }
346 } elseif ( is_callable( [ $value, '__toString' ] ) ) {
347 $value = (string)$value;
348 } else {
349 $value = (array)$value + [ self::META_TYPE => 'assoc' ];
350 }
351 }
352
353 if ( is_string( $value ) ) {
354 // Optimization: avoid querying the service locator for each value.
355 static $contentLanguage = null;
356 if ( !$contentLanguage ) {
357 $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
358 }
359 $value = $contentLanguage->normalize( $value );
360 } elseif ( is_array( $value ) ) {
361 foreach ( $value as $k => $v ) {
362 $value[$k] = self::validateValue( $v );
363 }
364 } elseif ( $value !== null && !is_scalar( $value ) ) {
365 $type = gettype( $value );
366 // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.is_resource
367 if ( is_resource( $value ) ) {
368 $type .= '(' . get_resource_type( $value ) . ')';
369 }
370 throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
371 } elseif ( is_float( $value ) && !is_finite( $value ) ) {
372 throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
373 }
374
375 return $value;
376 }
377
394 public function addValue( $path, $name, $value, $flags = 0 ) {
395 $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
396
397 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
398 // self::size needs the validated value. Then flag
399 // to not re-validate later.
400 $value = self::validateValue( $value );
401 $flags |= self::NO_VALIDATE;
402
403 $newsize = $this->size + self::size( $value );
404 if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
405 $this->errorFormatter->addWarning(
406 'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
407 );
408 return false;
409 }
410 $this->size = $newsize;
411 }
412
413 self::setValue( $arr, $name, $value, $flags );
414 return true;
415 }
416
423 public static function unsetValue( array &$arr, $name ) {
424 $ret = null;
425 if ( isset( $arr[$name] ) ) {
426 $ret = $arr[$name];
427 unset( $arr[$name] );
428 }
429 return $ret;
430 }
431
442 public function removeValue( $path, $name, $flags = 0 ) {
443 $path = (array)$path;
444 if ( $name === null ) {
445 if ( !$path ) {
446 throw new InvalidArgumentException( 'Cannot remove the data root' );
447 }
448 $name = array_pop( $path );
449 }
450 $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
451 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
452 $newsize = $this->size - self::size( $ret );
453 $this->size = max( $newsize, 0 );
454 }
455 return $ret;
456 }
457
467 public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
468 if ( $name === null ) {
469 throw new InvalidArgumentException( 'Content value must be named' );
470 }
471 self::setContentField( $arr, $name, $flags );
472 self::setValue( $arr, $name, $value, $flags );
473 }
474
485 public function addContentValue( $path, $name, $value, $flags = 0 ) {
486 if ( $name === null ) {
487 throw new InvalidArgumentException( 'Content value must be named' );
488 }
489 $this->addContentField( $path, $name, $flags );
490 return $this->addValue( $path, $name, $value, $flags );
491 }
492
500 public function addParsedLimit( $moduleName, $limit ) {
501 // Add value, allowing overwriting
502 $this->addValue( 'limits', $moduleName, $limit,
503 self::OVERRIDE | self::NO_SIZE_CHECK );
504 }
505
506 // endregion -- end of Content
507
508 /***************************************************************************/
509 // region Metadata
520 public static function setContentField( array &$arr, $name, $flags = 0 ) {
521 if ( isset( $arr[self::META_CONTENT] ) &&
522 isset( $arr[$arr[self::META_CONTENT]] ) &&
523 !( $flags & self::OVERRIDE )
524 ) {
525 throw new RuntimeException(
526 "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
527 ' is already set as the content element'
528 );
529 }
530 $arr[self::META_CONTENT] = $name;
531 }
532
541 public function addContentField( $path, $name, $flags = 0 ) {
542 $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
543 self::setContentField( $arr, $name, $flags );
544 }
545
553 public static function setSubelementsList( array &$arr, $names ) {
554 if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
555 $arr[self::META_SUBELEMENTS] = (array)$names;
556 } else {
557 $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
558 }
559 }
560
568 public function addSubelementsList( $path, $names ) {
569 $arr = &$this->path( $path );
570 self::setSubelementsList( $arr, $names );
571 }
572
580 public static function unsetSubelementsList( array &$arr, $names ) {
581 if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
582 $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
583 }
584 }
585
593 public function removeSubelementsList( $path, $names ) {
594 $arr = &$this->path( $path );
595 self::unsetSubelementsList( $arr, $names );
596 }
597
604 public static function setIndexedTagName( array &$arr, $tag ) {
605 if ( !is_string( $tag ) ) {
606 throw new InvalidArgumentException( 'Bad tag name' );
607 }
608 $arr[self::META_INDEXED_TAG_NAME] = $tag;
609 }
610
617 public function addIndexedTagName( $path, $tag ) {
618 $arr = &$this->path( $path );
619 self::setIndexedTagName( $arr, $tag );
620 }
621
629 public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
630 if ( !is_string( $tag ) ) {
631 throw new InvalidArgumentException( 'Bad tag name' );
632 }
633 $arr[self::META_INDEXED_TAG_NAME] = $tag;
634 foreach ( $arr as $k => &$v ) {
635 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
636 self::setIndexedTagNameRecursive( $v, $tag );
637 }
638 }
639 }
640
648 public function addIndexedTagNameRecursive( $path, $tag ) {
649 $arr = &$this->path( $path );
650 self::setIndexedTagNameRecursive( $arr, $tag );
651 }
652
663 public static function setPreserveKeysList( array &$arr, $names ) {
664 if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
665 $arr[self::META_PRESERVE_KEYS] = (array)$names;
666 } else {
667 $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
668 }
669 }
670
678 public function addPreserveKeysList( $path, $names ) {
679 $arr = &$this->path( $path );
680 self::setPreserveKeysList( $arr, $names );
681 }
682
690 public static function unsetPreserveKeysList( array &$arr, $names ) {
691 if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
692 $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
693 }
694 }
695
703 public function removePreserveKeysList( $path, $names ) {
704 $arr = &$this->path( $path );
705 self::unsetPreserveKeysList( $arr, $names );
706 }
707
716 public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
717 if ( !in_array( $type, [
718 'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
719 ], true ) ) {
720 throw new InvalidArgumentException( 'Bad type' );
721 }
722 $arr[self::META_TYPE] = $type;
723 if ( is_string( $kvpKeyName ) ) {
724 $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
725 }
726 }
727
735 public function addArrayType( $path, $tag, $kvpKeyName = null ) {
736 $arr = &$this->path( $path );
737 self::setArrayType( $arr, $tag, $kvpKeyName );
738 }
739
747 public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
748 self::setArrayType( $arr, $type, $kvpKeyName );
749 foreach ( $arr as $k => &$v ) {
750 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
751 self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
752 }
753 }
754 }
755
763 public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
764 $arr = &$this->path( $path );
765 self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
766 }
767
768 // endregion -- end of Metadata
769
770 /***************************************************************************/
771 // region Utility
780 public static function isMetadataKey( $key ) {
781 // Optimization: This is a very hot and highly optimized code path. Note that ord() only
782 // considers the first character and also works with empty strings and integers.
783 // 95 corresponds to the '_' character.
784 return ord( $key ) === 95;
785 }
786
796 protected static function applyTransformations( array $dataIn, array $transforms ) {
797 $strip = $transforms['Strip'] ?? 'none';
798 if ( $strip === 'base' ) {
799 $transforms['Strip'] = 'none';
800 }
801 $transformTypes = $transforms['Types'] ?? null;
802 if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
803 throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
804 }
805
806 $metadata = [];
807 $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
808
809 if ( isset( $transforms['Custom'] ) ) {
810 if ( !is_callable( $transforms['Custom'] ) ) {
811 throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
812 }
813 call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
814 }
815
816 if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
817 isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
818 !isset( $metadata[self::META_KVP_KEY_NAME] )
819 ) {
820 throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
821 'ApiResult::META_KVP_KEY_NAME metadata item' );
822 }
823
824 // BC transformations
825 $boolKeys = null;
826 if ( isset( $transforms['BC'] ) ) {
827 if ( !is_array( $transforms['BC'] ) ) {
828 throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
829 }
830 if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
831 $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
832 ? array_fill_keys( $metadata[self::META_BC_BOOLS], true )
833 : [];
834 }
835
836 if ( !in_array( 'no*', $transforms['BC'], true ) &&
837 isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
838 ) {
839 $k = $metadata[self::META_CONTENT];
840 $data['*'] = $data[$k];
841 unset( $data[$k] );
842 $metadata[self::META_CONTENT] = '*';
843 }
844
845 if ( !in_array( 'nosub', $transforms['BC'], true ) &&
846 isset( $metadata[self::META_BC_SUBELEMENTS] )
847 ) {
848 foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
849 if ( isset( $data[$k] ) ) {
850 $data[$k] = [
851 '*' => $data[$k],
852 self::META_CONTENT => '*',
853 self::META_TYPE => 'assoc',
854 ];
855 }
856 }
857 }
858
859 if ( isset( $metadata[self::META_TYPE] ) ) {
860 switch ( $metadata[self::META_TYPE] ) {
861 case 'BCarray':
862 case 'BCassoc':
863 $metadata[self::META_TYPE] = 'default';
864 break;
865 case 'BCkvp':
866 $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
867 break;
868 }
869 }
870 }
871
872 // Figure out type, do recursive calls, and do boolean transform if necessary
873 $defaultType = 'array';
874 $maxKey = -1;
875 foreach ( $data as $k => &$v ) {
876 $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
877 if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
878 if ( !$v ) {
879 unset( $data[$k] );
880 continue;
881 }
882 $v = '';
883 }
884 if ( is_string( $k ) ) {
885 $defaultType = 'assoc';
886 } elseif ( $k > $maxKey ) {
887 $maxKey = $k;
888 }
889 }
890 unset( $v );
891
892 // Determine which metadata to keep
893 switch ( $strip ) {
894 case 'all':
895 case 'base':
896 $keepMetadata = [];
897 break;
898 case 'none':
899 $keepMetadata = &$metadata;
900 break;
901 case 'bc':
902 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal Type mismatch on pass-by-ref args
903 $keepMetadata = array_intersect_key( $metadata, [
904 self::META_INDEXED_TAG_NAME => 1,
905 self::META_SUBELEMENTS => 1,
906 ] );
907 break;
908 default:
909 throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
910 }
911
912 // No type transformation
913 if ( $transformTypes === null ) {
914 return $data + $keepMetadata;
915 }
916
917 if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
918 $defaultType = 'assoc';
919 }
920
921 // Override type, if provided
922 $type = $defaultType;
923 if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
924 $type = $metadata[self::META_TYPE];
925 }
926 if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
927 empty( $transformTypes['ArmorKVP'] )
928 ) {
929 $type = 'assoc';
930 } elseif ( $type === 'BCarray' ) {
931 $type = 'array';
932 } elseif ( $type === 'BCassoc' ) {
933 $type = 'assoc';
934 }
935
936 // Apply transformation
937 switch ( $type ) {
938 case 'assoc':
939 $metadata[self::META_TYPE] = 'assoc';
940 $data += $keepMetadata;
941 return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
942
943 case 'array':
944 ksort( $data );
945 $data = array_values( $data );
946 $metadata[self::META_TYPE] = 'array';
947 // @phan-suppress-next-line PhanTypeMismatchReturnNullable Type mismatch on pass-by-ref args
948 return $data + $keepMetadata;
949
950 case 'kvp':
951 case 'BCkvp':
952 $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
953 $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
954 $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
955 $merge = !empty( $metadata[self::META_KVP_MERGE] );
956
957 $ret = [];
958 foreach ( $data as $k => $v ) {
959 if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
960 $vArr = (array)$v;
961 if ( isset( $vArr[self::META_TYPE] ) ) {
962 $mergeType = $vArr[self::META_TYPE];
963 } elseif ( is_object( $v ) ) {
964 $mergeType = 'assoc';
965 } else {
966 $keys = array_keys( $vArr );
967 sort( $keys, SORT_NUMERIC );
968 $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
969 }
970 } else {
971 $mergeType = 'n/a';
972 }
973 if ( $mergeType === 'assoc' ) {
974 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable vArr set when used
975 $item = $vArr + [
976 $key => $k,
977 ];
978 if ( $strip === 'none' ) {
979 self::setPreserveKeysList( $item, [ $key ] );
980 }
981 } else {
982 $item = [
983 $key => $k,
984 $valKey => $v,
985 ];
986 if ( $strip === 'none' ) {
987 $item += [
988 self::META_PRESERVE_KEYS => [ $key ],
989 self::META_CONTENT => $valKey,
990 self::META_TYPE => 'assoc',
991 ];
992 }
993 }
994 $ret[] = $assocAsObject ? (object)$item : $item;
995 }
996 $metadata[self::META_TYPE] = 'array';
997
998 // @phan-suppress-next-line PhanTypeMismatchReturnNullable Type mismatch on pass-by-ref args
999 return $ret + $keepMetadata;
1000
1001 default:
1002 throw new UnexpectedValueException( "Unknown type '$type'" );
1003 }
1004 }
1005
1016 public static function stripMetadata( $data ) {
1017 if ( is_array( $data ) || is_object( $data ) ) {
1018 $isObj = is_object( $data );
1019 if ( $isObj ) {
1020 $data = (array)$data;
1021 }
1022 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1023 ? (array)$data[self::META_PRESERVE_KEYS]
1024 : [];
1025 foreach ( $data as $k => $v ) {
1026 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1027 unset( $data[$k] );
1028 } elseif ( is_array( $v ) || is_object( $v ) ) {
1029 $data[$k] = self::stripMetadata( $v );
1030 }
1031 }
1032 if ( $isObj ) {
1033 $data = (object)$data;
1034 }
1035 }
1036 return $data;
1037 }
1038
1050 public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1051 if ( !is_array( $metadata ) ) {
1052 $metadata = [];
1053 }
1054 if ( is_array( $data ) || is_object( $data ) ) {
1055 $isObj = is_object( $data );
1056 if ( $isObj ) {
1057 $data = (array)$data;
1058 }
1059 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1060 ? (array)$data[self::META_PRESERVE_KEYS]
1061 : [];
1062 foreach ( $data as $k => $v ) {
1063 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1064 $metadata[$k] = $v;
1065 unset( $data[$k] );
1066 }
1067 }
1068 if ( $isObj ) {
1069 $data = (object)$data;
1070 }
1071 }
1072 return $data;
1073 }
1074
1081 private static function size( $value ) {
1082 $s = 0;
1083 if ( is_array( $value ) ) {
1084 foreach ( $value as $k => $v ) {
1085 if ( !self::isMetadataKey( $k ) ) {
1086 $s += self::size( $v );
1087 }
1088 }
1089 } elseif ( is_scalar( $value ) ) {
1090 $s = strlen( $value );
1091 }
1092
1093 return $s;
1094 }
1095
1107 private function &path( $path, $create = 'append' ) {
1108 $path = (array)$path;
1109 $ret = &$this->data;
1110 foreach ( $path as $i => $k ) {
1111 if ( !isset( $ret[$k] ) ) {
1112 switch ( $create ) {
1113 case 'append':
1114 $ret[$k] = [];
1115 break;
1116 case 'prepend':
1117 $ret = [ $k => [] ] + $ret;
1118 break;
1119 case 'dummy':
1120 $tmp = [];
1121 return $tmp;
1122 default:
1123 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1124 throw new InvalidArgumentException( "Path $fail does not exist" );
1125 }
1126 }
1127 if ( !is_array( $ret[$k] ) ) {
1128 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1129 throw new InvalidArgumentException( "Path $fail is not an array" );
1130 }
1131 $ret = &$ret[$k];
1132 }
1133 return $ret;
1134 }
1135
1144 public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1145 // Process subarrays and determine if this is a JS [] or {}
1146 $hash = $forceHash;
1147 $maxKey = -1;
1148 $bools = [];
1149 foreach ( $vars as $k => $v ) {
1150 if ( is_array( $v ) || is_object( $v ) ) {
1151 $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1152 } elseif ( is_bool( $v ) ) {
1153 // Better here to use real bools even in BC formats
1154 $bools[] = $k;
1155 }
1156 if ( is_string( $k ) ) {
1157 $hash = true;
1158 } elseif ( $k > $maxKey ) {
1159 $maxKey = $k;
1160 }
1161 }
1162 if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1163 $hash = true;
1164 }
1165
1166 // Set metadata appropriately
1167 if ( $hash ) {
1168 // Get the list of keys we actually care about. Unfortunately, we can't support
1169 // certain keys that conflict with ApiResult metadata.
1170 $keys = array_diff( array_keys( $vars ), [
1171 self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1172 self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1173 ] );
1174
1175 return [
1176 self::META_TYPE => 'kvp',
1177 self::META_KVP_KEY_NAME => 'key',
1178 self::META_PRESERVE_KEYS => $keys,
1179 self::META_BC_BOOLS => $bools,
1180 self::META_INDEXED_TAG_NAME => 'var',
1181 ] + $vars;
1182 } else {
1183 return [
1184 self::META_TYPE => 'array',
1185 self::META_BC_BOOLS => $bools,
1186 self::META_INDEXED_TAG_NAME => 'value',
1187 ] + $vars;
1188 }
1189 }
1190
1199 public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1200 static $dbInfinity;
1201 $dbInfinity ??= MediaWikiServices::getInstance()->getConnectionProvider()
1202 ->getReplicaDatabase()
1203 ->getInfinity();
1204
1205 if ( $expiry === '' || $expiry === null || $expiry === false ||
1206 wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1207 ) {
1208 return $infinity;
1209 } else {
1210 return wfTimestamp( TS_ISO_8601, $expiry );
1211 }
1212 }
1213
1214 // endregion -- end of Utility
1215
1216}
1217
1218/*
1219 * This file uses VisualStudio style region/endregion fold markers which are
1220 * recognised by PHPStorm. If modelines are enabled, the following editor
1221 * configuration will also enable folding in vim, if it is in the last 5 lines
1222 * of the file. We also use "@name" which creates sections in Doxygen.
1223 *
1224 * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
1225 */
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Formats errors and warnings for the API, and add them to the associated ApiResult.
This class represents the result of the API operations.
Definition ApiResult.php:35
addArrayType( $path, $tag, $kvpKeyName=null)
Set the array data type for a path.
static unsetSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
static unsetPreserveKeysList(array &$arr, $names)
Don't preserve specified keys.
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
const META_TYPE
Key for the 'type' metadata item.
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
serializeForApiResult()
Allow for adding one ApiResult into another.
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
static setValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name.
const META_SUBELEMENTS
Key for the 'subelements' metadata item.
Definition ApiResult.php:78
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
const META_PRESERVE_KEYS
Key for the 'preserve keys' metadata item.
Definition ApiResult.php:84
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
Definition ApiResult.php:58
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
getSize()
Get the size of the result, i.e.
__construct( $maxSize)
static unsetValue(array &$arr, $name)
Remove an output value to the array by name.
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
addPreserveKeysList( $path, $names)
Preserve specified keys.
addParsedLimit( $moduleName, $limit)
Add the numeric limit for a limit=max to the result.
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
const META_CONTENT
Key for the 'content' metadata item.
Definition ApiResult.php:90
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
removeSubelementsList( $path, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
setErrorFormatter(ApiErrorFormatter $formatter)
const OVERRIDE
Override existing value in addValue(), setValue(), and similar functions.
Definition ApiResult.php:41
static setSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
static setArrayTypeRecursive(array &$arr, $type, $kvpKeyName=null)
Set the array data type recursively.
const META_KVP_KEY_NAME
Key for the metadata item whose value specifies the name used for the kvp key in the alternative outp...
removeValue( $path, $name, $flags=0)
Remove value from the output data at the given path.
const ADD_ON_TOP
For addValue(), setValue() and similar functions, if the value does not exist, add it as the first el...
Definition ApiResult.php:49
getResultData( $path=[], $transforms=[])
Get the result data array.
const META_BC_SUBELEMENTS
Key for the 'BC subelements' metadata item.
removePreserveKeysList( $path, $names)
Don't preserve specified keys.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
const META_INDEXED_TAG_NAME
Key for the 'indexed tag name' metadata item.
Definition ApiResult.php:72
const META_KVP_MERGE
Key for the metadata item that indicates that the KVP key should be added into an assoc value,...
reset()
Clear the current result data.
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
static setIndexedTagNameRecursive(array &$arr, $tag)
Set indexed tag name on $arr and all subarrays.
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
const NO_VALIDATE
For addValue(), setValue() and similar functions, do not validate data.
Definition ApiResult.php:66
addContentValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path and mark as META_CONTENT.
addContentField( $path, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Service locator for MediaWiki core services.
This interface allows for overriding the default conversion applied by ApiResult::validateValue().