Research-mind wordpress-advanced-architecture
Advanced WordPress development with REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable applications.
install
source · Clone the upstream repo
git clone https://github.com/MacPhobos/research-mind
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/MacPhobos/research-mind "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/toolchains-php-frameworks-wordpress-advanced-architecture" ~/.claude/skills/macphobos-research-mind-wordpress-advanced-architecture && rm -rf "$T"
manifest:
.claude/skills/toolchains-php-frameworks-wordpress-advanced-architecture/SKILL.mdsource content
Advanced WordPress Architecture
Master advanced WordPress development patterns including REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable WordPress applications.
1. REST API Development
The WordPress REST API provides a powerful interface for creating custom endpoints with proper authentication, validation, and response formatting.
Endpoint Registration with Namespacing
add_action( 'rest_api_init', 'register_custom_rest_routes' ); function register_custom_rest_routes() { // Namespace: myplugin/v1 (enables versioning) $namespace = 'myplugin/v1'; // GET /wp-json/myplugin/v1/books register_rest_route( $namespace, '/books', [ 'methods' => 'GET', 'callback' => 'get_books_callback', 'permission_callback' => '__return_true', // Public endpoint 'args' => [ 'per_page' => [ 'default' => 10, 'validate_callback' => function( $param ) { return is_numeric( $param ) && $param > 0 && $param <= 100; }, 'sanitize_callback' => 'absint', ], 'page' => [ 'default' => 1, 'validate_callback' => function( $param ) { return is_numeric( $param ) && $param > 0; }, 'sanitize_callback' => 'absint', ], ], ]); // GET /wp-json/myplugin/v1/books/(?P<id>\d+) register_rest_route( $namespace, '/books/(?P<id>\d+)', [ 'methods' => 'GET', 'callback' => 'get_book_callback', 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'validate_callback' => function( $param ) { return is_numeric( $param ); }, 'sanitize_callback' => 'absint', ], ], ]); // POST /wp-json/myplugin/v1/books (authenticated) register_rest_route( $namespace, '/books', [ 'methods' => 'POST', 'callback' => 'create_book_callback', 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, 'args' => [ 'title' => [ 'required' => true, 'type' => 'string', 'validate_callback' => function( $param ) { return is_string( $param ) && strlen( $param ) > 0; }, 'sanitize_callback' => 'sanitize_text_field', ], 'content' => [ 'required' => false, 'type' => 'string', 'sanitize_callback' => 'wp_kses_post', ], 'status' => [ 'default' => 'draft', 'enum' => [ 'draft', 'publish', 'private' ], ], ], ]); // PUT /wp-json/myplugin/v1/books/(?P<id>\d+) register_rest_route( $namespace, '/books/(?P<id>\d+)', [ 'methods' => 'PUT', 'callback' => 'update_book_callback', 'permission_callback' => function( $request ) { $book_id = $request->get_param( 'id' ); return current_user_can( 'edit_post', $book_id ); }, 'args' => [ 'id' => [ 'validate_callback' => function( $param ) { return is_numeric( $param ); }, 'sanitize_callback' => 'absint', ], 'title' => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], 'content' => [ 'type' => 'string', 'sanitize_callback' => 'wp_kses_post', ], ], ]); // DELETE /wp-json/myplugin/v1/books/(?P<id>\d+) register_rest_route( $namespace, '/books/(?P<id>\d+)', [ 'methods' => 'DELETE', 'callback' => 'delete_book_callback', 'permission_callback' => function( $request ) { $book_id = $request->get_param( 'id' ); return current_user_can( 'delete_post', $book_id ); }, 'args' => [ 'id' => [ 'validate_callback' => function( $param ) { return is_numeric( $param ); }, 'sanitize_callback' => 'absint', ], ], ]); }
Complete CRUD Implementation
// GET /wp-json/myplugin/v1/books function get_books_callback( $request ) { $per_page = $request->get_param( 'per_page' ); $page = $request->get_param( 'page' ); $offset = ( $page - 1 ) * $per_page; $args = [ 'post_type' => 'book', 'posts_per_page' => $per_page, 'offset' => $offset, 'post_status' => 'publish', ]; $query = new WP_Query( $args ); if ( ! $query->have_posts() ) { return rest_ensure_response([ 'books' => [], 'total' => 0, 'page' => $page, 'per_page' => $per_page, ]); } $books = []; while ( $query->have_posts() ) { $query->the_post(); $books[] = [ 'id' => get_the_ID(), 'title' => get_the_title(), 'content' => get_the_content(), 'author' => get_the_author(), 'date' => get_the_date( 'c' ), // ISO 8601 format 'link' => get_permalink(), ]; } wp_reset_postdata(); $response = rest_ensure_response([ 'books' => $books, 'total' => $query->found_posts, 'page' => $page, 'per_page' => $per_page, 'total_pages' => ceil( $query->found_posts / $per_page ), ]); // Add HATEOAS links $response->add_link( 'self', rest_url( "myplugin/v1/books?page={$page}&per_page={$per_page}" ) ); if ( $page > 1 ) { $prev_page = $page - 1; $response->add_link( 'prev', rest_url( "myplugin/v1/books?page={$prev_page}&per_page={$per_page}" ) ); } if ( $page < ceil( $query->found_posts / $per_page ) ) { $next_page = $page + 1; $response->add_link( 'next', rest_url( "myplugin/v1/books?page={$next_page}&per_page={$per_page}" ) ); } return $response; } // GET /wp-json/myplugin/v1/books/123 function get_book_callback( $request ) { $book_id = $request->get_param( 'id' ); $book = get_post( $book_id ); if ( ! $book || 'book' !== $book->post_type ) { return new WP_Error( 'book_not_found', 'Book not found', [ 'status' => 404 ] ); } $data = [ 'id' => $book->ID, 'title' => $book->post_title, 'content' => apply_filters( 'the_content', $book->post_content ), 'excerpt' => $book->post_excerpt, 'author' => get_the_author_meta( 'display_name', $book->post_author ), 'date' => get_the_date( 'c', $book ), 'modified' => get_the_modified_date( 'c', $book ), 'status' => $book->post_status, 'link' => get_permalink( $book ), 'featured_image' => get_the_post_thumbnail_url( $book, 'large' ), 'meta' => [ 'isbn' => get_post_meta( $book->ID, '_isbn', true ), 'pages' => (int) get_post_meta( $book->ID, '_pages', true ), ], ]; return rest_ensure_response( $data ); } // POST /wp-json/myplugin/v1/books function create_book_callback( $request ) { $title = $request->get_param( 'title' ); $content = $request->get_param( 'content' ); $status = $request->get_param( 'status' ); $post_data = [ 'post_type' => 'book', 'post_title' => $title, 'post_content' => $content, 'post_status' => $status, 'post_author' => get_current_user_id(), ]; $book_id = wp_insert_post( $post_data, true ); if ( is_wp_error( $book_id ) ) { return new WP_Error( 'book_creation_failed', $book_id->get_error_message(), [ 'status' => 500 ] ); } // Add custom metadata if provided if ( $request->has_param( 'isbn' ) ) { update_post_meta( $book_id, '_isbn', sanitize_text_field( $request->get_param( 'isbn' ) ) ); } if ( $request->has_param( 'pages' ) ) { update_post_meta( $book_id, '_pages', absint( $request->get_param( 'pages' ) ) ); } $response = rest_ensure_response([ 'id' => $book_id, 'title' => $title, 'message' => 'Book created successfully', 'link' => get_permalink( $book_id ), ]); $response->set_status( 201 ); // Created $response->header( 'Location', rest_url( "myplugin/v1/books/{$book_id}" ) ); return $response; } // PUT /wp-json/myplugin/v1/books/123 function update_book_callback( $request ) { $book_id = $request->get_param( 'id' ); $book = get_post( $book_id ); if ( ! $book || 'book' !== $book->post_type ) { return new WP_Error( 'book_not_found', 'Book not found', [ 'status' => 404 ] ); } $post_data = [ 'ID' => $book_id ]; if ( $request->has_param( 'title' ) ) { $post_data['post_title'] = $request->get_param( 'title' ); } if ( $request->has_param( 'content' ) ) { $post_data['post_content'] = $request->get_param( 'content' ); } if ( $request->has_param( 'status' ) ) { $post_data['post_status'] = $request->get_param( 'status' ); } $result = wp_update_post( $post_data, true ); if ( is_wp_error( $result ) ) { return new WP_Error( 'book_update_failed', $result->get_error_message(), [ 'status' => 500 ] ); } return rest_ensure_response([ 'id' => $book_id, 'message' => 'Book updated successfully', 'link' => get_permalink( $book_id ), ]); } // DELETE /wp-json/myplugin/v1/books/123 function delete_book_callback( $request ) { $book_id = $request->get_param( 'id' ); $book = get_post( $book_id ); if ( ! $book || 'book' !== $book->post_type ) { return new WP_Error( 'book_not_found', 'Book not found', [ 'status' => 404 ] ); } // Soft delete (trash) or hard delete $force = $request->get_param( 'force' ); $result = wp_delete_post( $book_id, $force ); if ( ! $result ) { return new WP_Error( 'book_deletion_failed', 'Failed to delete book', [ 'status' => 500 ] ); } return rest_ensure_response([ 'deleted' => true, 'id' => $book_id, 'message' => $force ? 'Book permanently deleted' : 'Book moved to trash', ]); }
Controller Pattern for Complex Endpoints
For complex REST endpoints, use a controller class to organize logic:
<?php namespace MyPlugin\API; class Books_Controller extends \WP_REST_Controller { protected $namespace = 'myplugin/v1'; protected $rest_base = 'books'; public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_items' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], 'args' => $this->get_collection_params(), ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'create_item' ], 'permission_callback' => [ $this, 'create_item_permissions_check' ], 'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ], 'schema' => [ $this, 'get_public_item_schema' ], ]); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [ 'args' => [ 'id' => [ 'description' => 'Unique identifier for the book', 'type' => 'integer', ], ], [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_item' ], 'permission_callback' => [ $this, 'get_item_permissions_check' ], 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_item' ], 'permission_callback' => [ $this, 'update_item_permissions_check' ], 'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'delete_item' ], 'permission_callback' => [ $this, 'delete_item_permissions_check' ], 'args' => [ 'force' => [ 'type' => 'boolean', 'default' => false, 'description' => 'Whether to bypass trash and force deletion', ], ], ], 'schema' => [ $this, 'get_public_item_schema' ], ]); } public function get_items( $request ) { // Implementation similar to get_books_callback above } public function get_item( $request ) { // Implementation similar to get_book_callback above } public function create_item( $request ) { // Implementation similar to create_book_callback above } public function update_item( $request ) { // Implementation similar to update_book_callback above } public function delete_item( $request ) { // Implementation similar to delete_book_callback above } public function get_items_permissions_check( $request ) { return true; // Public endpoint } public function get_item_permissions_check( $request ) { return true; // Public endpoint } public function create_item_permissions_check( $request ) { return current_user_can( 'edit_posts' ); } public function update_item_permissions_check( $request ) { $book_id = $request->get_param( 'id' ); return current_user_can( 'edit_post', $book_id ); } public function delete_item_permissions_check( $request ) { $book_id = $request->get_param( 'id' ); return current_user_can( 'delete_post', $book_id ); } public function get_public_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = [ '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'book', 'type' => 'object', 'properties' => [ 'id' => [ 'description' => 'Unique identifier for the book', 'type' => 'integer', 'context' => [ 'view', 'edit', 'embed' ], 'readonly' => true, ], 'title' => [ 'description' => 'The book title', 'type' => 'string', 'context' => [ 'view', 'edit', 'embed' ], 'required' => true, ], 'content' => [ 'description' => 'The book content', 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'status' => [ 'description' => 'The book status', 'type' => 'string', 'enum' => [ 'draft', 'publish', 'private' ], 'context' => [ 'view', 'edit' ], ], ], ]; $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } // Register controller add_action( 'rest_api_init', function() { $controller = new \MyPlugin\API\Books_Controller(); $controller->register_routes(); });
REST API Authentication
// Application Passwords (WordPress 5.6+) // Users generate passwords at /wp-admin/profile.php // Example cURL request with authentication: // curl -X POST https://example.com/wp-json/myplugin/v1/books \ // -u username:application_password \ // -H "Content-Type: application/json" \ // -d '{"title":"New Book","content":"Book content"}' // Cookie authentication for logged-in users (requires nonce) add_action( 'rest_api_init', function() { // Add nonce to wp_localize_script wp_localize_script( 'my-ajax-script', 'wpApiSettings', [ 'root' => esc_url_raw( rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ), ]); }); // JavaScript REST API call with nonce: /* fetch( wpApiSettings.root + 'myplugin/v1/books', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce }, body: JSON.stringify({ title: 'New Book', content: 'Book content' }) }).then( response => response.json() ) .then( data => console.log( data ) ); */
2. WP-CLI Commands
WP-CLI enables automation of WordPress tasks through custom commands.
Custom Command Registration
<?php namespace MyPlugin\CLI; class Books_Command { /** * List all books. * * ## OPTIONS * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * [--status=<status>] * : Filter by post status. * * ## EXAMPLES * * wp books list * wp books list --format=json * wp books list --status=publish * * @when after_wp_load */ public function list( $args, $assoc_args ) { $defaults = [ 'format' => 'table', 'status' => 'any', ]; $assoc_args = wp_parse_args( $assoc_args, $defaults ); $query_args = [ 'post_type' => 'book', 'posts_per_page' => -1, 'post_status' => $assoc_args['status'], ]; $books = get_posts( $query_args ); if ( empty( $books ) ) { \WP_CLI::warning( 'No books found.' ); return; } $items = []; foreach ( $books as $book ) { $items[] = [ 'ID' => $book->ID, 'Title' => $book->post_title, 'Status' => $book->post_status, 'Author' => get_the_author_meta( 'display_name', $book->post_author ), 'Date' => get_the_date( 'Y-m-d H:i:s', $book ), ]; } \WP_CLI\Utils\format_items( $assoc_args['format'], $items, [ 'ID', 'Title', 'Status', 'Author', 'Date' ] ); \WP_CLI::success( sprintf( 'Found %d books.', count( $items ) ) ); } /** * Create a new book. * * ## OPTIONS * * <title> * : The book title. * * [--content=<content>] * : The book content. * * [--status=<status>] * : The book status. * --- * default: draft * options: * - draft * - publish * - private * --- * * [--isbn=<isbn>] * : The book ISBN. * * [--pages=<pages>] * : Number of pages. * * ## EXAMPLES * * wp books create "My Book Title" * wp books create "My Book" --status=publish --isbn=978-3-16-148410-0 --pages=350 * * @when after_wp_load */ public function create( $args, $assoc_args ) { $title = $args[0]; $defaults = [ 'content' => '', 'status' => 'draft', ]; $assoc_args = wp_parse_args( $assoc_args, $defaults ); $post_data = [ 'post_type' => 'book', 'post_title' => $title, 'post_content' => $assoc_args['content'], 'post_status' => $assoc_args['status'], 'post_author' => get_current_user_id(), ]; $book_id = wp_insert_post( $post_data, true ); if ( is_wp_error( $book_id ) ) { \WP_CLI::error( 'Failed to create book: ' . $book_id->get_error_message() ); } // Add custom metadata if ( isset( $assoc_args['isbn'] ) ) { update_post_meta( $book_id, '_isbn', sanitize_text_field( $assoc_args['isbn'] ) ); } if ( isset( $assoc_args['pages'] ) ) { update_post_meta( $book_id, '_pages', absint( $assoc_args['pages'] ) ); } \WP_CLI::success( sprintf( 'Created book #%d: %s', $book_id, $title ) ); } /** * Import books from CSV file. * * ## OPTIONS * * <file> * : Path to CSV file. * * [--dry-run] * : Preview import without creating books. * * ## EXAMPLES * * wp books import books.csv * wp books import books.csv --dry-run * * @when after_wp_load */ public function import( $args, $assoc_args ) { $file = $args[0]; $dry_run = isset( $assoc_args['dry-run'] ); if ( ! file_exists( $file ) ) { \WP_CLI::error( 'File not found: ' . $file ); } $csv = array_map( 'str_getcsv', file( $file ) ); $header = array_shift( $csv ); $total = count( $csv ); $created = 0; \WP_CLI::log( sprintf( 'Processing %d books...', $total ) ); $progress = \WP_CLI\Utils\make_progress_bar( 'Importing books', $total ); foreach ( $csv as $row ) { $book = array_combine( $header, $row ); if ( $dry_run ) { \WP_CLI::log( sprintf( 'Would create: %s', $book['title'] ) ); } else { $post_data = [ 'post_type' => 'book', 'post_title' => $book['title'], 'post_content' => $book['content'] ?? '', 'post_status' => $book['status'] ?? 'draft', ]; $book_id = wp_insert_post( $post_data, true ); if ( ! is_wp_error( $book_id ) ) { if ( isset( $book['isbn'] ) ) { update_post_meta( $book_id, '_isbn', $book['isbn'] ); } $created++; } } $progress->tick(); } $progress->finish(); if ( $dry_run ) { \WP_CLI::success( sprintf( 'Dry run complete. Would create %d books.', $total ) ); } else { \WP_CLI::success( sprintf( 'Imported %d of %d books.', $created, $total ) ); } } /** * Generate sample books. * * ## OPTIONS * * [--count=<count>] * : Number of books to generate. * --- * default: 10 * --- * * [--status=<status>] * : Book status. * --- * default: publish * --- * * ## EXAMPLES * * wp books generate --count=50 * wp books generate --count=100 --status=draft * * @when after_wp_load */ public function generate( $args, $assoc_args ) { $count = isset( $assoc_args['count'] ) ? absint( $assoc_args['count'] ) : 10; $status = isset( $assoc_args['status'] ) ? $assoc_args['status'] : 'publish'; $progress = \WP_CLI\Utils\make_progress_bar( 'Generating books', $count ); for ( $i = 1; $i <= $count; $i++ ) { $post_data = [ 'post_type' => 'book', 'post_title' => sprintf( 'Sample Book %d', $i ), 'post_content' => sprintf( 'This is sample book number %d.', $i ), 'post_status' => $status, ]; $book_id = wp_insert_post( $post_data ); // Add random metadata update_post_meta( $book_id, '_isbn', sprintf( '978-3-16-%06d-0', rand( 100000, 999999 ) ) ); update_post_meta( $book_id, '_pages', rand( 100, 500 ) ); $progress->tick(); } $progress->finish(); \WP_CLI::success( sprintf( 'Generated %d books.', $count ) ); } } // Register WP-CLI command if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'books', 'MyPlugin\CLI\Books_Command' ); }
Interactive Prompts and Confirmation
/** * Delete all books (with confirmation). * * ## OPTIONS * * [--yes] * : Skip confirmation prompt. * * ## EXAMPLES * * wp books delete-all * wp books delete-all --yes * * @when after_wp_load */ public function delete_all( $args, $assoc_args ) { $books = get_posts([ 'post_type' => 'book', 'posts_per_page' => -1, 'fields' => 'ids', ]); $count = count( $books ); if ( 0 === $count ) { \WP_CLI::warning( 'No books to delete.' ); return; } // Prompt for confirmation unless --yes flag is provided \WP_CLI::confirm( sprintf( 'Are you sure you want to delete %d books?', $count ), $assoc_args ); $progress = \WP_CLI\Utils\make_progress_bar( 'Deleting books', $count ); foreach ( $books as $book_id ) { wp_delete_post( $book_id, true ); // Force delete $progress->tick(); } $progress->finish(); \WP_CLI::success( sprintf( 'Deleted %d books.', $count ) ); }
Testing WP-CLI Commands
# List all WP-CLI commands wp cli command-list # Get help for custom command wp help books wp help books create # Test commands wp books list wp books list --format=json wp books create "Test Book" --status=publish wp books generate --count=50 wp books import books.csv --dry-run wp books delete-all --yes
3. Performance Optimization
Transients API (Expiring Cache)
// Store data with expiration function get_popular_books() { $transient_key = 'popular_books'; // Try to get cached value $popular_books = get_transient( $transient_key ); if ( false === $popular_books ) { // Cache miss - fetch and store $popular_books = new WP_Query([ 'post_type' => 'book', 'posts_per_page' => 10, 'meta_key' => '_view_count', 'orderby' => 'meta_value_num', 'order' => 'DESC', ]); // Cache for 1 hour (3600 seconds) set_transient( $transient_key, $popular_books, HOUR_IN_SECONDS ); } return $popular_books; } // Invalidate cache on post update add_action( 'save_post_book', 'invalidate_books_cache' ); function invalidate_books_cache( $post_id ) { delete_transient( 'popular_books' ); } // Site-specific transients (multisite) set_site_transient( 'network_data', $data, DAY_IN_SECONDS ); $data = get_site_transient( 'network_data' ); delete_site_transient( 'network_data' ); // Clear all transients (cleanup) global $wpdb; $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '%_transient_%'" );
Object Caching (Redis, Memcached)
// WordPress Object Cache functions (wp_cache_*) // Works with persistent cache (Redis/Memcached) if configured // Add to cache wp_cache_add( 'my_key', $data, 'my_group', 3600 ); // Get from cache $data = wp_cache_get( 'my_key', 'my_group' ); if ( false === $data ) { // Cache miss - fetch and cache $data = expensive_database_query(); wp_cache_set( 'my_key', $data, 'my_group', 3600 ); } // Delete from cache wp_cache_delete( 'my_key', 'my_group' ); // Flush entire cache wp_cache_flush(); // Example: Cache user data function get_user_books( $user_id ) { $cache_key = "user_{$user_id}_books"; $cache_group = 'user_books'; $books = wp_cache_get( $cache_key, $cache_group ); if ( false === $books ) { $books = get_posts([ 'post_type' => 'book', 'author' => $user_id, 'posts_per_page' => -1, ]); wp_cache_set( $cache_key, $books, $cache_group, HOUR_IN_SECONDS ); } return $books; } // Invalidate user cache on book update add_action( 'save_post_book', 'invalidate_user_books_cache', 10, 2 ); function invalidate_user_books_cache( $post_id, $post ) { wp_cache_delete( "user_{$post->post_author}_books", 'user_books' ); }
Redis Configuration (object-cache.php)
// Install Redis plugin or drop-in // Recommended: https://wordpress.org/plugins/redis-cache/ // Or manual configuration: // wp-content/object-cache.php <?php // Redis configuration global $redis_server; $redis_server = [ 'host' => '127.0.0.1', 'port' => 6379, 'auth' => '', // Password if required 'database' => 0, // Redis database number ]; // wp-config.php settings define( 'WP_REDIS_HOST', '127.0.0.1' ); define( 'WP_REDIS_PORT', 6379 ); define( 'WP_REDIS_DATABASE', 0 ); define( 'WP_REDIS_TIMEOUT', 1 ); define( 'WP_REDIS_READ_TIMEOUT', 1 );
Database Query Optimization
// BAD: Multiple queries in loop $posts = get_posts([ 'post_type' => 'book', 'posts_per_page' => -1 ]); foreach ( $posts as $post ) { $author = get_user_by( 'id', $post->post_author ); // N+1 query problem $meta = get_post_meta( $post->ID, '_isbn', true ); // Another query per iteration } // GOOD: Pre-fetch data $posts = get_posts([ 'post_type' => 'book', 'posts_per_page' => -1 ]); $author_ids = wp_list_pluck( $posts, 'post_author' ); $authors = get_users([ 'include' => $author_ids ]); // Single query $authors_by_id = []; foreach ( $authors as $author ) { $authors_by_id[ $author->ID ] = $author; } // Pre-load all meta with update_meta_cache() update_post_caches( $posts, 'book' ); foreach ( $posts as $post ) { $author = $authors_by_id[ $post->post_author ]; $isbn = get_post_meta( $post->ID, '_isbn', true ); // Uses cache } // Custom queries with JOIN global $wpdb; $results = $wpdb->get_results(" SELECT p.*, pm.meta_value as isbn FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_isbn' WHERE p.post_type = 'book' AND p.post_status = 'publish' ORDER BY p.post_date DESC LIMIT 10 "); // Add database indexes for frequently queried meta global $wpdb; $wpdb->query(" CREATE INDEX meta_key_value ON {$wpdb->postmeta} (meta_key, meta_value(20)) ");
Lazy Loading and Pagination
// Paginate large queries function get_books_paginated( $page = 1, $per_page = 20 ) { $args = [ 'post_type' => 'book', 'posts_per_page' => $per_page, 'paged' => $page, ]; return new WP_Query( $args ); } // Infinite scroll with AJAX add_action( 'wp_ajax_load_more_books', 'ajax_load_more_books' ); add_action( 'wp_ajax_nopriv_load_more_books', 'ajax_load_more_books' ); function ajax_load_more_books() { check_ajax_referer( 'load_more_nonce', 'nonce' ); $page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; $query = get_books_paginated( $page, 10 ); if ( $query->have_posts() ) { ob_start(); while ( $query->have_posts() ) { $query->the_post(); get_template_part( 'template-parts/content', 'book' ); } $html = ob_get_clean(); wp_send_json_success([ 'html' => $html, 'has_more' => $query->max_num_pages > $page, ]); } else { wp_send_json_error( 'No more books' ); } } // Lazy load images (native HTML) <img src="book-cover.jpg" loading="lazy" alt="Book Cover">
Profiling with Query Monitor
// Install Query Monitor plugin // https://wordpress.org/plugins/query-monitor/ // Add custom timing measurements do_action( 'qm/start', 'my_expensive_operation' ); // ... expensive code ... do_action( 'qm/stop', 'my_expensive_operation' ); // Log custom data do_action( 'qm/debug', 'Custom debug message' ); do_action( 'qm/info', $data_to_inspect ); // Benchmark queries Query Monitor shows: // - SQL queries (count, time, duplicates) // - HTTP requests // - Hooks and actions // - PHP errors and notices // - Template file hierarchy // - Enqueued scripts/styles
4. Caching Strategies
Fragment Caching
// Cache HTML fragments function render_book_grid() { $cache_key = 'book_grid_html'; $html = get_transient( $cache_key ); if ( false === $html ) { ob_start(); $books = new WP_Query([ 'post_type' => 'book', 'posts_per_page' => 12, ]); if ( $books->have_posts() ) { echo '<div class="book-grid">'; while ( $books->have_posts() ) { $books->the_post(); ?> <div class="book-item"> <h3><?php the_title(); ?></h3> <?php the_post_thumbnail( 'medium' ); ?> </div> <?php } echo '</div>'; } wp_reset_postdata(); $html = ob_get_clean(); set_transient( $cache_key, $html, HOUR_IN_SECONDS ); } echo $html; }
Page Caching vs Object Caching
/** * Page Caching: * - Caches entire HTML pages * - Fastest (serves static HTML) * - Plugins: WP Super Cache, W3 Total Cache, WP Rocket * - Bypassed for logged-in users * * Object Caching: * - Caches database queries and computed values * - Works for all users (logged-in and anonymous) * - Requires persistent cache backend (Redis, Memcached) * - Granular cache control */ // Example: Bypass page cache for dynamic content header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); // Cache-Control headers for static assets function add_cache_headers() { if ( is_admin() || is_user_logged_in() ) { return; } // Cache static pages for 1 hour if ( is_page() || is_single() ) { header( 'Cache-Control: public, max-age=3600' ); } // Cache archives for 30 minutes if ( is_archive() || is_home() ) { header( 'Cache-Control: public, max-age=1800' ); } } add_action( 'send_headers', 'add_cache_headers' );
Cache Invalidation Patterns
// Pattern 1: Time-based expiration set_transient( 'data', $value, 12 * HOUR_IN_SECONDS ); // Pattern 2: Event-based invalidation add_action( 'save_post_book', 'clear_book_caches' ); function clear_book_caches( $post_id ) { // Clear specific caches delete_transient( 'popular_books' ); delete_transient( 'recent_books' ); wp_cache_delete( "book_{$post_id}", 'books' ); // Clear author cache $post = get_post( $post_id ); wp_cache_delete( "user_{$post->post_author}_books", 'user_books' ); } // Pattern 3: Versioned cache keys function get_cache_version() { $version = wp_cache_get( 'cache_version', 'global' ); if ( false === $version ) { $version = time(); wp_cache_set( 'cache_version', $version, 'global' ); } return $version; } function get_cached_data( $key ) { $version = get_cache_version(); $versioned_key = "{$key}_v{$version}"; return wp_cache_get( $versioned_key, 'my_group' ); } function set_cached_data( $key, $data ) { $version = get_cache_version(); $versioned_key = "{$key}_v{$version}"; wp_cache_set( $versioned_key, $data, 'my_group', HOUR_IN_SECONDS ); } function invalidate_all_caches() { // Increment version to invalidate all caches $new_version = time(); wp_cache_set( 'cache_version', $new_version, 'global' ); } // Pattern 4: Cache warming add_action( 'save_post_book', 'warm_book_caches', 20 ); function warm_book_caches( $post_id ) { // Rebuild cache immediately after invalidation get_popular_books(); // Rebuilds transient }
CDN Integration
// Rewrite asset URLs to CDN add_filter( 'wp_get_attachment_url', 'cdn_rewrite_url' ); add_filter( 'wp_calculate_image_srcset', 'cdn_rewrite_srcset' ); function cdn_rewrite_url( $url ) { $cdn_domain = 'https://cdn.example.com'; $site_url = site_url(); // Only rewrite uploads directory if ( strpos( $url, '/wp-content/uploads/' ) !== false ) { return str_replace( $site_url, $cdn_domain, $url ); } return $url; } function cdn_rewrite_srcset( $sources ) { if ( ! is_array( $sources ) ) { return $sources; } foreach ( $sources as &$source ) { $source['url'] = cdn_rewrite_url( $source['url'] ); } return $sources; } // Purge CDN cache on content update add_action( 'save_post', 'purge_cdn_cache' ); function purge_cdn_cache( $post_id ) { // Example: Cloudflare API $zone_id = 'your_zone_id'; $api_key = 'your_api_key'; $email = 'your@email.com'; $url = get_permalink( $post_id ); wp_remote_post( "https://api.cloudflare.com/client/v4/zones/{$zone_id}/purge_cache", [ 'headers' => [ 'X-Auth-Email' => $email, 'X-Auth-Key' => $api_key, 'Content-Type' => 'application/json', ], 'body' => json_encode([ 'files' => [ $url ], ]), ]); }
5. Advanced Database Patterns
WP_Query Optimization
// Use 'fields' parameter to limit data $query = new WP_Query([ 'post_type' => 'book', 'fields' => 'ids', // Only return IDs (faster) ]); // Count queries $count = new WP_Query([ 'post_type' => 'book', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, // Skip SQL_CALC_FOUND_ROWS 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ]); // Complex meta queries with performance in mind $query = new WP_Query([ 'post_type' => 'book', 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_pages', 'value' => 200, 'compare' => '>', 'type' => 'NUMERIC', ], [ 'key' => '_rating', 'value' => 4, 'compare' => '>=', 'type' => 'DECIMAL', ], ], 'orderby' => 'meta_value_num', 'order' => 'DESC', ]);
Direct SQL for Complex Queries
// When WP_Query is too slow, use direct SQL global $wpdb; // Get books with JOIN on multiple meta keys $results = $wpdb->get_results(" SELECT p.ID, p.post_title, pm1.meta_value as isbn, pm2.meta_value as pages, pm3.meta_value as rating FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_isbn' LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_pages' LEFT JOIN {$wpdb->postmeta} pm3 ON p.ID = pm3.post_id AND pm3.meta_key = '_rating' WHERE p.post_type = 'book' AND p.post_status = 'publish' AND CAST(pm2.meta_value AS UNSIGNED) > 200 ORDER BY CAST(pm3.meta_value AS DECIMAL(3,2)) DESC LIMIT 20 "); // Use $wpdb->prepare() for dynamic values $min_pages = 200; $results = $wpdb->get_results( $wpdb->prepare(" SELECT p.ID, p.post_title, pm.meta_value as pages FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'book' AND pm.meta_key = '_pages' AND CAST(pm.meta_value AS UNSIGNED) > %d ORDER BY p.post_date DESC ", $min_pages ) );
Database Indexing
// Add custom indexes in plugin activation register_activation_hook( __FILE__, 'create_custom_indexes' ); function create_custom_indexes() { global $wpdb; // Index on post_type and post_status (common filter) $wpdb->query(" CREATE INDEX idx_post_type_status ON {$wpdb->posts} (post_type, post_status) "); // Index on meta_key and meta_value for frequent lookups $wpdb->query(" CREATE INDEX idx_meta_key_value ON {$wpdb->postmeta} (meta_key, meta_value(191)) "); // Composite index for date-based queries $wpdb->query(" CREATE INDEX idx_post_type_date ON {$wpdb->posts} (post_type, post_date) "); } // Remove indexes on deactivation register_deactivation_hook( __FILE__, 'remove_custom_indexes' ); function remove_custom_indexes() { global $wpdb; $wpdb->query( "DROP INDEX idx_post_type_status ON {$wpdb->posts}" ); $wpdb->query( "DROP INDEX idx_meta_key_value ON {$wpdb->postmeta}" ); $wpdb->query( "DROP INDEX idx_post_type_date ON {$wpdb->posts}" ); }
6. Multisite Development
Network-Activated Plugins
// Detect multisite if ( is_multisite() ) { // Multisite-specific code } // Network-wide hooks add_action( 'network_admin_menu', 'add_network_admin_page' ); function add_network_admin_page() { add_menu_page( 'Network Settings', 'My Plugin', 'manage_network_options', 'my-network-settings', 'render_network_settings_page' ); } // Network options (not site-specific) add_site_option( 'my_network_setting', 'value' ); $value = get_site_option( 'my_network_setting' ); update_site_option( 'my_network_setting', 'new_value' ); delete_site_option( 'my_network_setting' );
Cross-Site Operations
// Switch to another site $current_blog_id = get_current_blog_id(); switch_to_blog( 2 ); // Switch to site ID 2 // Perform operations on site 2 $posts = get_posts([ 'post_type' => 'book' ]); update_option( 'my_option', 'value' ); // Always restore restore_current_blog(); // Iterate all sites $sites = get_sites([ 'number' => 999 ]); foreach ( $sites as $site ) { switch_to_blog( $site->blog_id ); // Do something on each site $count = wp_count_posts( 'book' ); error_log( "Site {$site->blog_id} has {$count->publish} books" ); restore_current_blog(); }
7. Best Practices
Service-Oriented Architecture
<?php namespace MyPlugin\Services; class BookService { private $cache; private $validator; public function __construct( CacheService $cache, ValidationService $validator ) { $this->cache = $cache; $this->validator = $validator; } public function get_book( $book_id ) { // Check cache first $cache_key = "book_{$book_id}"; $book = $this->cache->get( $cache_key ); if ( false === $book ) { $book = get_post( $book_id ); if ( $book && 'book' === $book->post_type ) { $this->cache->set( $cache_key, $book, HOUR_IN_SECONDS ); } } return $book; } public function create_book( $data ) { // Validate input $errors = $this->validator->validate_book_data( $data ); if ( ! empty( $errors ) ) { return new \WP_Error( 'validation_failed', 'Validation failed', $errors ); } // Create book $book_id = wp_insert_post([ 'post_type' => 'book', 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_status' => $data['status'], ]); if ( is_wp_error( $book_id ) ) { return $book_id; } // Clear related caches $this->cache->invalidate_group( 'books' ); return $book_id; } } // Dependency injection container class Container { private $services = []; public function register( $name, $service ) { $this->services[ $name ] = $service; } public function get( $name ) { if ( ! isset( $this->services[ $name ] ) ) { throw new \Exception( "Service not found: {$name}" ); } return $this->services[ $name ]; } } // Bootstrap $container = new Container(); $container->register( 'cache', new CacheService() ); $container->register( 'validator', new ValidationService() ); $container->register( 'books', new BookService( $container->get( 'cache' ), $container->get( 'validator' ) ) );
Event-Driven Design
// Fire custom events do_action( 'myplugin_book_created', $book_id, $book_data ); do_action( 'myplugin_book_updated', $book_id, $old_data, $new_data ); do_action( 'myplugin_book_deleted', $book_id ); // Other plugins can listen add_action( 'myplugin_book_created', function( $book_id, $book_data ) { // Send email notification // Update analytics // Sync to external service }, 10, 2 ); // Use filters for modifiable data $book_data = apply_filters( 'myplugin_before_book_save', $book_data, $book_id ); $notification_recipients = apply_filters( 'myplugin_notification_recipients', [ 'admin@example.com' ], $book_id );
Scalability Considerations
/** * Performance Checklist: * * 1. Database: * - Use indexes on frequently queried columns * - Avoid SELECT * queries (use specific fields) * - Batch operations instead of loops * - Use LIMIT for large datasets * * 2. Caching: * - Implement object caching (Redis/Memcached) * - Use transients for expensive operations * - Fragment caching for HTML blocks * - CDN for static assets * * 3. Queries: * - Use 'fields' => 'ids' when possible * - Set 'no_found_rows' => true if not paginating * - Disable update_post_meta_cache and update_post_term_cache when not needed * * 4. Assets: * - Minify and concatenate CSS/JS * - Lazy load images * - Use responsive images (srcset) * - Defer non-critical JavaScript * * 5. Monitoring: * - Use Query Monitor for development * - New Relic or Application Insights for production * - Monitor slow queries * - Track cache hit rates */ // Example: Batch processing with WP-CLI function process_books_batch() { $offset = 0; $batch_size = 100; do { $books = get_posts([ 'post_type' => 'book', 'posts_per_page' => $batch_size, 'offset' => $offset, 'fields' => 'ids', ]); foreach ( $books as $book_id ) { // Process each book update_post_meta( $book_id, '_processed', time() ); } $offset += $batch_size; // Prevent memory leaks wp_cache_flush(); } while ( count( $books ) === $batch_size ); }
Related Skills
When building advanced WordPress applications, consider these complementary skills (available in the skill library):
- WordPress Plugin Fundamentals: Core plugin architecture and hooks - essential foundation for custom REST endpoints and WP-CLI commands
- WordPress Security & Data Validation: Security best practices - critical for securing REST API endpoints and validating user input
- WordPress Testing & QA: Testing REST endpoints and WP-CLI - comprehensive testing strategies for advanced WordPress features
- GraphQL: Alternative to REST API - consider GraphQL as a modern alternative to WordPress REST API for complex data queries
- Docker: Development environment setup - containerize WordPress development for consistent and reproducible environments
References
- REST API Handbook - Official REST API documentation
- WP-CLI - Command-line interface for WordPress
- Object Cache - WordPress object caching
- Transients API - WordPress transients documentation
- Query Monitor - Performance profiling plugin
- Redis Object Cache - Redis integration plugin