Make WordPress Core

Opened 32 hours ago

Last modified 2 hours ago

#66583 new enhancement

REST API: Add WP_REST_Server methods that return HTTP method sets as arrays; soft-deprecate the string constants

Reported by: moonmeister Owned by:
Priority: normal Milestone: 7.3
Component: REST API Version: trunk
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses:

Description

Filing as part of my work on #65616 and at @westonruter suggestion as a solution to our issues as well as establishing a more mantainable and consistent standard going forward.

Problem

WP_REST_Server defines its HTTP method sets as comma-separated strings:

<?php
const READABLE   = 'GET';
const CREATABLE  = 'POST';
const EDITABLE   = 'POST, PUT, PATCH';
const DELETABLE  = 'DELETE';
const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';

hey are sets of methods, but three of them happen to hold exactly one method, and code in the wild treats them that way.

A sweep of the plugin directory finds 60 plugins with 20.8M active installs, including WooCommerce, Elementor and Jetpack, that would break if READABLE, CREATABLE or DELETABLE gained a second method. Some compare the constant with $request->get_method(); others pass it as the method of an outbound HTTP request.

So the sets are frozen. This already matters: the HTTP QUERY method (RFC 10008, #65616) is a safe, idempotent read, but it can't join READABLE or ALLMETHODS without changing every existing route on upgrade. Any future method has the same problem.

Proposal

Add static methods that return each set as string[]:

<?php
public static function get_readable_methods()  { return array( 'GET' ); }
public static function get_creatable_methods() { return array( 'POST' ); }
public static function get_editable_methods()  { return array( 'POST', 'PUT', 'PATCH' ); }
public static function get_deletable_methods() { return array( 'DELETE' ); }

register_rest_route() already accepts an array for methods, so these work without any other change:

<?php
register_rest_route( 'my-plugin/v1', '/items', array(
        array(
                'methods'             => WP_REST_Server::get_readable_methods(),
                'callback'            => 'my_plugin_get_items',
                'permission_callback' => '__return_true',
        ),
) );

Then soft-deprecate the constants with @deprecated docblocks pointing to the new methods. The constant values stay frozen, and there is no runtime notice.

Why functions returning arrays

  • The type matches the meaning. A set of methods is a list. An array can't be compared to a single method with ===, so code that adopts the new API has to treat it as a list, e.g.
<?php
in_array( $request->get_method(), WP_REST_Server::get_readable_methods(), true )
  • The sets can evolve. The docblocks say these lists may gain methods in future versions. A new method can then be added in one place, and only routes whose authors adopted the new API receive it.
  • Nothing changes on upgrade. Existing code keeps using the frozen constants and behaves exactly as before.

ALLMETHODS

This ticket proposes deprecating ALLMETHODS without a replacement. This should proabably be it's own ticket. I'll leave the reasoning for deprecation and a possible alternative. But if we're okay with defering any replacemnt I'll open a sperate ticket for discussion.

Why no direct replacement

  • The name promises more than a list can deliver. "All" reads as "any method", but it is five methods: it omits HEAD and OPTIONS, and the router already accepts any method name a route registers. A list can only ever mean "the methods enumerated so far."
  • It is mostly used to register one callback for every method. In plugins that callback usually serves reads and writes and tells them apart by checking for GET. That is exactly the shape that breaks when a new method is added: the callback can't tell a new safe method from a write. A growing list would send every future method straight into those callbacks.

Routes should register one handler per kind of operation, as core's controllers do. Code that needs a list of methods, for example to validate a method name, can merge the per-kind lists:

<?php
$methods = array_values( array_unique( array_merge(
        WP_REST_Server::get_readable_methods(),
        WP_REST_Server::get_creatable_methods(),
        WP_REST_Server::get_editable_methods(),
        WP_REST_Server::get_deletable_methods()
) ) );

Core's only use of ALLMETHODS, the abilities run controller, can use this merge and keep exactly its current behavior.

Alternative: get_supported_methods()

Instead of no replacement, add:

<?php
/**
 * The HTTP methods the REST API handles, including HEAD and OPTIONS, which core answers itself.
 *
 * This list grows when WordPress adds support for a method. A route registered with it
 * accepts responsibility for every method it may receive, now and in future versions,
 * and must handle unexpected methods safely, for example by returning a 405.
 *
 * @return string[]
 */
public static function get_supported_methods() {
        return array( 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE' );
}
  • It has a use inside core. rest_send_cors_headers() keeps its own list of methods for Access-Control-Allow-Methods. Deriving that list from this function means adding a method happens in one place.
  • It can be used for registration, with a documented contract. The name is honest: these are the methods WordPress supports, and the list will grow. A route that registers with it gets every supported method, so its callback must handle any method safely, current or fnderstands and return a 405 for the rest. The abilities run controller already worksthis way. It validates the method against each ability's annotations and returns a 405 otherwise, so it could switch to this function with no change in behavior. * Growth is a documented contract, not a backward-compatibility break. When list, routes registered with it will receive that method. A callback that mishandlesit, for example by treating anything other than GET as a write, is a bug in the callback against the documented contract. Core would announce each addition in a dev note ahead of the release, and a WordPress Coding Standards / Plugin Check warning on registering with this fu at the contract.

The trade-off: this keeps a supported, honest way to register a catch-all, at the casy to write. No replacement makes them deliberately awkward. This ticket leanstowards no replacement but would accept either.

Guidance for route authors

A route registered with one of these methods may receive methods added to the set in later versions. Callbacks shouldn't assume a particular method, such as treating `'GET' ===
$request->get_method()` as "this is a read". The docblocks and the REST API handbookdler per kind of operation, as core's own controllers do: reads in one callback,writes in another, never one callback that serves both.

Scope

  • The new methods, the @deprecated docblocks, and unit tests: list contents match the constants, constants unchanged, and registering with the arrays works.
  • Core's own registrations can move to the new methods here or in a follow-up. Eithehavior until a set gains a method.

Change History (2)

#1 @westonruter
31 hours ago

  • Milestone Awaiting Review → 7.3

This ticket was mentioned in ​PR #15829 on ​WordPress/wordpress-develop by ​@therssoftware.


2 hours ago
#2

  • Keywords has-patch has-unit-tests added; needs-patch removed

Description

WP_REST_Server currently defines HTTP method sets as comma-separated string constants:

  • READABLE = 'GET'
  • CREATABLE = 'POST'
  • EDITABLE = 'POST, PUT, PATCH'
  • DELETABLE = 'DELETE'
  • ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE'

Because three of these happen to contain a single method, extensive ecosystem code (including WooCommerce, Elementor, and Jetpack) performs strict or loose equality checks such as $request->get_method() === WP_REST_Server::READABLE or passes the constant directly to outbound HTTP requests. Consequently, these constants are effectively frozen and cannot gain new HTTP methods (such as RFC 10008 HTTP QUERY method from #65616) without breaking existing routes across the ecosystem.

This PR addresses the issue by introducing dedicated static accessor methods on WP_REST_Server that return sets of methods as string[]:

  • WP_REST_Server::get_readable_methods() -> array( 'GET' )
  • WP_REST_Server::get_creatable_methods() -> array( 'POST' )
  • WP_REST_Server::get_editable_methods() -> array( 'POST', 'PUT', 'PATCH' )
  • WP_REST_Server::get_deletable_methods() -> array( 'DELETE' )

Returning arrays establishes the correct type semantics (a set of HTTP methods is a list, preventing incorrect string equality checks like ===), allows the method sets to safely expand in future WordPress versions, and seamlessly integrates with register_rest_route(), which already natively accepts arrays of methods.

The legacy constants are soft-deprecated via @deprecated 7.3.0 docblocks without triggering runtime notices, keeping existing plugins 100% functional.

Changes

  1. Added static methods get_readable_methods(), get_creatable_methods(), get_editable_methods(), and get_deletable_methods() to WP_REST_Server.
  2. Added @deprecated 7.3.0 tags to READABLE, CREATABLE, EDITABLE, DELETABLE, and ALLMETHODS.
  3. Added dedicated unit tests covering method return values, constant equivalence, route registration, and request dispatching in tests/phpunit/tests/rest-api/wpRestServerGetMethods.php.

Testing Instructions

  1. Run the dedicated unit test suite:
    vendor/bin/phpunit tests/phpunit/tests/rest-api/wpRestServerGetMethods.php
    
  1. Verify route registration with the new methods:
    register_rest_route( 'my-plugin/v1', '/items', array(
           array(
               'methods'             => WP_REST_Server::get_readable_methods(),
               'callback'            => 'my_plugin_get_items',
               'permission_callback' => '__return_true',
           ),
       ) );
    
  1. Run PHPCS to ensure compliance:
    vendor/bin/phpcs src/wp-includes/rest-api/class-wp-rest-server.php tests/phpunit/tests/rest-api/wpRestServerGetMethods.php
    

Attribution

Props @moonmeister, @westonruter, @therssoftware.

---
AI Usage Disclosure:
Assisted by generative AI (Antigravity) to prepare the implementation, DocBlocks, and PHPUnit test suite in accordance with WordPress coding guidelines. All code and test coverage were reviewed and verified.

Note: See TracTickets for help on using tickets.