• Resolved malcolmdixon

    (@malcolmdixon)


    Hi folks,

    I need some advice on using Ajax with WordPress as I’ve read, watched and experimented with different techniques and I’m not clear on the best approach.

    I am new to WordPress development and web development in general, so I don’t want to fall into any pits later on, so any guidance would be appreciated.

    After watching a YouTube video https://www.youtube.com/watch?v=OwBBxwmG49w, it seems that the best appoach is to use the Rewrite API.

    So, I wrote code to create the necessary endpoints etc for the required small API, all GET requests.

      add_action('init', function () {
        add_rewrite_tag('%cpt_id%', '([0-9]+)');
        add_rewrite_tag('%cpt%', '([a-z]+)');
    
        add_rewrite_rule(self::API_BASE_URL . '([a-z]+)/([0-9]+)/?', 'index.php?cpt=$matches[1]&cpt_id=$matches[2]', 'top');
        add_rewrite_rule(self::API_BASE_URL . '([a-z]+)/?$', 'index.php?cpt=$matches[1]', 'top');
      });
    
      add_filter( 'query_vars', function ($vars) {
        array_push($vars, 'make', 'plug_type');
        return $vars;
      });

    This seems to work so far with the limited testing, allowing me to route depending on the custom post type but I do feel that I’m implementing a REST API that’s already available albeit that supplies much more data than necessary. Although you can use the query variable _fields to limit data. But then to get the featured image would be another call as far as I can tell.

    Someone said to use WP_Query object too but I’m far more comfortable using SQL directly on $wpdb, which is easy enough to do for the above:

    $results = $wpdb->get_results("SELECT posts.post_title, posts.ID, meta.meta_value AS img_src FROM $wpdb->posts AS posts LEFT JOIN $wpdb->postmeta ON post_id = posts.ID AND meta_key = '_thumbnail_id' LEFT JOIN $wpdb->posts AS attachment ON attachment.ID = meta_value LEFT JOIN $wpdb->postmeta as meta ON meta.post_id = attachment.ID AND meta.meta_key = '_wp_attached_file' WHERE posts.post_type = 'makes';", OBJECT_K);

    Probably should have done a subquery but the above works.

    So, my question is do I use:

    1) The WP REST API.

    2) The Rewrite API and $wpdb, which I have sort of working now.

    3) A PHP file using require_once(“wp-load.php”); and $wpdb called directly. This seems to work but I’m unsure about security.

    <?php
      require_once("wp-load.php");
    
      global $wpdb;
    
      $results = $wpdb->get_results("SELECT posts.post_title, posts.ID, meta.meta_value AS img_src FROM $wpdb->posts AS posts LEFT JOIN $wpdb->postmeta ON post_id = posts.ID AND meta_key = '_thumbnail_id' LEFT JOIN $wpdb->posts AS attachment ON attachment.ID = meta_value LEFT JOIN $wpdb->postmeta as meta ON meta.post_id = attachment.ID AND meta.meta_key = '_wp_attached_file' WHERE posts.post_type = 'makes';", OBJECT_K);
    
      $message = json_encode($results);
    
      wp_send_json_success([
        'message' => $message
      ]);
    ?>

    I haven’t even attempted to try the admin-ajax.php option due to the YouTube video showing how slow it is compared to the other options and from what I’ve “skim” read seems complex anyway.

    I appreciate anyone spending their time to advise me on this.

Viewing 15 replies - 1 through 15 (of 17 total)
  • What is the goal?
    If you don’t want to use the WordPress functionality, why bother making it a CPT?
    I definitely would veto writing SQL. How compatible with other plugins do you want this CPT to be?
    Do you want the theme to be able to handle it?
    The WP functions have been tested and have years of working. Why reinvent all that?

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi Joy,

    Thanks for the reply.

    The goal is to have a multi step form that could start at different steps, so depending on the starting step determines whether data needs to be loaded into the 3D rotating carousel I’ve developed. I don’t want the initial PHP file to create the DOM elements as they may not be needed, so the intention was depending on user selection an Ajax call will get the data to load the controls. Some steps will then have a value that is used to fetch data for the next step etc. I would prefer this to use Ajax than requesting another PHP file and refreshing the page.

    The CPTs are for site specific use only and will not be used elsewhere.

    I don’t understand what’s meant by the theme handling it! My knowledge is sadly lacking ATM. The 3 CPTs that will be needed are for back-end data to feed this form. To load data and images into controls and provide results dependent on user choices.

    I agree, I don’t want to reinvent the wheel, so I’ll take your advice onboard and avoid using SQL and use the WP_Query class instead. The thing is Joy I just don’t really know what I’m doing and I’m trying to find my way around ATM.

    But is it advisable to use the Rewrite API as shown? OR should I just use the REST API? OR call a single page as shown?

    Even though you gave some details, it really doesn’t answer the question of the goal.
    Is this for the front end of the site? Or is it for when you are editing the CPT?
    Is there just one page that has this script?
    Is the CPT public? (I assume so, since you are doing rewrites.) The thing about rewrites is that a bot can crawl all the variations of the URL. Should all of them have AJAX, and can anyone use it, or do you have to be logged in?

    The WP AJAX handling gives you access to all the functions of WP, but also it is separated into privileged and non-privileged, and you can check the user’s capabilities and the nonce using WP functions.

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi Joy,

    This is just a small part of the site whereby any unauthorized user can use the form to find a particular product or service related to the site. Every interaction will be READ ONLY.
    Only admins can CRUD the CPTs, the data is consumed by the Ajax calls in the form only.
    Yes, the intention is that it will be like a single page app for this part of the site.
    As far as I can tell I’m getting access to all of WP functions required providing wp-load.php has been required.
    My terminology and explanations may not be precise as I’m still in the learning phase.
    I hope that helps you understand what I’m trying to achieve, so that you can provide any useful tips. I really appreciate you taking time to respond, thank you Joy.

    I don’t know if the Plugin Handbook actually says not to use wp_load.php, but it is highly discouraged. One thing is because it is fragile regarding the configuration of the folders, since the content folder can be moved and WP could be in a folder instead of the root. I suppose that doesn’t really apply to your one-off site, but it’s why, and it does limit you for future changes.
    If you are loading WP anyway, you might as well use WP AJAX, and let it handle some things for you. There are a lot of places in WP that check if the context is AJAX, so it’s best if that is set correctly.

    Every interaction will be READ ONLY.

    This is the part of interest. In order to enforce this, you have to be very careful that your code can’t be invoked in a wrong way or the request modified by a hacker to do things it wasn’t intended for. Hackers look for query variables that can be set by the user that are then displayed in ways that they can exploit. You need to be concerned about SQL injection and scripting hacks.

    Thread Starter malcolmdixon

    (@malcolmdixon)

    So, the best option is to use the slower admin-ajax.php method but it will be more secure. Okay, I’ll check out more videos on this option and read in-depth rather than skimming!

    Thanks again for your replies.

    Moderator bcworkz

    (@bcworkz)

    The reason admin-ajax.php is slow is it has to start up the entire WP environment. If you need to use WP resources, it’s what you have to do. If you don’t need WP resources, it’s the wrong approach. I wouldn’t expect the REST API to be any different regarding performance.

    I didn’t thoroughly read this entire thread, so my comments may lack nuance. Use of the rewrite API is independent of your other choices, it’s not a choice like the others. It’s purpose is to let you use your own preferred URL structure if the default URLs WP uses are undesirable.

    REST API responses are always JSON. You generally need substantial client side code to parse the data and present it in some attractive way. Ajax responses can be almost anything you like, as long as it’s coordinated client side. Client side code could be as simple as assigning the return verbatim to an innerHTML property.

    Joy already said as much, but I’ll repeat for emphasis. Don’t require wp-load.php! It can cause portability issues and there are better ways to invoke the WP environment.

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi bcworkz, thanks for your input.

    Considering I need access to the WP_Query object since Joy recommends avoiding the use of SQL via $wpdb, I take it that I must use admin-ajax.php since it is a WP resource and you have both confirmed not to use require wp-load.php (which I have seen on a couple of YouTube channels).

    One purpose for using the Rewrite API as I understand it is that you can create your own endpoints and return only the data you require unlike the REST API.
    What are better ways to invoke the WP environment? Can you point me in a direction for further research? Thank you.

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi,

    Just read this article on https://10up.github.io/Engineering-Best-Practices/php/#performance

    AJAX Endpoints

    AJAX stands for Asynchronous JavaScript and XML. Often, we use JavaScript on the client-side to ping endpoints for things like infinite scroll.

    WordPress provides an API to register AJAX endpoints on wp-admin/admin-ajax.php. However, WordPress does not cache queries within the administration panel for obvious reasons. Therefore, if you send requests to an admin-ajax.php endpoint, you are bootstrapping WordPress and running un-cached queries. Used properly, this is totally fine. However, this can take down a website if used on the frontend.

    For this reason, front-facing endpoints should be written by using the Rewrite Rules API and hooking early into the WordPress request process.

    Here is a simple example of how to structure your endpoints:

    <?php
    /**
     * Register a rewrite endpoint for the API.
     */
    function prefix_add_api_endpoints() {
    	add_rewrite_tag( '%api_item_id%', '([0-9]+)' );
    	add_rewrite_rule( 'api/items/([0-9]+)/?', 'index.php?api_item_id=$matches[1]', 'top' );
    }
    add_action( 'init', 'prefix_add_api_endpoints' );
    
    /**
     * Handle data (maybe) passed to the API endpoint.
     */
    function prefix_do_api() {
    	global $wp_query;
    
    	$item_id = $wp_query->get( 'api_item_id' );
    
    	if ( ! empty( $item_id ) ) {
    		$response = array();
    
    		// Do stuff with $item_id
    
    		wp_send_json( $response );
    	}
    }
    add_action( 'template_redirect', 'prefix_do_api' );
    ?>

    This seems to go against using admin-ajax.php and favour the Rewrite API which is what I’ve done.

    Any comments?

    Moderator bcworkz

    (@bcworkz)

    OK, I see now. You’re essentially creating your own API through a template_redirect hook. Using the Rewrite API simply gets you there. Rewrite doesn’t get you any data in itself, but it’ll get you to where you can get data.

    By using an URL structure without a query string you can leverage any caching that might be in place. But if the endpoint argument is highly variable, it’s unlikely to be cached anyway and you could greatly enlarge cached data to little benefit. So in theory the approach sounds good, but in practice there could be much less of a benefit than it seems. Any benefit will depend on the specific situation in which it is used.

    If you want to leverage caching by using rewrites, that’s fine. It’s somewhat independent of how to actually get data. I’m not too keen on setting up an API through template_redirect only because nearly all front end page requests go through the hook. You don’t have to rewrite through index.php even though that’s most common. You could still rewrite through /wp-admin/admin-ajax.php. Or through /wp-admin/admin-post.php.

    Regarding wp-load.php — you saw something on the Internet, so it must be accurate??? πŸ˜› People can do that on their own site. It’s poor practice for code used by others and is cause for rejection in the WP repository.

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi bcworkz,

    Thanks again for your reply.

    Yes, essentially that’s what I’m doing as it’s supposed to be the “best” method as “confirmed” by the 10up article.

    The plan is to route the calls to the appropriate controller to retrieve the required data.

    So, by hooking into template_redirect will affect performance of other front end pages for probably slight gains in performance for the one page that will use the custom endpoints.

    By using an URL structure without a query string you can leverage any caching that might be in place
    I was going to be using 2 query parameters as a way to filter some results, but from your statement I assume these would not be cached? So, instead of using say api/v1/models?make=id, I should create an endpoint api/v1/makes/id/models to retrieve all models of a particular make in order to make it cacheable?

    LOL, I don’t believe anything on the Internet, I’d rather test things myself but there’s a lot of much cleverer people than me out there that I hoped would guide me. You’re one of them, so thanks πŸ™‚

    Best regards, Malcolm.

    Moderator bcworkz

    (@bcworkz)

    I suppose it depends on your caching scheme. I’m assuming URLs with query strings aren’t cached, otherwise the entire idea of using Rewrite API is invalid. But to follow that concept to its logical conclusion, then yes, include all parameters in the URL structure and avoid query strings if you want the page to be cached for better performance. api/v1/makes/id/models/ would be the way to achieve this.

    Is there a reason for creating your own API instead of creating a custom route/endpoint through the existing REST API? You may do so if you wish, but utilizing the REST API infrastructure would be more “WordPressy”. Just sayin’.

    I knew you don’t believe everything you see or you wouldn’t be here seeking confirmation. Use of wp-load.php is sort of a pet peeve so I couldn’t resist poking a little fun πŸ™‚

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Thanks yet again for replying.

    Firstly, glad you had a little fun at my expense, no problem gotta have a laugh πŸ™‚

    Like I’ve said PHP and WordPress development is all very new so it would be stupid of me not to get confirmation, so thanks to you and Joy for sharing your wisdom.

    The reason for creating my own API was due to the YouTube video by WPCasts that showed that the Rewrite Rules API was fastest and that the Rest API can become slower in time due to the amount of plugins that hook into it etc.

    When I discovered that WordPress had a Rest API, I thought great I’ll use that but my acquaintance that I’m helping out with this seemed to be against the idea too, can’t remember why though, so I didn’t explore this avenue further. I did learn that it returns lots of unnecessary data but I know you can filter to a certain extent. Based on your advice I should probably investigate this further even though I have the code more or less working using the Rewrite API.

    Don’t worry you’ve got a convert here, I won’t use require_once(‘wp-load.php’); ever. It’s drummed in now lol.

    Best regards,
    Malcolm.

    Moderator bcworkz

    (@bcworkz)

    In theory a good number of REST API requests are as cache-able as those to a custom API. Either way, real world performance may not be as great as some think due to the cache not being adequately primed. A well designed custom REST API route ought to be just as performative as a custom API.

    At the very least, I’d rewrite to admin-post.php or admin-ajax.php instead of to index.php and hooking “template_redirect”. Only because the action you then hook only fires when appropriate instead of on every request involving templates. More of a structural preference than any real performance gain.

    There’s also an argument for if it works, don’t fix it πŸ™‚

    Thread Starter malcolmdixon

    (@malcolmdixon)

    Hi,

    I tried admin-ajax.php and admin-post.php instead of index.php but they didn’t work so I just changed it back! Thanks anyway. I guess being admin*.php it’s something to do with lack of permission, although I was logged in at the time!

    Best regards,

    Malcolm.

    • This reply was modified 2 years, 11 months ago by malcolmdixon.
Viewing 15 replies - 1 through 15 (of 17 total)
  • The topic ‘Ajax best approach’ is closed to new replies.