wordpress-advanced-architecture

安装量: 129
排名: #6689

安装

npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill wordpress-advanced-architecture

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:

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[\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 ] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * [--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 * * * : 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(); ?> <pre class="codehilite"><code> <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; </code></pre> <p>}</p> <p>Page Caching vs Object Caching /*<em> * 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 </em>/</p> <p>// Example: Bypass page cache for dynamic content header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );</p> <p>// Cache-Control headers for static assets function add_cache_headers() { if ( is_admin() || is_user_logged_in() ) { return; }</p> <pre class="codehilite"><code>// 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' ); } </code></pre> <p>} add_action( 'send_headers', 'add_cache_headers' );</p> <p>Cache Invalidation Patterns // Pattern 1: Time-based expiration set_transient( 'data', $value, 12 * HOUR_IN_SECONDS );</p> <p>// 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' );</p> <pre class="codehilite"><code>// Clear author cache $post = get_post( $post_id ); wp_cache_delete( "user_{$post->post_author}_books", 'user_books' ); </code></pre> <p>}</p> <p>// 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; }</p> <p>function get_cached_data( $key ) { $version = get_cache_version(); $versioned_key = "{$key}_v{$version}"; return wp_cache_get( $versioned_key, 'my_group' ); }</p> <p>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 ); }</p> <p>function invalidate_all_caches() { // Increment version to invalidate all caches $new_version = time(); wp_cache_set( 'cache_version', $new_version, 'global' ); }</p> <p>// 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 }</p> <p>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' );</p> <p>function cdn_rewrite_url( $url ) { $cdn_domain = 'https://cdn.example.com'; $site_url = site_url();</p> <pre class="codehilite"><code>// Only rewrite uploads directory if ( strpos( $url, '/wp-content/uploads/' ) !== false ) { return str_replace( $site_url, $cdn_domain, $url ); } return $url; </code></pre> <p>}</p> <p>function cdn_rewrite_srcset( $sources ) { if ( ! is_array( $sources ) ) { return $sources; }</p> <pre class="codehilite"><code>foreach ( $sources as &$source ) { $source['url'] = cdn_rewrite_url( $source['url'] ); } return $sources; </code></pre> <p>}</p> <p>// 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';</p> <pre class="codehilite"><code>$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 ], ]), ]); </code></pre> <p>}</p> <ol> <li>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) ]);</li> </ol> <p>// 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, ]);</p> <p>// 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', ]);</p> <p>Direct SQL for Complex Queries // When WP_Query is too slow, use direct SQL global $wpdb;</p> <p>// 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 ");</p> <p>// 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 ) );</p> <p>Database Indexing // Add custom indexes in plugin activation register_activation_hook( <strong>FILE</strong>, 'create_custom_indexes' ); function create_custom_indexes() { global $wpdb;</p> <pre class="codehilite"><code>// 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) "); </code></pre> <p>}</p> <p>// Remove indexes on deactivation register_deactivation_hook( <strong>FILE</strong>, 'remove_custom_indexes' ); function remove_custom_indexes() { global $wpdb;</p> <pre class="codehilite"><code>$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}" ); </code></pre> <p>}</p> <ol> <li>Multisite Development Network-Activated Plugins // Detect multisite if ( is_multisite() ) { // Multisite-specific code }</li> </ol> <p>// 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' ); }</p> <p>// 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' );</p> <p>Cross-Site Operations // Switch to another site $current_blog_id = get_current_blog_id(); switch_to_blog( 2 ); // Switch to site ID 2</p> <p>// Perform operations on site 2 $posts = get_posts([ 'post_type' => 'book' ]); update_option( 'my_option', 'value' );</p> <p>// Always restore restore_current_blog();</p> <p>// Iterate all sites $sites = get_sites([ 'number' => 999 ]); foreach ( $sites as $site ) { switch_to_blog( $site->blog_id );</p> <pre class="codehilite"><code>// Do something on each site $count = wp_count_posts( 'book' ); error_log( "Site {$site->blog_id} has {$count->publish} books" ); restore_current_blog(); </code></pre> <p>}</p> <ol> <li>Best Practices Service-Oriented Architecture</li> </ol> <?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 ?> </article> <a href="/" class="back-link">← <span data-i18n="detail.backToLeaderboard">返回排行榜</span></a> </div> <aside class="sidebar"> <section class="related-skills" id="relatedSkillsSection"> <h2 class="related-title" data-i18n="detail.relatedSkills">相关 Skills</h2> <div class="related-list" id="relatedSkillsList"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> </section> </aside> </div> </div> <script src="https://unpkg.com/i18next@23.11.5/i18next.min.js" defer></script> <script src="https://unpkg.com/i18next-browser-languagedetector@7.2.1/i18nextBrowserLanguageDetector.min.js" defer></script> <script defer> // Language resources - same pattern as index page const resources = { 'zh-CN': null, 'en': null, 'ja': null, 'ko': null, 'zh-TW': null, 'es': null, 'fr': null }; // Load language files (only current + fallback for performance) async function loadLanguageResources() { const savedLang = localStorage.getItem('i18nextLng') || 'en'; const langsToLoad = new Set([savedLang, 'en']); // current + fallback await Promise.all([...langsToLoad].map(async (lang) => { try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } })); } // Load a single language on demand (for language switching) async function loadLanguage(lang) { if (resources[lang]) return; try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; i18next.addResourceBundle(lang, 'translation', resources[lang].translation); } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } } // Initialize i18next async function initI18n() { try { await loadLanguageResources(); // Filter out null values from resources const validResources = {}; for (const [lang, data] of Object.entries(resources)) { if (data !== null) { validResources[lang] = data; } } console.log('Loaded languages:', Object.keys(validResources)); console.log('zh-CN resource:', validResources['zh-CN']); console.log('detail.home in resource:', validResources['zh-CN']?.translation?.detail?.home); // 检查是否有保存的语言偏好 const savedLang = localStorage.getItem('i18nextLng'); // 如果没有保存的语言偏好,默认使用英文 const defaultLang = savedLang && ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'].includes(savedLang) ? savedLang : 'en'; await i18next .use(i18nextBrowserLanguageDetector) .init({ lng: defaultLang, // 强制设置初始语言 fallbackLng: 'en', supportedLngs: ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'], resources: validResources, detection: { order: ['localStorage'], // 只使用 localStorage,不检测浏览器语言 caches: ['localStorage'], lookupLocalStorage: 'i18nextLng' }, interpolation: { escapeValue: false } }); console.log('i18next initialized, language:', i18next.language); console.log('Test translation:', i18next.t('detail.home')); // Set initial language in selector const langSwitcher = document.getElementById('langSwitcher'); langSwitcher.value = i18next.language; // Update page language updatePageLanguage(); // Language switch event langSwitcher.addEventListener('change', async (e) => { await loadLanguage(e.target.value); // load on demand i18next.changeLanguage(e.target.value).then(() => { updatePageLanguage(); localStorage.setItem('i18nextLng', e.target.value); }); }); } catch (error) { console.error('i18next init failed:', error); } } // Translation helper function t(key, options = {}) { return i18next.t(key, options); } // Update all translatable elements function updatePageLanguage() { // Update HTML lang attribute document.documentElement.lang = i18next.language; // Update elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } // Copy command function function copyCommand() { const command = document.getElementById('installCommand').textContent; const btn = document.getElementById('copyBtn'); navigator.clipboard.writeText(command).then(() => { btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for non-HTTPS const textArea = document.createElement('textarea'); textArea.value = command; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }); } // Initialize document.getElementById('copyBtn').addEventListener('click', copyCommand); initI18n(); // 异步加载相关 Skills async function loadRelatedSkills() { const owner = 'bobmatnyc'; const skillName = 'wordpress-advanced-architecture'; const currentLang = 'en'; const listContainer = document.getElementById('relatedSkillsList'); const section = document.getElementById('relatedSkillsSection'); try { const response = await fetch(`/api/related-skills/${encodeURIComponent(owner)}/${encodeURIComponent(skillName)}?limit=6`); if (!response.ok) { throw new Error('Failed to load'); } const data = await response.json(); const relatedSkills = data.related_skills || []; if (relatedSkills.length === 0) { // 没有相关推荐时隐藏整个区域 section.style.display = 'none'; return; } // 渲染相关 Skills listContainer.innerHTML = relatedSkills.map(skill => { const desc = skill.description || ''; const truncatedDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return ` <a href="${currentLang === 'en' ? '' : '/' + currentLang}/skill/${skill.owner}/${skill.repo}/${skill.skill_name}" class="related-card"> <div class="related-name">${escapeHtml(skill.skill_name)}</div> <div class="related-meta"> <span class="related-owner">${escapeHtml(skill.owner)}</span> <span class="related-installs">${skill.installs}</span> </div> <div class="related-desc">${escapeHtml(truncatedDesc)}</div> </a> `; }).join(''); } catch (error) { console.error('Failed to load related skills:', error); // 加载失败时显示提示或隐藏 listContainer.innerHTML = '<div class="related-empty">暂无相关推荐</div>'; } } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 页面加载完成后异步加载相关 Skills if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadRelatedSkills); } else { loadRelatedSkills(); } </script> </body> </html>