استعلامات Eloquent المتقدمة في Laravel
استعلامات Eloquent المتقدمة في Laravel
بينما تكون استعلامات Eloquent الأساسية قوية، يوفر Laravel إمكانيات استعلام متقدمة لسيناريوهات استرجاع البيانات المعقدة. يستكشف هذا الدرس تقنيات الاستعلام المتطورة التي ستساعدك على بناء عمليات قاعدة بيانات فعالة.
تقنيات Query Builder المتقدمة
بعيداً عن جمل where الأساسية، يوفر query builder في Laravel طرقاً متقدمة للشروط المعقدة:
// Where مع استعلامات فرعية
$users = User::where('votes', '>', function ($query) {
$query->selectRaw('avg(votes)')
->from('users')
->where('status', 'active');
})->get();
// whereColumn - مقارنة عمودين
$products = Product::whereColumn('sale_price', '<', 'regular_price')->get();
// whereIn مع استعلام فرعي
$activeUsers = User::whereIn('id', function ($query) {
$query->select('user_id')
->from('orders')
->where('created_at', '>=', now()->subMonth());
})->get();
// whereBetween مع التواريخ
$recentOrders = Order::whereBetween('created_at', [
now()->subDays(7),
now()
])->get();
// whereDate, whereMonth, whereDay, whereYear
$todayOrders = Order::whereDate('created_at', today())->get();
$decemberSales = Sale::whereMonth('created_at', 12)->get();
التعبيرات الخام وأمان SQL Injection
أحياناً تحتاج إلى SQL خام للعمليات المعقدة. يوفر Laravel طرقاً آمنة لتضمين التعبيرات الخام:
// selectRaw للأعمدة المحسوبة
$users = User::selectRaw('name, email, age * 12 as months_lived')
->get();
// whereRaw مع الربط (يمنع SQL injection)
$orders = Order::whereRaw('price > IF(status = "premium", ?, ?)', [100, 50])
->get();
// orderByRaw للفرز المخصص
$products = Product::orderByRaw('FIELD(category, "featured", "new", "sale")')
->get();
// havingRaw لشروط التجميع
$results = DB::table('orders')
->select('customer_id', DB::raw('SUM(price) as total'))
->groupBy('customer_id')
->havingRaw('SUM(price) > ?', [1000])
->get();
// DB::raw للتعبيرات المعقدة
$users = User::select(DB::raw('COUNT(*) as user_count, status'))
->where('status', '<>', 'deleted')
->groupBy('status')
->get();
تقسيم مجموعات النتائج الكبيرة
عند التعامل مع آلاف السجلات، قد يسبب تحميلها جميعاً في الذاكرة مشاكل. التقسيم يعالج النتائج في دفعات أصغر:
// Chunk - يعالج في دفعات
User::where('active', true)->chunk(200, function ($users) {
foreach ($users as $user) {
// معالجة كل مستخدم
$user->sendMonthlyReport();
}
});
// chunkById - أكثر كفاءة لمجموعات البيانات الكبيرة (يستخدم ترتيب ID)
Order::where('status', 'pending')
->chunkById(100, function ($orders) {
foreach ($orders as $order) {
$order->process();
}
}, $column = 'id');
// Cursor - يحمل واحداً تلو الآخر بشكل كسول (كفاءة في الذاكرة)
foreach (User::where('votes', '>', 100)->cursor() as $user) {
// يتم تحميل مستخدم واحد فقط في الذاكرة في كل مرة
$user->calculateStatistics();
}
// lazy - مشابه لـ cursor ولكن مع تحكم أفضل
User::where('active', true)->lazy()->each(function ($user) {
// معالجة المستخدم
$user->updateMetrics();
});
// lazyById - أكثر كفاءة لمجموعات البيانات الضخمة جداً
User::where('subscribed', true)
->lazyById(500)
->each(function ($user) {
$user->sendNewsletter();
});
chunk() للمعالجة الدفعية، cursor() أو lazy() عند المعالجة واحداً تلو الآخر، وchunkById() أو lazyById() لأفضل أداء على الجداول الضخمة جداً.
الدوال التجميعية والتجميع المتقدم
يوفر Laravel طرقاً للحسابات الإحصائية والتجميع المعقد:
// التجميعات الأساسية
$totalOrders = Order::count();
$averagePrice = Order::avg('price');
$maxPrice = Order::max('price');
$minPrice = Order::min('price');
$totalRevenue = Order::sum('price');
// التجميع مع التجميعات
$salesByCategory = Product::select('category', DB::raw('COUNT(*) as count'))
->groupBy('category')
->get();
// جمل Having مع التجميعات
$activeCustomers = Order::select('customer_id', DB::raw('COUNT(*) as order_count'))
->groupBy('customer_id')
->having('order_count', '>', 5)
->get();
// أعمدة groupBy متعددة
$monthlySales = Order::select(
DB::raw('YEAR(created_at) as year'),
DB::raw('MONTH(created_at) as month'),
DB::raw('SUM(price) as revenue')
)
->groupBy('year', 'month')
->orderBy('year', 'desc')
->orderBy('month', 'desc')
->get();
// التجميعات الشرطية
$stats = Order::selectRaw('
COUNT(*) as total_orders,
SUM(CASE WHEN status = "completed" THEN 1 ELSE 0 END) as completed,
SUM(CASE WHEN status = "pending" THEN 1 ELSE 0 END) as pending,
AVG(price) as average_price
')->first();
الجمل الشرطية والاستعلامات الديناميكية
بناء الاستعلامات ديناميكياً بناءً على الشروط دون جمل if/else فوضوية:
// when() - تضيف جملة فقط إذا كان الشرط صحيحاً
$sortBy = request('sort_by');
$products = Product::when($sortBy, function ($query, $sortBy) {
return $query->orderBy($sortBy);
})->get();
// when() مع callback افتراضي
$role = request('role');
$users = User::when($role, function ($query, $role) {
// يُطبق عندما يكون role موجوداً
return $query->where('role', $role);
}, function ($query) {
// يُطبق عندما لا يكون role موجوداً (افتراضي)
return $query->where('role', 'user');
})->get();
// unless() - عكس when()
$showInactive = request('show_inactive');
$users = User::unless($showInactive, function ($query) {
// يُطبق فقط عندما يكون showInactive خطأ
return $query->where('active', true);
})->get();
// مثال بحث عملي
$search = request('search');
$category = request('category');
$minPrice = request('min_price');
$maxPrice = request('max_price');
$products = Product::query()
->when($search, function ($query, $search) {
$query->where('name', 'like', "%{$search}%");
})
->when($category, function ($query, $category) {
$query->where('category_id', $category);
})
->when($minPrice, function ($query, $minPrice) {
$query->where('price', '>=', $minPrice);
})
->when($maxPrice, function ($query, $maxPrice) {
$query->where('price', '<=', $maxPrice);
})
->get();
when() وunless() يحافظ على كود بناء الاستعلام نظيفاً وقابلاً للقراءة، متجنباً جمل if المتداخلة بعمق.
الاستعلامات الفرعية في جمل Select
قم بتضمين البيانات المحسوبة من الجداول المرتبطة بكفاءة باستخدام الاستعلامات الفرعية:
// إضافة نتائج الاستعلام الفرعي كأعمدة
$users = User::select(['name', 'email'])
->selectSub(function ($query) {
$query->from('orders')
->whereColumn('user_id', 'users.id')
->selectRaw('COUNT(*)');
}, 'orders_count')
->selectSub(function ($query) {
$query->from('orders')
->whereColumn('user_id', 'users.id')
->selectRaw('SUM(price)');
}, 'total_spent')
->get();
// استخدام addSelect لاستعلامات فرعية إضافية
$products = Product::select('id', 'name', 'price')
->addSelect([
'reviews_avg' => Review::selectRaw('AVG(rating)')
->whereColumn('product_id', 'products.id'),
'reviews_count' => Review::selectRaw('COUNT(*)')
->whereColumn('product_id', 'products.id')
])
->get();
// ترتيب الاستعلام الفرعي
$users = User::orderBy(
Order::selectRaw('COUNT(*)')->whereColumn('user_id', 'users.id'),
'desc'
)->get();
الخلاصة
استعلامات Eloquent المتقدمة تمنحك القوة للتعامل مع استرجاع البيانات المعقدة بكفاءة:
- الاستعلامات الفرعية: تمكّن من التصفية المعقدة والأعمدة المحسوبة
- التعبيرات الخام: توفر مرونة SQL مع الأمان من خلال الربط
- التقسيم: يعالج مجموعات البيانات الكبيرة دون مشاكل في الذاكرة
- الجمل الشرطية: بناء الاستعلامات الديناميكية بشكل نظيف مع when() و unless()
- التجميعات: حساب الإحصائيات مباشرة في قاعدة البيانات
في الدرس التالي، سنستكشف API Resources وكيفية تحويل نماذج Eloquent لاستجابات API.