أهلاً بيك يا صديقي المطور في واحد من أهم دروس النضج البرمجي في ووردبريس! لما تبدأ تتعلم REST API، أول حاجة بتعملها إنك بتفتح ملف functions.php وتكتب register_rest_route() وترمي جواه Closure function فيها استعلام SQL وشوية كود تحقق، والموضوع بيشتغل وبتفرح. لكن أول ما مشروعك يكبر، ويبقى عندك 30 مسار، وعمليات دفع، ومصادقة مستخدمين، وحسابات فواتير، هتلاقي نفسك قدام “سباغيتي كود” كارثي مستحيل صيانته، وأي تعديل في سطر بيضرب تلات مسارات تانية! هنا بقى بيجي دور متحكمات REST (WP_REST_Controller) وفصل المسؤوليات وفق مبادئ SOLID وهندسة طبقة الخدمات (Service-Repository Pattern). في المقال العملي المعمق ده، هنتعلم إزاي نبني Controllers مؤسسية نظيفة قابلة للتوسيع والاختبار، ونفصل منطق البيزنس تماماً عن طبقة الـ HTTP، ونشوف دراسة حالة حقيقية لإنقاذ منصة تعليمية مصرية كبرى.
لماذا صمم ووردبريس كلاس WP_REST_Controller؟
في هندسة البرمجيات الكائنية (OOP)، الكنترولر (Controller) وظيفته الأساسية في معمارية MVC هو إنه يكون “منسق حركة المرور” (Traffic Cop)؛ يستلم الريكوست من العميل، يفكك المدخلات، يطلب من طبقة البيزنس معالجتها، ويرجع الاستجابة المناسبة.
فريق تطوير ووردبريس الأساسي صمم الكلاس التجريدي WP_REST_Controller (الموجود في wp-includes/rest-api/endpoints/class-wp-rest-controller.php) ليكون العقد الإجباري الموحد (Contract) لأي واجهة برمجية.
لما ترث من الكلاس ده، أنت بتستفيد بـ 4 مزايا خرافية:
- توحيد أسماء وتواقيع الدوال (Standardized Interface): كل مطور في فريقك هيكون عارف بالظبط فين دالة جلب القائمة (
get_items)، وفين دالة المورد الفردي (get_item)، وفين دالة الإنشاء (create_item)، بدون أي ارتجال عشوائي. - توليد مواصفات الحقول والتحقق تلقائياً: بفضل دالة
get_item_schema()ودالةget_endpoint_args_for_item_schema()، محرك ووردبريس بياخد مواصفات حقولك ويحولها لقواعد Validation و Sanitization أوتوماتيك دون كتابة سطر كود إضافي! - دعم الـ Context Filtering: الكلاس بيدعم تلقائياً التمييز بين سياق العرض العام (
viewcontext) وسياق التعديل الإداري (editcontext) لحجب الحقول الحساسة عن العامة. - سهولة الصيانة والتوثيق التلقائي: المسارات المسجلة عبر الكنترولر بتكون موثقة ذاتياً وتدعم طلبات
OPTIONSالتفاعلية.
تطبيق مبادئ SOLID الخمسة داخل متحكمات ووردبريس
عشان الكنترولر بتاعك يكون “Slim Controller” رشيق وسريع، لازم تطبق مبادئ SOLID الهندسية:
1. مبدأ المسؤولية الواحدة (Single Responsibility Principle – SRP)
الكنترولر لازم يكون له سبب واحد بس للتغيير: تغيير متطلبات طبقة الـ HTTP (زي تغيير اسم ترويسة، أو كود حالة الرد). الكنترولر مينفعش يكتب استعلامات SQL بإيده، ومينفعش يكلم بوابات الدفع بنفسه، ومينفعش يبعت إيميلات! كل العمليات دي مكانها الطبيعي هو كلاسات الخدمات المستقلة (Dedicated Service Classes). الكنترولر يستدعي الخدمة ويستلم النتيجة فقط.
2. مبدأ عكس التبعية وحقن الاعتماديات (Dependency Inversion – DIP)
بدل ما الكنترولر ينشئ كائنات الخدمات جواه بـ new Payment_Gateway()، بنمرر له واجهة الخدمة (Interface) عبر الـ Constructor (Constructor Injection).
ده بيخلينا نقدر أثناء اختبارات الوحدات (Unit Testing) نحقن Mock Service وهمية بدون ما نخصم فلوس حقيقية أو نلمس قاعدة البيانات الحية.
مقارنة معمارية شاملة: أنماط تنظيم كود الـ REST API في ووردبريس
| المعيار الهندسي | الكنترولر الرشيق + طبقة الخدمات (Slim Controller + Service) | الكنترولر السمين (Fat Controller) | الدوال الإجرائية المبعثرة (Procedural in functions.php) |
|---|---|---|---|
| فصل المسؤوليات (Separation of Concerns) | مثالي ونظيف بنسبة 100% | سيئ (خلط الـ HTTP مع الـ Business Logic) | منعدم تماماً وكارثي |
| قابلية الاختبار (Testability & Mocking) | عالية جداً؛ كل خدمة يمكن اختبارها بمعزل تام | صعبة وتتطلب تشغيل بيئة الـ REST كاملة | شبه مستحيلة بدون تشغيل ووردبريس كاملاً |
| إعادة استخدام الكود (Code Reusability) | فائقة؛ نفس الخدمة يمكن استخدامها في REST و CLI و Cron | محدودة بسياق طلبات الـ HTTP فقط | تتطلب نسخ ولصق الأكواد في أماكن متعددة |
| عقود البيانات والـ Schema | موحدة عبر get_item_schema() المعيارية | موجودة ولكن متداخلة مع المنطق | غائبة تماماً ويعتمد على التحقق اليدوي العقيم |
| سهولة الصيانة لفرق العمل الكبيرة | سلسة جداً وواضحة لكل مهندس برمجيات | متوسطة إلى معقدة مع كبر حجم الملف | كابوس يؤدي للنزاعات (Git Merge Conflicts) الدائمة |
تطبيق برودكشن متكامل: بناء نظام اشتراكات أكاديمي بنمط Controller & Service
تعال نبني كود حقيقي لمنظومة حجز كورسات دراسية. الكود مقسم معمارياً إلى:
1. واجهة الخدمة (Interface).
2. كلاس الخدمة المسؤولة عن معالجة البيزنس وقاعدة البيانات.
3. كلاس الـ Controller اللي بيرث من WP_REST_Controller ومحقون جواه كائن الخدمة:
<?php
/**
* بنية متحكمات REST مؤسسية مع طبقة الخدمات
* Author: Mohamed Abozaid
*/
if (!defined('ABSPATH')) {
exit;
}
// ==========================================
// 1. العقد البرمجي للخدمة (Service Interface)
// ==========================================
interface Course_Subscription_Service_Interface {
public function enroll_student(int $user_id, int $course_id, string $coupon = ''): array|WP_Error;
public function get_user_enrollments(int $user_id, int $page, int $per_page): array;
public function count_user_enrollments(int $user_id): int;
}
// ==========================================
// 2. كلاس تطبيق الخدمة (Service Implementation)
// ==========================================
class Course_Subscription_Service implements Course_Subscription_Service_Interface {
public function enroll_student(int $user_id, int $course_id, string $coupon = ''): array|WP_Error {
global $wpdb;
// التحقق من وجود الكورس ونشره
$course = get_post($course_id);
if (!$course || $course->post_type !== 'courses' || $course->post_status !== 'publish') {
return new WP_Error('course_not_found', __('الدورة التدريبية المطلوبة غير متاحة.', 'text-domain'), ['status' => 404]);
}
$table = $wpdb->prefix . 'lms_enrollments';
// التحقق من عدم وجود تسجيل مسبق
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$table} WHERE user_id = %d AND course_id = %d AND status = 'active'",
$user_id,
$course_id
));
if ($existing) {
return new WP_Error('already_enrolled', __('الطالب مسجل بالفعل في هذه الدورة.', 'text-domain'), ['status' => 409]);
}
// تطبيق منطق الخصم والعمليات الحسابية
$base_price = (float) get_post_meta($course_id, '_course_price', true);
$final_price = $base_price;
if (!empty($coupon) && strtoupper($coupon) === 'EGYPT2026') {
$final_price *= 0.8; // خصم 20%
}
// تسجيل الاشتراك في المعاملات المالية وقاعدة البيانات
$inserted = $wpdb->insert(
$table,
[
'user_id' => $user_id,
'course_id' => $course_id,
'paid_amount' => $final_price,
'coupon_used' => sanitize_text_field($coupon),
'status' => 'active',
'enrolled_at' => current_time('mysql'),
],
['%d', '%d', '%f', '%s', '%s', '%s']
);
if (!$inserted) {
return new WP_Error('db_insert_failed', __('تعذر إتمام عملية التسجيل بقاعدة البيانات.', 'text-domain'), ['status' => 500]);
}
return [
'enrollment_id' => $wpdb->insert_id,
'course_id' => $course_id,
'user_id' => $user_id,
'paid_amount' => $final_price,
'status' => 'active',
'created_at' => current_time('mysql'),
];
}
public function get_user_enrollments(int $user_id, int $page, int $per_page): array {
global $wpdb;
$table = $wpdb->prefix . 'lms_enrollments';
$offset = ($page - 1) * $per_page;
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$table} WHERE user_id = %d ORDER BY id DESC LIMIT %d OFFSET %d",
$user_id,
$per_page,
$offset
),
ARRAY_A
) ?: [];
}
public function count_user_enrollments(int $user_id): int {
global $wpdb;
$table = $wpdb->prefix . 'lms_enrollments';
return (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE user_id = %d", $user_id));
}
}
// ==========================================
// 3. كلاس المتحكم النظيف (Enterprise REST Controller)
// ==========================================
class Enterprise_Courses_REST_Controller extends WP_REST_Controller {
protected $namespace = 'lms/v1';
protected $rest_base = 'enrollments';
private Course_Subscription_Service_Interface $service;
/**
* حقن التبعية عبر الـ Constructor
*/
public function __construct(Course_Subscription_Service_Interface $service) {
$this->service = $service;
}
/**
* تسجيل المسارات الرسمية
*/
public function register_routes(): void {
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'],
]
);
}
public function get_items_permissions_check($request): bool|WP_Error {
if (!is_user_logged_in()) {
return new WP_Error('rest_not_logged_in', __('يجب تسجيل الدخول لاستعراض اشتراكاتك.', 'text-domain'), ['status' => 401]);
}
return true;
}
public function get_items($request): WP_REST_Response|WP_Error {
$user_id = get_current_user_id();
$page = (int) $request->get_param('page');
$per_page = (int) $request->get_param('per_page');
$items = $this->service->get_user_enrollments($user_id, $page, $per_page);
$total = $this->service->count_user_enrollments($user_id);
$response_data = [];
foreach ($items as $item) {
$data = $this->prepare_item_for_response($item, $request);
$response_data[] = $this->prepare_response_for_collection($data);
}
$response = new WP_REST_Response($response_data, 200);
$max_pages = ceil($total / $per_page);
$response->header('X-WP-Total', (string) $total);
$response->header('X-WP-TotalPages', (string) $max_pages);
return $response;
}
public function create_item_permissions_check($request): bool|WP_Error {
if (!is_user_logged_in()) {
return new WP_Error('rest_forbidden', __('التسجيل في الدورات متاح فقط للطلاب المسجلين.', 'text-domain'), ['status' => 401]);
}
return true;
}
public function create_item($request): WP_REST_Response|WP_Error {
$user_id = get_current_user_id();
$course_id = (int) $request->get_param('course_id');
$coupon = (string) $request->get_param('coupon');
// تفويض منطق البيزنس لطبقة الخدمة
$result = $this->service->enroll_student($user_id, $course_id, $coupon);
if (is_wp_error($result)) {
return $result;
}
$response = $this->prepare_item_for_response($result, $request);
$response->set_status(201); // 201 Created
$response->header('Location', rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $result['enrollment_id'])));
return $response;
}
public function prepare_item_for_response($item, $request): WP_REST_Response {
$item = (array) $item;
$data = [
'id' => (int) ($item['id'] ?? $item['enrollment_id']),
'course_id' => (int) $item['course_id'],
'paid_amount' => (float) $item['paid_amount'],
'status' => (string) $item['status'],
'created_at' => mysql_to_rfc3339($item['created_at'] ?? $item['enrolled_at']),
];
$response = new WP_REST_Response($data, 200);
$response->add_link('self', rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $data['id'])));
$response->add_link('course', rest_url(sprintf('wp/v2/courses/%d', $data['course_id'])));
return $response;
}
public function get_item_schema(): array {
if ($this->schema) {
return $this->add_additional_fields_schema($this->schema);
}
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'course_enrollment',
'type' => 'object',
'properties' => [
'id' => [
'description' => __('معرف الاشتراك الفريد.', 'text-domain'),
'type' => 'integer',
'readonly' => true,
],
'course_id' => [
'description' => __('معرف الدورة التدريبية المراد الاشتراك بها.', 'text-domain'),
'type' => 'integer',
'required' => true,
'minimum' => 1,
],
'coupon' => [
'description' => __('كوبون الخصم الترويجي إن وجد.', 'text-domain'),
'type' => 'string',
'required' => false,
'sanitize_callback' => 'sanitize_text_field',
],
],
];
return $this->add_additional_fields_schema($this->schema);
}
}
// تسجيل المتحكم مع حقن الخدمة
add_action('rest_api_init', function() {
$service = new Course_Subscription_Service();
$controller = new Enterprise_Courses_REST_Controller($service);
$controller->register_routes();
});
تفكيك الكود وشرح فلسفته المعمارية بالعامية المصرية
ركز معايا يا صديقي في جمال ونظافة المعمارية دي:
- الكنترولر مبيعرفش الداتابيز شكلها إيه: لو بصيت جوة
Enterprise_Courses_REST_Controller، مش هتلاقي ولا سطر$wpdb->get_results()ولا$wpdb->insert()! الكنترولر بيطلب من$this->service->enroll_student()وينتظر النتيجة. لو بكرة الصبح قررنا ننقل جداول الاشتراكات من MySQL إلى MongoDB أو DynamoDB، الكنترولر هيفضل زي ما هو مش هيتغير فيه حرف واحد! - حقن الاعتمادية (Dependency Injection): حقنا
Course_Subscription_Service_Interfaceفي الكنترولر. لو بنعمل Unit Test، بنعمل Fake Service بترجع Mock Data ونتأكد إن الـ Status Codes والترويسات مضبوطة بنسبة 100%. - الالتزام التام بكود الـ HTTP الصحيح: لما الطالب بيشترك بنجاح، الكنترولر مرجعش
200 OK؛ رجع201 Createdمع ترويسةLocationبتشاور على رابط الاشتراك الفردي، وده التزام صارم بمعايير RESTful Architecture العالمية.
دراسة حالة واقعية من السوق المصري: إنقاذ منصة ثانوية عامة من سباغيتي الـ 1,800 سطر
تعال أحكيلك حكاية حقيقية لواحد من أشهر مواقع الكورسات التعليمية لطلاب الثانوية العامة في مصر:
الكابوس البرمجي
المنصة لما بدأت، وظفت مطور جونيور كتب كل العمليات جوة ملف functions.php. كان عامل دالة واحدة طولها 1,800 سطر كود اسمها handle_student_checkout_api()!
الدالة دي كانت بتعمل الآتي في نفس المكان:
1. تقرا من $_POST مباشرة بدون تعقيم.
2. تفحص كود كارت الشحن من فودافون كاش أو فوري.
3. تفتح استعلام SQL مباشر في الداتابيز.
4. تنشئ مستخدم جديد في ووردبريس.
5. تولد كود تشفير الفيديو على Vimeo.
6. تبعت رسالة WhatsApp للطالب.
7. تطبع echo json_encode(['status' => 'ok']); wp_die();!
لحظة الانهيار في موسم الامتحانات
في ليلة امتحان الفيزياء، دخل 15,000 طالب في نفس الساعة يشتروا المراجعة النهائية! السيرفر انهار تماماً في أول 5 دقائق! ليه؟ لأن أي ريكوست يفشل فيه إرسال رسالة الواتساب، الدالة كانت بتعلق وتعمل Timeout، والفلوس تتخصم من فودافون كاش وميتعملش تفعيل للكورس في ووردبريس! أولياء الأمور حاصروا صفحة الفيسبوك بالشكاوى، والمنصة خسرت مبيعات تتجاوز 180,000 جنيه في 3 ساعات فقط!
خطة الإنقاذ المعمارية
- تفكيك الدالة السباغيتي إلى نمط المتحكم والخدمات (Controller-Service): قسمنا الـ 1,800 سطر إلى:
Vodafone_Cash_Gateway_Service(مسؤولة فقط عن الدفع).Vimeo_Video_Protection_Service(مسؤولة عن التشفير).Student_Enrollment_Controller(مسؤول فقط عن الـ HTTP Request/Response).
- ترحيل العمليات البطيئة إلى Async Queue: إرسال رسائل الواتساب وتوليد توكنات الفيديو تم ترحيلها إلى Background Worker عبر Redis Queue، بحيث ريكوست التفعيل يخلص في 35ms فقط!
- كتابة Unit Tests صارمة: اختبرنا كل كلاس خدمة بمفرده للتأكد من معالجة كل حالات فشل الشبكة وانقطاع الدفع دون تعليق الـ API.
جدول نتائج التحول الهندسي
| المؤشر الفني والتشغيلي | قبل التحول (Spaghetti Code) | بعد التحول (Controllers & Services) | نسبة التحسن |
|---|---|---|---|
| متوسط زمن استجابة طلب الاشتراك | 6,800 ms (مع حالات Timeout كثيرة) | 45 ms | تسريع بمقدار 151 ضعفاً |
| معدل فشل العمليات أثناء الذروة | 32.4% فشل وتجميد | 0.01% فقط | استقرار بنسبة 99.99% |
| حجم كود ملف functions.php | أكثر من 3,500 سطر متداخل | 40 سطر تهيئة نظيفة فقط | تخفيض حجم الملف بنسبة 98% |
| زمن إضافة ميزة دفع جديدة (مثل InstaPay) | أسبوعان من التعديل المرعب والمخاطرة | يوم واحد فقط بإنشاء Service جديدة | مرونة وتوسع غير محدود |
الوصايا العشر لبناء متحكمات REST احترافية في ووردبريس
- احرص على أن تكون الـ Controllers نحيفة (Keep Controllers Slim): وظيفة الكنترولر هي التوجيه والتحقق والتغليف؛ لا تضع منطق قواعد البيانات والبيزنس بداخله.
- ارث دائماً من
WP_REST_Controller: لا تعيد اختراع العجلة؛ وراثة الكلاس النواتي تمنحك مجاناً كافة آليات التحقق والـ Schemas والمعايير الرسمية. - احقن الخدمات عبر الـ Constructor (Dependency Injection): لا تستخدم
new Class()داخل الدوال؛ مرر التبعيات لتسهيل عمل Unit Tests واختبارات العزل. - استخدم واجهات برمجية (Interfaces) لخدماتك: الاعتماد على الـ Interfaces يمنحك مرونة استبدال طريقة التنفيذ مستقبلاً دون المساس بطبقة الـ API.
- التزم بتسميات دوال الـ CRUD القياسية: استخدم
get_items،get_item،create_item،update_item، وdelete_itemلتوحيد المعايير بين أعضاء الفريق. - لا تتجاهل دالة
prepare_item_for_response(): هي المكان المخصص لتصفية الحقول الحساسة وإضافة روابط HATEOAS قبل إرسال البيانات للعميل. - اعتمد على
get_item_schema(): وثق كل حقولك وأنواعها وقيمها الافتراضية في الـ Schema؛ سيتولى ووردبريس فحص المدخلات وتوليد أخطاء 400 نيابة عنك. - ارجع أكواد HTTP الدقيقة: استخدم
201 Createdعند الإضافة الناجحة،204 No Contentعند الحذف، و409 Conflictعند تكرار مورد موجود. - عالج الأخطاء دائماً عبر كائن
WP_Error: لا ترمِ مصفوفات عشوائية؛ الكلاس النواتي مهيأ لتحويلWP_Errorإلى ردود JSON مهيكلة فوراً. - افصل ملفات الـ Controllers في مجلدات معمارية منظمة: ضع الكنترولرز في مجلد
includes/Controllers/والخدمات فيincludes/Services/مع استخدام PSR-4 Autoloading.
التعمق المعماري: وراثة المخططات (Schema Inheritance) وتوسيع الحقول عبر register_rest_field
يا صديقي المطور، من أقوى النقاط المعمارية في كلاس WP_REST_Controller هي الطريقة اللي بيتعامل بيها مع الإضافات التانية اللي عايزة تضيف حقول مخصصة لنفس المورد بتاعك بدون ما تعدل في كود الكنترولر الأساسي!
ده بيتم عبر الميثود المعيارية add_additional_fields_schema() اللي بتشوفها في نهاية دالة get_item_schema():
return $this->add_additional_fields_schema( $this->schema );
السطر ده بيفتح باب التوسعة المفتوحة (Open for Extension, Closed for Modification – مبدأ OCP في SOLID).
لو في مطور تاني عايز يضيف حقل “تقييم الدورة” أو “شهادة إتمام الدورة” للاشتراك بتاعك، مش محتاج يعمل Fork للكود بتاعك؛ هو ببساطة هيستخدم الدالة النواتية register_rest_field():
register_rest_field('course_enrollment', 'completion_certificate_url', [
'get_callback' => function($item) {
return get_user_meta($item['user_id'], '_cert_url_' . $item['course_id'], true);
},
'schema' => [
'description' => __('رابط شهادة إتمام الدورة المعتمدة.', 'text-domain'),
'type' => 'string',
'format' => 'uri',
'context' => ['view', 'edit'],
],
]);
بمجرد تسجيل الحقل ده، كلاس WP_REST_Controller هيقوم بدمجه أوتوماتيكياً في شجرة الـ JSON Schema، وفي استجابة الـ API، وفي طلبات الـ OPTIONS التوثيقية! ده قمة النضج المعماري اللي بيخلي نظامك بيئة عمل متكاملة (Platform) مش مجرد كود مقفول على نفسه.
دمج نمط المستودع (Repository Pattern) مع الكنترولر
في المشاريع فائقة الضخامة (Enterprise Scale)، مهندسو النظم بيفصلوا كمان طبقة الوصول لقاعدة البيانات عن الـ Service عبر نمط Repository Pattern.
الـ Service بتكون مسؤولة عن حسابات البيزنس البحتة (Business Rules)، وبتكلم Course_Enrollment_Repository اللي بينفذ استعلامات الـ SQL أو كاش الـ Redis.
ده بيدي الكنترولر استقراراً مطلقاً، لأن أي تغيير في بنية جداول الداتابيز بيتم عزله داخل كلاس المستودع فقط، وبتفضل طبقة الـ REST API محمية من أي تقلبات داخلية.
الأسئلة الشائعة حول متحكمات REST في ووردبريس (20 سؤالاً تقنياً)
1. لماذا يعتبر وراثة الكلاس WP_REST_Controller أفضل من كتابة دوال عادية؟
لأنه يفرض هيكلية معمارية موحدة، ويوفر دوالاً نواتية جاهزة لفحص المدخلات، وتوليد الـ JSON Schema، وتطبيق الصلاحيات، وإدارة الـ Context Filtering تلقائياً.
2. ما هو الفرق الجوهري بين Controller و Service في معمارية البرمجيات؟
الـ Controller يتعامل حصرياً مع بروتوكول HTTP (المعاملات، الترويسات، وحالة الرد). أما الـ Service فتتعامل مع منطق العمل (Business Logic)، وقواعد البيانات، وبوابات الدفع دون معرفة هل الطلب قادم عبر REST أو CLI أو Cron.
3. كيف يساعد حقن التبعيات (Dependency Injection) في اختبار الكنترولر؟
يسمح بتمرير خدمات وهمية (Mock Services) إلى الكنترولر أثناء الاختبار، مما يتيح اختبار استجابات الـ API وأكواد الأخطاء دون الحاجة للاتصال بقاعدة بيانات حية أو بوابات خارجية.
4. ما هي وظيفة الدالة prepare_item_for_response() في الكنترولر؟
وظيفتها أخذ البيانات الخام من قاعدة البيانات أو الخدمة، وتنسيق الحقول، وإزالة البيانات الحساسة ككلمات المرور، وإضافة روابط HATEOAS وتغليفها في كائن WP_REST_Response.
5. كيف تدعم متحكمات ووردبريس مفهوم الـ Context (view vs edit)؟
من خلال تحديد مفتاح context لكل حقل في get_item_schema()؛ فالحقول المصنفة لـ edit فقط لا تظهر للعامة في سياق الـ view إلا للمستخدمين المصرح لهم بتعديل المورد.
6. ما هو الدور الذي تلعبه دالة prepare_response_for_collection()؟
تقوم بتصفية استجابة المورد الفردي عند عرضه ضمن قائمة (Collection)، وإزالة الروابط والحقول غير الضرورية لتخفيف حجم البيانات المنقولة وتسريع التصفح.
7. كيف يمكنني دعم الـ Pagination في دالة get_items() داخل الكنترولر؟
عبر قراءة معامل page و per_page، وتمريرهما للخدمة مع استخدام دالة $this->get_collection_params()، ثم حقن ترويسات X-WP-Total و X-WP-TotalPages في الاستجابة.
8. هل يجب أن يحتوي كل مسار على كلاس Controller منفصل؟
الأفضل معمارياً هو تخصيص Controller لكل مورد رئيسي (مثل Orders_Controller، Products_Controller، Customers_Controller) لتطبيق مبدأ المسؤولية الواحدة وتسهيل الصيانة.
9. ما الذي يحدث إذا لم أقم بتعريف دالة get_item_schema() في الكنترولر؟
لن يتمكن ووردبريس من التحقق التلقائي من صحة المدخلات، ولن يتمكن العملاء الخارجيون من استكشاف بنية الحقول عند طلب OPTIONS، مما يفقد الـ API ميزة التوثيق الذاتي.
10. كيف أتعامل مع الصلاحيات المتعددة لنفس الكنترولر؟
بتعريف دوال فحص صلاحيات مستقلة لكل عملية: مثل get_items_permissions_check للقراءة العامة، و create_item_permissions_check للمسجلين، و delete_item_permissions_check للمديرين فقط.
11. ما هي الفائدة من استخدام دالة get_endpoint_args_for_item_schema()؟
تقوم تلقائياً بتحويل الـ Schema المعرفة للمورد إلى مصفوفة معاملات مقبولة لمسارات الـ POST والـ PUT، مما يضمن تطابق قواعد التحقق مع وثيقة البيانات دون تكرار الكود.
12. هل يمكن استخدام الـ Traits لتشارك الدوال المشتركة بين عدة Controllers؟
نعم، يفضل استخدام Traits لإعادة استخدام الدوال المساعدة مثل دوال التنسيق المالي، أو معالجة الصور، أو التحقق من التوقيعات الرقمية عبر عدة متحكمات مختلفة.
13. كيف أرجع كود حالة HTTP 204 No Content عند حذف مورد؟
في دالة delete_item()، بعد إتمام الحذف بنجاح عبر الخدمة، يمكنك إرجاع استجابة فارغة مع ضبط الحالة عبر $response->set_status(204);.
14. أين يجب تخزين كلاسات الـ Controllers في بنية الإضافة؟
وفق معيار PSR-4، يتم تخزينها داخل المجلد src/Controllers/ أو includes/Controllers/ مع تسمية الملفات بأسلوب يطابق اسم الكلاس، وتحميلها تلقائياً عبر Composer Autoloader.
15. كيف أمنع وصول المستخدم لبيانات مستخدم آخر في دالة get_item()؟
بالتحقق في get_item_permissions_check من أن معرف المورد المطلوب يطابق معرف المستخدم الحالي المسجل get_current_user_id()، أو أن المستخدم يملك صلاحية إدارة عامة كـ manage_options.
16. ما هو دور الوسيط $request الممرر لجميع دوال الكنترولر؟
هو كائن من نوع WP_REST_Request يحتوي على كافة بيانات الطلب المعقمة (Parameters, Headers, Body, Files) ومسار الطلب والميثود المستخدمة.
17. كيف يمكنني اختبار دالة create_item() في الكنترولر برمجياً؟
بإنشاء كائن WP_REST_Request وتعبئته بالبيانات، ثم استدعاء $controller->create_item($request) وفحص كود الحالة ومحتوى الرد في اختبارات PHPUnit.
18. ماذا يجب أن ترجع دالة get_collection_params()؟
ترجع مصفوفة تحدد المعاملات المقبولة في استعلامات القوائم، مثل page و per_page و search و order مع تحديد أنواعها وقيمها الافتراضية والحد الأقصى المسموح به.
19. كيف أربط بين كلاس الكنترولر ونظام الـ Caching في ووردبريس؟
يفضل تطبيق التخزين المؤقت في طبقة الخدمة (Service Layer) باستخدام الـ Object Cache والـ Transients، مع إمكانية إضافة ترويسات ETag و Cache-Control في الكنترولر لاستجابات الـ GET.
20. ما هي القاعدة الذهبية لتفادي السباغيتي كود في مشاريع الـ REST الضخمة؟
القاعدة الذهبية: “اجعل الكنترولر غبياً والخدمة ذكية” (Dumb Controller, Smart Service)؛ الكنترولر لا يفكر في المنطق المعقد، بل يقتصر دوره على التنسيق والتحقق والتغليف فقط.
المراجع الرسمية والتوثيق الأكاديمي
- WordPress Developer Guide: Controller Classes (WP_REST_Controller)
- WordPress Core Class Reference: WP_REST_Controller
- PHP FIG: PSR-4 Autoloading Standard
- Model-View-Controller & Separation of Concerns Architectural Patterns
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler)


اترك تعليقاً
يجب أنت تكون مسجل الدخول لتضيف تعليقاً.