طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 132 · تقنيات تطوير الواجهات الخلفية
الدرس 14 · الوحدة 6: الدمج مع الخدمات وواجهات برمجة التطبيقات

استهلاك خدمة خارجية

حين يحتاج خادمك بيانات من خادم آخر. ⏱ نحو 30 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح دور خادمك عميلًا لخدمة خارجية، ولماذا يبقى المفتاح على الخادم.
  • ترسل طلبات HTTP من PHP بـ cURL مع مهلة.
  • تتعامل مع أعطال الخدمة وأشكال ردودها، وترد بـ 502 عند تعطلها.
  • تخزّن الردود مؤقتًا وتحفظ المفاتيح السرية خارج الكود.

١الخادم عميلًا لخادم آخر

كثير من الوظائف لا تبنيها بنفسك، بل تطلبها من خدمة خارجية عبر API: أسعار العملات، والطقس، وبوابات الدفع، وإرسال الرسائل النصية، والخرائط. هنا يصير خادمك عميلًا: يرسل طلب HTTP إلى خادم آخر، ويستقبل JSON، ثم يستخدمه في رده على المستخدم.

الخطوةمن؟
1. المتصفح يطلب صفحة التحويلالمستخدم ← خادمك
2. خادمك يطلب أسعار العملاتخادمك ← خادم الخدمة
3. الخدمة ترد بـ JSONخادم الخدمة ← خادمك
4. خادمك يحسب ويرد بصفحةخادمك ← المستخدم

لماذا لا يطلب المتصفح الخدمة مباشرة؟ لأن أغلب الخدمات تحتاج مفتاحًا سريًّا API key، ومفتاح في المتصفح يقرؤه أي أحد. فالمفتاح يبقى على خادمك فقط.

٢الطلب الحقيقي بـ cURL

في PHP مكتبة cURL لإرسال طلبات HTTP (مفعّلة في XAMPP). هذه دالة صغيرة نعيد استخدامها، تعيد رمز الحالة والجسم:

<?php// http.php: النسخة الحقيقية (على XAMPP أو الخادم المنشور)function http_get(string $url, array $headers = []): array{    $ch = curl_init($url);    curl_setopt_array($ch, [        CURLOPT_RETURNTRANSFER => true,      // أعد الجسم بدل طباعته        CURLOPT_TIMEOUT        => 5,         // لا تنتظر أكثر من 5 ثوانٍ        CURLOPT_HTTPHEADER     => $headers,  // مثل: Authorization: Bearer <المفتاح>        CURLOPT_USERAGENT      => 'tatbeeqha-course',    ]);    $body = curl_exec($ch);    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);    $error = curl_error($ch);    curl_close($ch);     if ($body === false) {        return [0, $error];                   // فشل الاتصال نفسه    }    return [$status, $body];}

٣في المختبر: خدمة محاكاة

المختبر يعمل داخل متصفحك بلا اتصال بالإنترنت، فلن يصل cURL لأي خادم. لذلك نستخدم نسخة من http_get بالتوقيع نفسه تقرأ الردود من ملفات محلية. وهذه فكرة يستخدمها المحترفون في الاختبار: كود التطبيق لا يعرف أن الخدمة محاكاة، ولا يتغيّر حين تستبدلها بالحقيقية:

<?phprequire __DIR__ . '/http.php'; $amount = (float) ($_GET['amount'] ?? 100);$to = strtoupper($_GET['to'] ?? 'USD'); [$status, $body] = http_get('https://api.example.com/rates?base=SAR');if ($status !== 200) {    http_response_code(502);    exit('خدمة الأسعار غير متاحة الآن');}$data = json_decode($body, true);$rate = $data['rates'][$to] ?? null;if ($rate === null) {    http_response_code(422);    exit("العملة $to غير مدعومة");}echo "$amount ريال = " . round($amount * $rate, 2) . " $to (أسعار {$data['date']})";

الحالة الثالثة: الخدمة ردت بنجاح لكن لا يوجد فيها الين، فرددنا 422. وإن تعطلت الخدمة نفسها نرد 502 Bad Gateway: «الخادم الذي خلفي فشل».

٤حين تفشل الخدمة

الخدمة الخارجية ليست تحت سيطرتك: قد تبطئ، أو تتعطل، أو تغيّر شكل ردها، أو ترفض لأنك تجاوزت حد الطلبات. الكود الجيد يتوقع ذلك:

المشكلةالعلاج
الخدمة بطيئة جدًّامهلة قصيرة CURLOPT_TIMEOUT، فلا يعلق مستخدمك.
الخدمة متعطلة (5xx) أو لا اتصالرسالة لطيفة ورمز 502، أو آخر نسخة محفوظة.
تجاوزت حد الطلبات (429)خزّن الرد مؤقتًا ولا تطلب كل مرة.
الرد بشكل غير متوقعتحقق من وجود المفاتيح بـ ?? قبل استخدامها.

والتخزين المؤقت Caching يحل أكثر هذه المشكلات معًا: أسعار العملات لا تتغير كل ثانية، فاحفظ الرد في ملف، واستخدمه ساعة كاملة قبل طلب جديد:

<?phpfunction cached_get(string $url, int $ttl = 3600): array{    $file = sys_get_temp_dir() . '/cache-' . md5($url) . '.json';    if (file_exists($file) && time() - filemtime($file) < $ttl) {        return [200, file_get_contents($file)];    // من الذاكرة المؤقتة    }    [$status, $body] = http_get($url);    if ($status === 200) {        file_put_contents($file, $body);           // احفظ للمرات القادمة    } elseif (file_exists($file)) {        return [200, file_get_contents($file)];    // الخدمة تعطلت: استخدم آخر نسخة    }    return [$status, $body];}

٥أين تحفظ المفتاح السري؟

أغلب الخدمات تعطيك مفتاحًا API key يُرسل مع كل طلب. والقاعدة: المفتاح لا يُكتب في الكود الذي قد تشاركه أو ترفعه إلى GitHub، بل في متغير بيئة أو ملف إعداد خارج المجلد العام:

<?php// inc/config.php (خارج public، ولا يُرفع إلى GitHub)return [    'rates_key' => getenv('RATES_API_KEY') ?: 'ضع-المفتاح-في-البيئة',]; // في الاستخدام:$config = require __DIR__ . '/../inc/config.php';[$status, $body] = http_get($url, ['Authorization: Bearer ' . $config['rates_key']]);

توقّع وتحقّق

لماذا يطلب خادمك الخدمة بدل المتصفح مباشرة؟

المفتاح في المتصفح مكشوف.

الرمز المناسب حين تتعطل الخدمة التي يعتمد عليها خادمك:

Bad Gateway.

ما فائدة CURLOPT_TIMEOUT؟

مهلة.

التخزين المؤقت للرد يفيد في:

Caching.

الخلاصة

تحدٍّ برمجي: حالة الطقس للمعهد

متوسط

اكتب weather.php: تطلب حالة الطقس لمدينة من خدمة (محاكاة عبر http_get)، وتعرضها. الملفات http.php وردود الخدمة جاهزة.

المطلوب:
  • رابط الخدمة: https://api.example.com/weather?city= متبوعًا باسم المدينة من $_GET['city'] (الافتراضي madinah).
  • الرد JSON فيه temp و desc. اعرض: <p>madinah: 31° · مشمس</p>
  • إن كان رمز الحالة من الخدمة غير 200: رد بـ 502 والنص خدمة الطقس غير متاحة.
  • استخدم http_get ولا تقرأ ملفات mock مباشرة.
👀 اعرض الاستجابة المطلوبة
✓ التحقق الآلي: يرسل المختبر هذه الطلبات إلى خادمك بالترتيب ثم يفحص الاستجابات
  1. GET /weather.php
  2. GET /weather.php?city=abha
  3. GET /weather.php?city=tabuk
  • الكود يعمل بلا أخطاء أو تحذيرات
  • بلا مدينة ← madinah: 31° · مشمس
  • abha ← abha: 19° · غائم جزئيًّا
  • مدينة لا تعرفها الخدمة ← 502
  • 502 مع النص المطلوب
  • تستخدم http_get
  • لا تقرأ ملفات mock مباشرة

💡 تلميح 1
[$status, $body] = http_get('https://api.example.com/weather?city=' . $city);
💡 تلميح 2
ثم json_decode($body, true) واقرأ temp و desc.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

<?phprequire __DIR__ . '/http.php'; $city = $_GET['city'] ?? 'madinah';[$status, $body] = http_get('https://api.example.com/weather?city=' . $city); if ($status !== 200) {    http_response_code(502);    exit('خدمة الطقس غير متاحة');}$w = json_decode($body, true);echo "<p>$city: {$w['temp']}° · {$w['desc']}</p>";
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس