Changeset 62474
- Timestamp:
- 06/08/2026 09:24:17 PM (3 months ago)
- Location:
- trunk
- Files:
-
- 2 edited
-
src/wp-includes/rest-api.php (modified) (2 diffs)
-
tests/phpunit/tests/rest-api.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/rest-api.php
r62334 r62474 1565 1565 * Determines if a given value is integer-like. 1566 1566 * 1567 * This reports whether the value represents an integer; it does not guarantee that the 1568 * value can be represented as a native PHP integer. Values whose magnitude exceeds 1569 * `PHP_INT_MAX` are still reported as integer-like, even though the `(int)` cast that 1570 * {@see rest_sanitize_value_from_schema()} applies for the 'integer' type cannot round-trip 1571 * them: an out-of-range numeric *string* saturates to `PHP_INT_MAX` or `PHP_INT_MIN`, while 1572 * an out-of-range *float* is an undefined conversion in PHP that yields an arbitrary wrapped 1573 * value. Likewise, a numeric value with a fractional part that is too large for the fraction 1574 * to be represented as a float (greater than 2 ** 53) is reported as integer-like. 1575 * 1567 1576 * @since 5.5.0 1568 1577 * … … 1570 1579 * @return bool True if an integer, otherwise false. 1571 1580 */ 1572 function rest_is_integer( $maybe_integer ) { 1573 return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer; 1581 function rest_is_integer( $maybe_integer ): bool { 1582 if ( is_int( $maybe_integer ) ) { 1583 return true; 1584 } 1585 1586 // A canonical integer string of any magnitude — verified without float conversion. 1587 if ( is_string( $maybe_integer ) && preg_match( '/^\s*[+-]?[0-9]+\s*$/', $maybe_integer ) ) { 1588 return true; 1589 } 1590 1591 // Decimal and scientific-notation strings (and floats) keep their historical behavior. 1592 if ( ! is_numeric( $maybe_integer ) ) { 1593 return false; 1594 } 1595 $float_value = (float) $maybe_integer; 1596 1597 /* 1598 * The strict equality here is not the unreliable "are two computed floats equal" comparison 1599 * (e.g. 0.1 + 0.2 === 0.3, which is false). It compares a float to its own floor() to ask 1600 * "does this float have a fractional part?". A float is whole exactly when it equals its floor, 1601 * so the comparison is exact and safe regardless of floating-point representation error. 1602 */ 1603 return floor( $float_value ) === $float_value; 1574 1604 } 1575 1605 -
trunk/tests/phpunit/tests/rest-api.php
r60104 r62474 2291 2291 2292 2292 /** 2293 * Tests rest_is_integer(). 2294 * 2293 2295 * @ticket 51146 2296 * @ticket 65271 2294 2297 * 2295 2298 * @dataProvider data_rest_is_integer 2296 2299 * 2297 * @param bool $expected Expected result of the check. 2298 * @param mixed $value The value to check. 2299 */ 2300 public function test_rest_is_integer( $expected, $value ) { 2300 * @param bool $expected_is_integer Expected result of the check. 2301 * @param mixed $value The value to check. 2302 * @param int|null $expected_sanitized For integer-like values, the integer that 2303 * {@see rest_sanitize_value_from_schema()} should return. 2304 * A value of null means the value is integer-like but its 2305 * sanitized result is not checked, because the value is too 2306 * large to reason about: the `(int)` cast of an out-of-range 2307 * float is undefined in PHP, so the result is unspecified. 2308 * 2309 * @covers ::rest_is_integer 2310 * @covers ::rest_sanitize_value_from_schema 2311 */ 2312 public function test_rest_is_integer( bool $expected_is_integer, $value, ?int $expected_sanitized = null ): void { 2301 2313 $is_integer = rest_is_integer( $value ); 2302 2314 2303 if ( $expected ) {2315 if ( $expected_is_integer ) { 2304 2316 $this->assertTrue( $is_integer ); 2317 2318 /* 2319 * Validation and sanitization must agree: any value treated as integer-like 2320 * must also be sanitized to the expected integer by the 'integer' type, 2321 * without the value being munged by the (int) cast. This is skipped when 2322 * $expected_sanitized is null, since the sanitized result of an out-of-range 2323 * float is undefined and therefore not worth asserting. 2324 */ 2325 if ( null !== $expected_sanitized ) { 2326 $sanitized = rest_sanitize_value_from_schema( $value, array( 'type' => 'integer' ) ); 2327 $this->assertSame( 2328 $expected_sanitized, 2329 $sanitized, 2330 'Sanitization should return the expected integer without munging the value.' 2331 ); 2332 } 2305 2333 } else { 2306 2334 $this->assertFalse( $is_integer ); … … 2308 2336 } 2309 2337 2310 public function data_rest_is_integer() { 2338 /** 2339 * Data provider for {@see self::test_rest_is_integer()}. 2340 * 2341 * Integer-like rows include a third element: the integer that 2342 * rest_sanitize_value_from_schema() should produce for the value. 2343 * 2344 * @return list<array<int, mixed>> 2345 * 2346 * @phpstan-return list<array{ 2347 * 0: bool, // $expected_is_integer 2348 * 1: mixed, // $value 2349 * 2?: int, // $expected_sanitized 2350 * }> 2351 */ 2352 public function data_rest_is_integer(): array { 2311 2353 return array( 2312 2354 array( 2313 2355 true, 2314 2356 1, 2357 1, 2315 2358 ), 2316 2359 array( 2317 2360 true, 2318 2361 '1', 2362 1, 2319 2363 ), 2320 2364 array( 2321 2365 true, 2322 2366 0, 2367 0, 2323 2368 ), 2324 2369 array( 2325 2370 true, 2326 2371 -1, 2372 -1, 2327 2373 ), 2328 2374 array( 2329 2375 true, 2330 2376 '05', 2377 5, 2331 2378 ), 2332 2379 array( … … 2344 2391 array( 2345 2392 false, 2393 -5.5, 2394 ), 2395 array( 2396 false, 2397 '-5.5', 2398 ), 2399 array( 2400 false, 2346 2401 array(), 2347 2402 ), … … 2349 2404 false, 2350 2405 true, 2406 ), 2407 array( 2408 true, 2409 '15e0', 2410 15, 2411 ), 2412 array( 2413 true, 2414 '15e+0', 2415 15, 2416 ), 2417 array( 2418 true, 2419 '15e-0', 2420 15, 2421 ), 2422 array( 2423 false, 2424 '15e-1', 2425 ), 2426 2427 /* 2428 * Integer-valued floats and decimal strings are accepted for back-compatibility. 2429 * Each of these also round-trips cleanly through the (int) cast performed by 2430 * rest_sanitize_value_from_schema() for the 'integer' type. 2431 */ 2432 array( 2433 true, 2434 1.0, 2435 1, 2436 ), 2437 array( 2438 true, 2439 5.0, 2440 5, 2441 ), 2442 array( 2443 true, 2444 '1.0', 2445 1, 2446 ), 2447 array( 2448 true, 2449 '5.0', 2450 5, 2451 ), 2452 array( 2453 true, 2454 1.5e3, 2455 1500, 2456 ), 2457 array( 2458 true, 2459 '1.5e3', 2460 1500, 2461 ), 2462 array( 2463 true, 2464 '15e2', 2465 1500, 2466 ), 2467 2468 // Signed canonical integer strings. 2469 array( 2470 true, 2471 '+5', 2472 5, 2473 ), 2474 array( 2475 true, 2476 '-5', 2477 -5, 2478 ), 2479 2480 // Non-numeric and non-string scalars are not integers. 2481 array( 2482 false, 2483 false, 2484 ), 2485 array( 2486 false, 2487 null, 2488 ), 2489 2490 // The following values test very large integers. 2491 array( 2492 true, 2493 2 ** 52, 2494 2 ** 52, 2495 ), 2496 array( 2497 true, 2498 2 ** 52 + 2, 2499 2 ** 52 + 2, 2500 ), 2501 array( 2502 true, 2503 '4503599627370496', // 2 ** 52 2504 4503599627370496, 2505 ), 2506 array( 2507 true, 2508 '4503599627370498', // 2 ** 52 + 2 2509 4503599627370498, 2510 ), 2511 array( 2512 true, 2513 '-4503599627370498', // -( 2 ** 52 + 2 ), a large negative integer string. 2514 -4503599627370498, 2515 ), 2516 array( 2517 true, 2518 '4611686018427387904', // 2 ** 62, a large positive integer string below PHP_INT_MAX. 2519 4611686018427387904, 2520 ), 2521 2522 /* 2523 * Out-of-range floats are reported as integer-like, but their sanitized value is 2524 * not asserted: integer arithmetic overflow promotes the result to a float, and the 2525 * subsequent (int) cast of an out-of-range float is undefined in PHP. The null 2526 * $expected_sanitized signals that the sanitized result should not be checked. 2527 */ 2528 array( 2529 true, 2530 PHP_INT_MAX + 1, // Integer overflow promotes to a float greater than PHP_INT_MAX. 2531 null, 2532 ), 2533 array( 2534 true, 2535 PHP_INT_MIN - 1, // Integer overflow promotes to a float less than PHP_INT_MIN. 2536 null, 2537 ), 2538 2539 /* 2540 * Canonical integer strings beyond the native integer range are reported as 2541 * integer-like, and unlike floats their (int) cast is well-defined: it saturates 2542 * to PHP_INT_MAX or PHP_INT_MIN, so the sanitized value can be asserted. 2543 */ 2544 array( 2545 true, 2546 PHP_INT_MAX . '000', // A string three orders of magnitude above PHP_INT_MAX, whatever the word size. 2547 PHP_INT_MAX, // (int) cast of an out-of-range numeric string saturates. 2548 ), 2549 array( 2550 true, 2551 PHP_INT_MIN . '000', // A string three orders of magnitude below PHP_INT_MIN, whatever the word size. 2552 PHP_INT_MIN, // (int) cast of an out-of-range numeric string saturates. 2351 2553 ), 2352 2554 );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)