פיתוח נקודות קצה מותאמות אישית של REST API עבור WordPress
במשך יותר מ-5 שנים, ההתמחות שלנו היא פיתוח נקודות קצה מותאמות אישית של REST API עבור WordPress. אפליקציית מובייל או חזית SPA דורשות מ-WordPress לספק נתונים מצטברים עם סינון לפי טקסונומיות ושדות מטא. ה-/wp/v2/posts הסטנדרטי לא יכול להציג סכום כולל של הזמנות של לקוח לאורך תקופה או רשימת פרויקטים עם מיון משולב. מצב טיפוסי: ללא קאש, ב-5,000 בקשות בשעה, השרת קורס. מפתחים לעתים קרובות פונים לשאילתות SQL ישירות, מה שמוביל לבעיות N+1 ולפרצות אבטחה. הניסיון שלנו מראה שנקודת קצה מותאמת אישית מתוכננת היטב פותרת משימות אלה תוך 2–5 ימים, ומפחיתה את עומס מסד הנתונים פי 3–5. במאמר זה, נפרק תרחישים טיפוסיים, ערימת הטכנולוגיות והארכיטקטורה.
כדי להימנע מבעיות כאלה, אנו משתמשים בממשק אחיד לכל הנתונים: נקודת קצה מותאמת אישית של REST API מאגדת פוסטים, שדות מטא וטקסונומיות בבקשה אחת. זה מקצץ קריאות HTTP פי 5–10 ומפשט את תחזוקת החזית. לדוגמה, עבור פרויקט אחד (קטלוג עם 20,000 מוצרים) יישמנו את נקודת הקצה /my-plugin/v1/products עם סינון לפי קטגוריות, מחיר ומאפיינים — זמן התגובה ירד מ-4 ל-1 שנייה.
מגבלות ה-REST API הסטנדרטי של WordPress
המסלולים ברירת המחדל /wp/v2/posts ו-/wp/v2/pages מתאימים לקריאת פוסטים, אך לא עבור:
- נתונים מצטברים — סכום כולל של הזמנות לקוח בחודש האחרון.
- מסננים מורכבים — שילוב של שדות מטא וטקסונומיות עם מיון.
- פעולות מותאמות אישית — יצירת הזמנה עם אימות מלאי ושליחת דוא"ל.
ללא נקודת קצה מותאמת אישית, הלקוח צריך לבצע בקשות מרובות או להשתמש בשאילתות SQL לא בטוחות. נקודת קצה מותאמת אישית עם קאש מאפשרת להפחית את זמן התגובה מ-4 ל-1 שנייה.
רישום נקודת קצה מותאמת אישית של REST API
רישום נקודת קצה עם שיטות GET ו-POST. פרטים נוספים ב-REST API Handbook.
add_action('rest_api_init', function () {
register_rest_route('my-plugin/v1', '/projects', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_plugin_get_projects',
'permission_callback' => '__return_true',
'args' => [
'category' => [
'type' => 'string',
'sanitize_callback' => 'sanitize_title',
],
'tech' => [
'type' => 'array',
'items' => ['type' => 'string'],
'sanitize_callback' => function ($value) {
return array_map('sanitize_title', (array) $value);
},
],
'per_page' => [
'type' => 'integer',
'default' => 12,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
],
'page' => [
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
],
],
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'my_plugin_create_project',
'permission_callback' => function () {
return current_user_can('edit_posts');
},
],
]);
register_rest_route('my-plugin/v1', '/projects/(?P<id>\d+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_plugin_get_project',
'permission_callback' => '__return_true',
'args' => [
'id' => [
'validate_callback' => function ($param) {
return is_numeric($param) && $param > 0;
},
],
],
]);
});"}מטפל בקשות GET עם סינון טקסונומיה:
function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response|WP_Error {
$per_page = $request->get_param('per_page');
$page = $request->get_param('page');
$category = $request->get_param('category');
$techs = $request->get_param('tech');
$query_args = [
'post_type' => 'project',
'post_status' => 'publish',
'posts_per_page' => $per_page,
'paged' => $page,
'no_found_rows' => false,
];
$tax_queries = [];
if ($category) {
$tax_queries[] = [
'taxonomy' => 'project_category',
'field' => 'slug',
'terms' => $category,
];
}
if (!empty($techs)) {
$tax_queries[] = [
'taxonomy' => 'tech_stack',
'field' => 'slug',
'terms' => $techs,
'operator' => 'IN',
];
}
if (!empty($tax_queries)) {
$query_args['tax_query'] = array_merge(['relation' => 'AND'], $tax_queries);
}
$query = new WP_Query($query_args);
$projects = [];
foreach ($query->posts as $post) {
$projects[] = my_plugin_format_project($post);
}
$response = new WP_REST_Response($projects, 200);
$response->header('X-WP-Total', $query->found_posts);
$response->header('X-WP-TotalPages', $query->max_num_pages);
return $response;
}
function my_plugin_format_project(WP_Post $post): array {
$thumbnail_id = get_post_thumbnail_id($post->ID);
$thumbnail_url = $thumbnail_id ? wp_get_attachment_image_url($thumbnail_id, 'large') : null;
return [
'id' => $post->ID,
'slug' => $post->post_name,
'title' => wp_strip_all_tags($post->post_title),
'excerpt' => wp_strip_all_tags(get_the_excerpt($post)),
'url' => get_permalink($post->ID),
'thumbnail' => $thumbnail_url,
'client' => get_post_meta($post->ID, 'project_client', true),
'year' => (int) get_post_meta($post->ID, 'project_year', true),
'categories' => wp_get_post_terms($post->ID, 'project_category', ['fields' => 'slugs']),
'tech_stack' => wp_get_post_terms($post->ID, 'tech_stack', ['fields' => 'slugs']),
'modified' => get_post_modified_time('c', true, $post),
];
}מטפל POST עם אימות:
function my_plugin_create_project(WP_REST_Request $request): WP_REST_Response|WP_Error {
$body = $request->get_json_params();
if (empty($body['title'])) {
return new WP_Error('missing_title', 'Заголовок обязателен', ['status' => 422]);
}
$post_id = wp_insert_post([
'post_type' => 'project',
'post_title' => sanitize_text_field($body['title']),
'post_content' => wp_kses_post($body['content'] ?? ''),
'post_status' => 'draft',
'post_author' => get_current_user_id(),
], true);
if (is_wp_error($post_id)) {
return new WP_Error('insert_failed', $post_id->get_error_message(), ['status' => 500]);
}
if (!empty($body['client'])) {
update_post_meta($post_id, 'project_client', sanitize_text_field($body['client']));
}
return new WP_REST_Response(
['id' => $post_id, 'url' => get_permalink($post_id)],
201
);
} באיזו שיטת אימות לבחור?
עבור נקודות קצה GET, גישה ציבורית מספיקה. עבור יצירה או שינוי נתונים, יש צורך בבדיקת הרשאות. השווה בין השיטות:
| שיטה | תרחיש | מורכבות |
|---|---|---|
| עוגיות (Cookie) | בקשות מאזור הניהול | אפס (מובנה) |
| סיסמאות יישום (Application Passwords) | לקוחות חיצוניים בצד השרת | נמוכה (תוסף רשמי) |
| JWT | SPA, אפליקציות מובייל | בינונית (תוסף או קוד מותאם אישית) |
דוגמה ליירוט טוקן Bearer:
add_filter('rest_authentication_errors', function ($result) {
if (!empty($result)) return $result;
$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!str_starts_with($auth_header, 'Bearer ')) {
return $result;
}
$token = substr($auth_header, 7);
$user_id = my_plugin_validate_jwt($token);
if (is_wp_error($user_id)) {
return $user_id;
}
wp_set_current_user($user_id);
return true;
}); קאש לתגובות REST API
עבור בקשות כבדות, אנו משתמשים ב-Transients API. זה מפחית את עומס מסד הנתונים פי 3–5. דוגמה:
function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response {
$cache_key = 'projects_' . md5(serialize($request->get_params()));
$cached = get_transient($cache_key);
if ($cached !== false) {
$response = new WP_REST_Response($cached['data'], 200);
$response->header('X-WP-Total', $cached['total']);
$response->header('X-Cache', 'HIT');
return $response;
}
// ... основная логика ...
set_transient($cache_key, ['data' => $projects, 'total' => $total], 5 * MINUTE_IN_SECONDS);
return $response;
}
add_action('save_post_project', function (int $post_id): void {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_projects_%'");
});
מה כלול בפיתוח נקודת קצה
| שלב | תוצאה |
|---|---|
| ניתוח | קביעת נקודות קצה, סוגי נתונים, שיטות אימות |
| עיצוב | סכמת מסלולים, מבנה תגובה, אימות פרמטרים |
| יישום | כתיבת קוד, רישום מסלולים, מטפלים, קאש |
| בדיקות | בדיקות יחידה (PHPUnit), בדיקות ידניות עם curl |
| פריסה | פריסה לשרת ייצור, הגדרת ניטור |
| תיעוד | סכמת OpenAPI או הוראות למפתחים |
אנו מבטיחים עמידה בלוח הזמנים ומספקים תמיכה לאחר השחרור למשך 30 יום. צרו קשר להערכת הפרויקט שלכם — נייעץ בנושא ארכיטקטורה והיקף עבודה. הזמינו פיתוח נקודת קצה מותאמת אישית החל מ-$2,000 וקצרו את זמן האינטגרציה פי 2.
תהליך עבודה לפיתוח REST API מותאם אישית
- ביקורת — קביעת רשימת נקודות הקצה, שיטות (GET/POST/PUT/DELETE) ומבני תגובה.
- עיצוב סכמת מסלולים — גרסאות (
add_action('rest_api_init', function () { register_rest_route('my-plugin/v1', '/projects', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => 'my_plugin_get_projects', 'permission_callback' => '__return_true', 'args' => [ 'category' => [ 'type' => 'string', 'sanitize_callback' => 'sanitize_title', ], 'tech' => [ 'type' => 'array', 'items' => ['type' => 'string'], 'sanitize_callback' => function ($value) { return array_map('sanitize_title', (array) $value); }, ], 'per_page' => [ 'type' => 'integer', 'default' => 12, 'minimum' => 1, 'maximum' => 100, 'sanitize_callback' => 'absint', ], 'page' => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'sanitize_callback' => 'absint', ], ], ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'my_plugin_create_project', 'permission_callback' => function () { return current_user_can('edit_posts'); }, ], ]); register_rest_route('my-plugin/v1', '/projects/(?P<id>\d+)', [ 'methods' => WP_REST_Server::READABLE, 'callback' => 'my_plugin_get_project', 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'validate_callback' => function ($param) { return is_numeric($param) && $param > 0; }, ], ], ]); });), ארגומנטים, אימות פרמטרים. - יישום מטפלים — כתיבת פונקציות callback, עיצוב נתונים, טיפול בשגיאות.
- הגדרת אימות — עוגיות (לניהול), סיסמאות יישום או JWT (ל-SPA/מובייל).
- קאש — Transients API או Redis, ביטול קאש בעת שינוי נתונים.
- בדיקות עם curl ו-PHPUnit, תיעוד בפורמט OpenAPI.
כיצד לנפות באגים ב-REST API מותאם אישית של WordPress?
השתמשו ב-function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response|WP_Error { $per_page = $request->get_param('per_page'); $page = $request->get_param('page'); $category = $request->get_param('category'); $techs = $request->get_param('tech'); $query_args = [ 'post_type' => 'project', 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $page, 'no_found_rows' => false, ]; $tax_queries = []; if ($category) { $tax_queries[] = [ 'taxonomy' => 'project_category', 'field' => 'slug', 'terms' => $category, ]; } if (!empty($techs)) { $tax_queries[] = [ 'taxonomy' => 'tech_stack', 'field' => 'slug', 'terms' => $techs, 'operator' => 'IN', ]; } if (!empty($tax_queries)) { $query_args['tax_query'] = array_merge(['relation' => 'AND'], $tax_queries); } $query = new WP_Query($query_args); $projects = []; foreach ($query->posts as $post) { $projects[] = my_plugin_format_project($post); } $response = new WP_REST_Response($projects, 200); $response->header('X-WP-Total', $query->found_posts); $response->header('X-WP-TotalPages', $query->max_num_pages); return $response; } function my_plugin_format_project(WP_Post $post): array { $thumbnail_id = get_post_thumbnail_id($post->ID); $thumbnail_url = $thumbnail_id ? wp_get_attachment_image_url($thumbnail_id, 'large') : null; return [ 'id' => $post->ID, 'slug' => $post->post_name, 'title' => wp_strip_all_tags($post->post_title), 'excerpt' => wp_strip_all_tags(get_the_excerpt($post)), 'url' => get_permalink($post->ID), 'thumbnail' => $thumbnail_url, 'client' => get_post_meta($post->ID, 'project_client', true), 'year' => (int) get_post_meta($post->ID, 'project_year', true), 'categories' => wp_get_post_terms($post->ID, 'project_category', ['fields' => 'slugs']), 'tech_stack' => wp_get_post_terms($post->ID, 'tech_stack', ['fields' => 'slugs']), 'modified' => get_post_modified_time('c', true, $post), ]; } לבדיקות בסיסיות. הפעילו WP_DEBUG ו-WP_DEBUG_LOG ב-function my_plugin_create_project(WP_REST_Request $request): WP_REST_Response|WP_Error { $body = $request->get_json_params(); if (empty($body['title'])) { return new WP_Error('missing_title', 'Заголовок обязателен', ['status' => 422]); } $post_id = wp_insert_post([ 'post_type' => 'project', 'post_title' => sanitize_text_field($body['title']), 'post_content' => wp_kses_post($body['content'] ?? ''), 'post_status' => 'draft', 'post_author' => get_current_user_id(), ], true); if (is_wp_error($post_id)) { return new WP_Error('insert_failed', $post_id->get_error_message(), ['status' => 500]); } if (!empty($body['client'])) { update_post_meta($post_id, 'project_client', sanitize_text_field($body['client'])); } return new WP_REST_Response( ['id' => $post_id, 'url' => get_permalink($post_id)], 201 ); } — שגיאות PHP יופיעו ב-add_filter('rest_authentication_errors', function ($result) { if (!empty($result)) return $result; $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (!str_starts_with($auth_header, 'Bearer ')) { return $result; } $token = substr($auth_header, 7); $user_id = my_plugin_validate_jwt($token); if (is_wp_error($user_id)) { return $user_id; } wp_set_current_user($user_id); return true; }); . תוסף Query Monitor מציג את כל שאילתות ה-SQL שבוצעו במהלך קריאת נקודת הקצה ועוזר לזהות בעיות N+1. בדקו כותרות תגובה: function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response { $cache_key = 'projects_' . md5(serialize($request->get_params())); $cached = get_transient($cache_key); if ($cached !== false) { $response = new WP_REST_Response($cached['data'], 200); $response->header('X-WP-Total', $cached['total']); $response->header('X-Cache', 'HIT'); return $response; } // ... основная логика ... set_transient($cache_key, ['data' => $projects, 'total' => $total], 5 * MINUTE_IN_SECONDS); return $response; } add_action('save_post_project', function (int $post_id): void { global $wpdb; $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_projects_%'"); }); צריך להכיל את מספר הרשומות, /my-plugin/v1/. בשגיאת 401, ודאו ש-curl -X GET https://site.com/wp-json/my-plugin/v1/projects -v מחזיר wp-config.php או בודק כראוי הרשאות משתמש.
למה נקודות קצה מותאמות אישית עדיפות על שאילתות SQL ישירות
נקודת קצה מותאמת אישית של REST API מבטיחה אבטחה (סינון דרך WP API), קאש (Transients/Redis) וגרסאות. לפי הנתונים שלנו, מעבר לנקודות קצה מותאמות אישית מקצר את זמן האינטגרציה פי 2 ומפחית שגיאות ב-60%. אנו מפתחים פתרונות WordPress כבר יותר מ-5 שנים, עם יותר מ-30 פרויקטים הכוללים REST API מותאם אישית. קבלו ייעוץ — נעריך את הפרויקט שלכם ונציע את הפתרון האופטימלי. ההשקעה הטיפוסית נעה בין $1,500 ל-$5,000 לכל סט נקודות קצה, עם חיסכון ממוצע של $3,000 בחודש בעלויות שרת לאחר המעבר.







