نبدأ رحلتنا في تعلم استخدام Laravel HTTP Client بمثال واقعي بسيط: إنشاء أداة "طقس مباشر" تعرض حالة الطقس الحالية في مدينة غزة، بالاعتماد على OpenWeatherMap API.
حيث اذا كان هناك بيانات بدون أخطاء سنحصل على هذا الشكل
وفي حال وجود خطأ ان يظهر الشكل التالي مع رسالة الخطأ، حيث كما نلاحظ بالصوره أدناه أن الخطأ هو invalid API key
في معظم الأحيان، يجب عليك مراجعة التوثيق الرسمي لأي API خارجي تستخدمه.
في حالة OpenWeatherMap، عند قراءة التوثيق الرسمي، فإن طريقة استدعاء حالة الطقس الحالية تكون كالتالي:
GET https://api.openweathermap.org/data/2.5/weather?lat=51.5074&lon=-0.1279&appid=YOUR_API_KEY&units=metric
حيث نلاحظ أنه يجب أن نحصل على api key ويتم ذلك من بعد التسجيل بالموقع ومن ثم my api keys
حسب التوثيق الرسمي لموقع openweather الإستجابه تكون بالشكل التالي
{ "coord": { "lon": 7.367, "lat": 45.133 }, "weather": [ { "id": 501, "main": "Rain", "description": "moderate rain", "icon": "10d" } ], "base": "stations", "main": { "temp": 284.2, "feels_like": 282.93, "temp_min": 283.06, "temp_max": 286.82, "pressure": 1021, "humidity": 60, "sea_level": 1021, "grnd_level": 910 }, "visibility": 10000, "wind": { "speed": 4.09, "deg": 121, "gust": 3.47 }, "rain": { "1h": 2.73 }, "clouds": { "all": 83 }, "dt": 1726660758, "sys": { "type": 1, "id": 6736, "country": "IT", "sunrise": 1726636384, "sunset": 1726680975 }, "timezone": 7200, "id": 3165523, "name": "Province of Turin", "cod": 200 }
// routes/web.php use App\Http\Controllers\HomeController; Route::get('/', [HomeController::class, 'index']);
namespace App\Http\Controllers; use App\Services\WeatherService; class HomeController extends Controller { public function index(WeatherService $weatherService) { $weatherData = $weatherService->getCurrentWeather(); return view('welcome', compact('weatherData')); } }
📌 الشرح:
في هذا الكنترولر قمنا باستخدام تقنية الحقن (Dependency Injection) لتمرير كائن من WeatherService إلى الدالة index.
الدالة index() تستدعي getCurrentWeather() من كلاس الخدمة (Service)، ثم تُرسل النتيجة إلى واجهة العرض welcome.blade.php.
الهدف من هذا التصميم هو فصل منطق التعامل مع API عن الكنترولر، مما يجعل الكود أكثر تنظيمًا، قابلية للاختبار (testable)، وسهولة في الصيانة.
✅ ملاحظة مهمة:
يفضّل دائمًا عند التعامل مع واجهات خارجية (External APIs) أن تتم هذه العمليات داخل Service Classes مخصصة، وليس مباشرةً في الكنترولر.
ذلك لأن الكنترولر يجب أن يكون مسؤولًا فقط عن استقبال الطلبات وتوجيهها، وليس تنفيذ منطق الأعمال أو التواصل مع الخدمات الخارجية.
الفكرة هنا هي فصل منطق استدعاء الـ API داخل كلاس مستقل لجعل الكود نظيفًا وقابلًا لإعادة الاستخدام:
php artisan make:class Services/WeatherService
<?php namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Exception; class WeatherService { private string $apiKey; private string $baseUrl; public function __construct() { $this->apiKey = config('services.openweather.key'); $this->baseUrl = config('services.openweather.url'); } public function getCurrentWeather(): array { $lat = 31.5017; $lon = 34.4668; if (empty($this->apiKey)) { return $this->getErrorResponse('OpenWeather API key not configured.'); } try { $response = Http::get("{$this->baseUrl}/weather", [ 'lat' => $lat, 'lon' => $lon, 'appid' => $this->apiKey, 'units' => 'metric', ]); if ($response->successful()) { $data = $response->json(); return $this->formatWeatherData($data); } $this->logApiError($response); return $this->getErrorResponse("API error ({$response->status()}): " . ($response->json()['message'] ?? 'Unknown error')); } catch (Exception $e) { Log::error('Weather API exception: ' . $e->getMessage()); return $this->getErrorResponse('Unexpected error occurred.'); } } private function formatWeatherData(array $data): array { return [ 'success' => true, 'city' => $data['name'] ?? 'Unknown', 'country' => $data['sys']['country'] ?? '', 'temperature' => round($data['main']['temp'] ?? 0), 'feels_like' => round($data['main']['feels_like'] ?? 0), 'description' => ucfirst($data['weather'][0]['description'] ?? 'No description'), 'icon' => $data['weather'][0]['icon'] ?? '01d', 'humidity' => $data['main']['humidity'] ?? 0, 'pressure' => $data['main']['pressure'] ?? 0, 'wind_speed' => $data['wind']['speed'] ?? 0, 'visibility' => isset($data['visibility']) ? $data['visibility'] / 1000 : null, 'clouds' => $data['clouds']['all'] ?? 0, 'sunrise' => isset($data['sys']['sunrise']) ? date('H:i', $data['sys']['sunrise']) : null, 'sunset' => isset($data['sys']['sunset']) ? date('H:i', $data['sys']['sunset']) : null, ]; } private function logApiError($response): void { Log::warning("Weather API failed with status {$response->status()}.", [ 'body' => $response->body() ]); } private function getErrorResponse(string $message): array { return [ 'error' => true, 'message' => $message, ]; } }
شرح الكلاس: WeatherService
كلاس WeatherService مسؤول عن الاتصال بواجهة OpenWeather API للحصول على بيانات الطقس الحالية لموقع جغرافي محدد (هنا سوف أستخدم latitude, longitude ثابتين وهم لمدينة غزة، فلسطين).
public function __construct() { $this->apiKey = config('services.openweather.key'); $this->baseUrl = config('services.openweather.url'); }
📌 الوصف:
كما نعلم الدالة __construct() هي دالة خاصة تُنفّذ تلقائيًا عند إنشاء كائن (Object) من هذا الكلاس، وتُستخدم هنا لتهيئة المتغيرات الأساسية المطلوبة للتعامل مع API.
في هذا السياق، نقوم بتخزين:
وفي ملف config/service.php نقوم بتعريف openweather
'openweather' => [ 'key' => env('OPENWEATHER_API_KEY'), 'url' => env('OPENWEATHER_BASE_URL'), ],
وفي ملف .env نقوم بتعريف ووضع قيم المتغيرات
OPENWEATHER_API_KEY=YOUR_API_KEY //from openWeatherMap OPENWEATHER_BASE_URL=https://api.openweathermap.org/data/2.5
✅ لماذا نستخدم config/services.php بدلًا من استخدام env() مباشرة في الكود؟
نحن نستخدم ملف config/services.php كوسيط بين الكود وملف .env، وذلك لأسباب مهمة:
📌 1. تنظيم الكود
بدل ما نكتب env() في كل مكان في الكود، نضعه مرة واحدة في ملف الإعدادات (services.php)، ثم نستدعي القيم عن طريق config()، مثل:
$this->apiKey = config('services.openweather.key');
هذا يجعل الكود أنظف وأسهل للفهم والصيانة.
📌 2. سهولة التبديل بين البيئات
عند تشغيل المشروع في بيئات مختلفة (مثل: محلي - سيرفر - بيئة اختبار)، نغير القيم فقط في ملف .env دون لمس الكود.
📌 3. التوافق مع التخزين المؤقت للإعدادات
Laravel يسمح بتسريع التطبيق بأمر:
php artisan config:cache
✅ هذا الأمر يعمل فقط إذا كانت القيم موجودة في ملفات config وليس في الكود مباشرة.
❌ أما إذا استخدمنا env() داخل الكود، فقد لا تعمل القيم بشكل صحيح بعد تنفيذ هذا الأمر.
public function getCurrentWeather(): array { // إحداثيات الموقع الجغرافي - هنا نستخدم غزة (خط العرض والطول) $lat = 31.5017; $lon = 34.4668; // تحقق من وجود مفتاح API، إذا لم يكن موجودًا نُعيد رسالة خطأ منظمة if (empty($this->apiKey)) { return $this->getErrorResponse('OpenWeather API key not configured.'); } try { // تنفيذ طلب HTTP GET إلى واجهة OpenWeather API مع إرسال المعاملات المطلوبة $response = Http::get("{$this->baseUrl}/weather", [ 'lat' => $lat, // خط العرض 'lon' => $lon, // خط الطول 'appid' => $this->apiKey, // مفتاح API للمصادقة 'units' => 'metric', // استخدام النظام المتري (درجة الحرارة بالـ°C) ]); //حيث سيقوم بتنفيذ الطلب //GET https://api.openweathermap.org/data/2.5/weather?lat=51.5074&lon=-0.1279&appid=YOUR_KEY&units=metric // إذا كانت الاستجابة ناجحة (كود الحالة 2xx) if ($response->successful()) { // تحويل البيانات من JSON إلى مصفوفة PHP $data = $response->json(); // تمرير البيانات إلى دالة formatWeatherData لتنسيقها قبل عرضها return $this->formatWeatherData($data); } // إذا لم تكن الاستجابة ناجحة، نقوم بتسجيل الخطأ في سجل النظام $this->logApiError($response); // نُعيد للمستخدم رسالة خطأ واضحة بناءً على كود الحالة والرسالة القادمة من API return $this->getErrorResponse("API error ({$response->status()}): " . ($response->json()['message'] ?? 'Unknown error')); } catch (Exception $e) { // في حال حدث استثناء غير متوقع أثناء الطلب أو أثناء تحليل الاستجابة Log::error('Weather API exception: ' . $e->getMessage()); // نُعيد رسالة خطأ عامة للمستخدم بدلًا من تعطل التطبيق return $this->getErrorResponse('Unexpected error occurred.'); } // نستخدم try-catch لضمان استقرار التطبيق والتعامل مع أي أخطاء غير متوقعة }
📌 الوصف:
هذه هي الدالة الأساسية التي تنفذ الاتصال الفعلي مع OpenWeather API.
تقوم بما يلي:
private function formatWeatherData(array $data): array { return [ 'success' => true, // توضيح أن العملية تمت بنجاح 'city' => $data['name'] ?? 'Unknown', // اسم المدينة أو "Unknown" إذا لم يكن موجودًا 'country' => $data['sys']['country'] ?? '', // رمز الدولة 'temperature' => round($data['main']['temp'] ?? 0), // درجة الحرارة الحالية 'feels_like' => round($data['main']['feels_like'] ?? 0), // درجة الحرارة المحسوسة 'description' => ucfirst($data['weather'][0]['description'] ?? 'No description'), // وصف الطقس 'icon' => $data['weather'][0]['icon'] ?? '01d', // رمز الأيقونة لعرض صورة الطقس 'humidity' => $data['main']['humidity'] ?? 0, // نسبة الرطوبة 'pressure' => $data['main']['pressure'] ?? 0, // الضغط الجوي 'wind_speed' => $data['wind']['speed'] ?? 0, // سرعة الرياح 'visibility' => isset($data['visibility']) ? $data['visibility'] / 1000 : null, // مدى الرؤية (تحويله من متر إلى كم) 'clouds' => $data['clouds']['all'] ?? 0, // نسبة الغيوم في السماء 'sunrise' => isset($data['sys']['sunrise']) ? date('H:i', $data['sys']['sunrise']) : null, // وقت الشروق 'sunset' => isset($data['sys']['sunset']) ? date('H:i', $data['sys']['sunset']) : null, // وقت الغروب ]; }
📌 الوصف:
private function logApiError($response): void { Log::warning("Weather API failed with status {$response->status()}.", [ 'body' => $response->body() ]); }
📌 الوصف:
هذه الدالة مسؤولة عن توثيق أي خطأ يحدث أثناء استدعاء OpenWeather API في ملفات السجلات (storage/logs/laravel.log).
يتم تسجيل:
✅ استخدمنا Log::warning() بدلًا من Log::error() لأن هذا النوع من الخطأ قادم من خدمة خارجية (وليس من داخل النظام)، وغالبًا لا يتطلب توقف التطبيق، لكنه يستحق المتابعة والتحقيق.
🧠 فائدة هذا التوثيق:
يساعد في تحليل الأسباب إذا توقفت بيانات الطقس عن العمل، دون التأثير على المستخدم.
private function getErrorResponse(string $message): array { return [ 'error' => true, // توضيح أن هذه الاستجابة تمثل حالة فشل 'message' => $message, // رسالة الخطأ التي سيتم عرضها للمستخدم أو تسجيلها ]; }
📌 الوصف:
تُستخدم هذه الدالة لإنشاء هيكل موحد لاستجابات الخطأ التي تُعاد من الدالة getCurrentWeather() في حال فشل الاتصال بالـ API أو حدوث استثناء.
تُرجع مصفوفة تحتوي على:
✅ الفائدة:
ملف .env
OPENWEATHER_API_KEY=your_api_key_here OPENWEATHER_BASE_URL=https://api.openweathermap.org/data/2.5
ملف config/services.php
'openweather' => [ 'key' => env('OPENWEATHER_API_KEY'), 'url' => env('OPENWEATHER_BASE_URL'), ],
Laravel يوفر دوال جاهزة للتعامل مع استجابات HTTP:
$response->successful(); // 2xx $response->ok(); // 200 $response->clientError(); // 4xx $response->serverError(); // 5xx $response->status(); // كود الحالة مثل 200، 404، إلخ
<div class="container"> <div class="row justify-content-center"> <div class="col-lg-8 col-md-10"> <div class="weather-card"> @if(isset($weatherData['error']) && $weatherData['error'] === true) {{-- حالة الخطأ --}} <div class="bg-white rounded shadow p-4 text-center"> <div class="display-3 text-danger mb-3">⚠️</div> <h2 class="h3 text-danger fw-bold mb-3">Weather Service Error</h2> <p class="lead text-muted mb-4">Unable to fetch weather data</p> <div class="alert alert-danger text-start" role="alert"> <strong>{{ $weatherData['message'] ?? 'Unknown error occurred' }}</strong> </div> </div> @else {{-- حالة النجاح --}} <div class="weather-header"> <div class="weather-icon"> @if(isset($weatherData['icon'])) <img src="https://openweathermap.org/img/wn/{{ $weatherData['icon'] }}@4x.png" alt="{{ $weatherData['description'] ?? 'Weather' }}" class="weather-icon"> @endif </div> <h1 class="city-name" id="cityName">{{ $weatherData['city'] }} - {{ $weatherData['country'] }}</h1> <p class="temperature" id="temperature">{{ round($weatherData['temperature'] ?? 0) }}°</p> <p class="weather-description" id="weatherDescription">{{ $weatherData['description'] ?? 'غير متوفر' }}</p> </div> <div class="weather-stats"> <div class="row g-4"> <div class="col-md-4"> <div class="stat-card"> <i class="fas fa-tint stat-icon humidity-icon"></i> <div class="stat-value">{{ $weatherData['humidity'] ?? '--' }}%</div> <div class="stat-label">الرطوبة</div> </div> </div> <div class="col-md-4"> <div class="stat-card"> <i class="fas fa-thermometer-half stat-icon pressure-icon"></i> <div class="stat-value">{{ $weatherData['pressure'] ?? '--' }}</div> <div class="stat-label">الضغط الجوي (hPa)</div> </div> </div> <div class="col-md-4"> <div class="stat-card"> <i class="fas fa-wind stat-icon wind-icon"></i> <div class="stat-value">{{ $weatherData['wind_speed'] ?? '--' }}</div> <div class="stat-label">سرعة الرياح (km/h)</div> </div> </div> </div> </div> @endif </div> </div> </div> </div>
📌 كما نلاحظ في ملف Blade، قمنا بالتحقق أولًا مما إذا كانت هناك بيانات ($weatherData) ثم تأكدنا من وجود خطأ داخل هذه البيانات من خلال:
@if(isset($weatherData['error']) && $weatherData['error'] === true)
✅ هذا الشرط يُستخدم لتحديد ما إذا كانت الاستجابة تحتوي على خطأ (أي أن جلب الطقس فشل).
إذا تحقق الشرط، يتم عرض رسالة تنبيه للمستخدم تفيد بوجود مشكلة في الاتصال بواجهة الطقس.
هذا الدرس يُعدّ حجر الأساس للتعامل مع أي API خارجي في Laravel. باستخدام Http::get() وتنظيم الكود في Service Classes:
✅ الفوائد التعليمية من هذا الدرس:
📚 تابع الدورة لتوسيع مهاراتك في العمل مع Laravel وواجهات الـ API الاحترافية.
يمكنك استعراض وتنزيل المشروع عبر GitHub من خلال الرابط التالي: