Make WordPress Core

Opened 9 months ago

Closed 7 months ago

Last modified 4 months ago

#61739 closed enhancement (fixed)

REST API: Implement 'targetHints' property

Reported by: mamaduka's profile Mamaduka Owned by: timothyblynjacobs's profile TimothyBlynJacobs
Milestone: 6.7 Priority: normal
Severity: normal Version:
Component: REST API Keywords: has-patch has-unit-tests
Focuses: rest-api Cc:

Description

A proposal to add the targetHints property to the link description object as part of the self link. The current ticket will only cover the allow header reference.

Originally suggested in https://core.trac.wordpress.org/ticket/50388#comment:6.

Why?

Consumers who render a list of posts and CRUD actions related to each item must make a separate OPTIONS request to check if the user has permission to perform these actions.

The N+1 requests affect the performance on the client and the server.

Example targetHints property:

  {
    "_links": {
        "self": [
            {
                "href": "https://api.w.org/wp-json/wp-json/wp/v2/pages/1",
                "targetHints": {
                    "allow": [ "GET", "POST", "PUT", "DELETE" ]
                }
            }
        ]
    }
}

Change History (12)

#1 @Mamaduka
9 months ago

  • Keywords needs-patch needs-unit-tests added

Sharing suggestions by @TimothyBlynJacobs, during our 1-on-1 chat:

I think the big question is whether this should be done controller by controller, or if we can
handle it more at a server level.

I think it’s worth looking at writing something similar to rest_send_allow_header, but
implementing it as “middleware” attached to rest_request_after_callbacks. Where we’d check if
the item or is an array of items with links, and then merge into it a targetHints definition.

That way we don’t have to add mostly boiler plate code to every controller, and everything get’s
it for free if they implement their API properly.

In this case, we shouldn’t need to dive into that level. We should be able to operate on a
higher level of abstraction. What rest_send_allow_header does is it calls the appropriate
permission_callback functions.

#2 @swissspidy
9 months ago

Previously: #50388

This ticket was mentioned in Slack in #core-editor by mamaduka. View the logs.


9 months ago

This ticket was mentioned in PR #7139 on WordPress/wordpress-develop by @TimothyBlynJacobs.


9 months ago
#4

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

This needs to be refactored to be less ugly, and handle errors from WP_REST_Request::from_url. But this is my thinking on how we could automatically add a targetHint.

Trac ticket: [](https://core.trac.wordpress.org/ticket/61739)

#5 @Mamaduka
8 months ago

  • Milestone changed from Awaiting Review to 6.7

This needs to be in the WP 6.7 milestone.

Here's the improvement we're shipping on the editor side - https://github.com/WordPress/gutenberg/pull/64504.

@noisysocks commented on PR #7139:


8 months ago
#6

@TimothyBJacobs: How's this going? It's marked as a backport for 6.7. Beta 1 is 4 weeks and I'll sync packages in just over 2 weeks.

@TimothyBlynJacobs commented on PR #7139:


8 months ago
#7

Refreshed with the latest changes from GB. I'll leave it open a few days for feedback.

#8 @TimothyBlynJacobs
7 months ago

  • Owner set to TimothyBlynJacobs
  • Resolution set to fixed
  • Status changed from new to closed

In 59032:

REST API: Automatically populate targetHints for the Allow header.

The REST API uses the "Allow" header to communicate what methods a user is authorized to perform on a resource. This works great when operating on a single item route, but can break down when needing to determine authorization over a collection of items.

This commit uses the "targetHints" property of JSON Hyper Schema to provide access to the "allow" header for "self" links. This alleviates needing to make a separate network request for each item in a collection.

Props mamaduka, noisysocks, peterwilsoncc, spacedmonkey, swissspidy, timothyblynjacobs, tyxla, youknowriad.
Fixes #61739.

@TimothyBlynJacobs commented on PR #7139:


7 months ago
#9

Merged in r59032.

@freibergergarcia commented on PR #7139:


4 months ago
#10

@TimothyBJacobs @swissspidy I would like to validate something about this change.

The change impacts performance depending on the number of users, posts, and how you hit the users endpoint.

When rest_send_allow_header is called from get_target_hints_for_link it ends up calling the user's permission callback: https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php#L458 and count_user_posts( $user->ID, $types ) for each user.

Depending on the number of $types and data (users, posts), this call can be expensive. An example could be: /wp-json/wp/v2/users?per_page=x&pagination=true&page=1. The higher per_page is set, the worse it performs.

Questions:

  • what's the best way to "disable" it?
  • is there a different way to implement this without having to call count_user_posts
  • have you considered the performance implications?
  • does it need to be "enabled" by default?

One alternative to work around this is the following. Do you think it's the most appropriate way?

/**
 * Add targetHints to the self-links in the user response to prevent COUNT slow queries.
 * 
 * @see get_target_hints_for_link core method in WordPress version 6.7
 */
add_filter( 'rest_prepare_user', function( $response, $user, $request ) {
        $links = $response->get_links();
        if ( empty( $links ) ) {
                return $response;
        }

        $response->remove_link('self');

        foreach( $links as $rel => $set ) {
                if ( 'self' !== $rel ) {
                        continue;
                }

                foreach( $set as $link ) {
                        if ( ! isset( $link['href'] ) ) {
                                continue;
                        }

                        $response->add_link( 
                                $rel,
                                $link['href'],
                                array_merge( 
                                        $link['attributes'] ?? [], 
                                        [
                                                'targetHints' => [
                                                        'allow' => [],
                                                ]
                                        ] 
                                ) 
                        );
                }
        }

        return $response;
}, 10, 3 );

I'm looking forward to hearing your thoughts on this.

@swissspidy commented on PR #7139:


4 months ago
#11

Could you perhaps open a Trac ticket to increase visibility of this?

Would you say that caching count_user_posts would mostly solve this? There's a ticket for that which would be nice to land: https://core.trac.wordpress.org/ticket/39242

@swissspidy commented on PR #7139:


4 months ago
#12

Actually, looks like https://core.trac.wordpress.org/ticket/46249 seems to basically have covered this case, but was pointed to https://core.trac.wordpress.org/ticket/39242 as well.. So yeah, maybe let's focus on the caching?

Note: See TracTickets for help on using tickets.