أساسيات Eloquent ORM
أساسيات Eloquent ORM
Eloquent هو نظام التعيين العلائقي للكائنات (ORM) القوي والأنيق في Laravel. إنه يوفر تطبيقًا جميلًا وبسيطًا لـ ActiveRecord للعمل مع قاعدة البيانات الخاصة بك. كل جدول في قاعدة البيانات له "نموذج" مقابل يُستخدم للتفاعل مع هذا الجدول.
ما هو ORM؟
يتيح لك ORM (التعيين العلائقي للكائنات) التفاعل مع قاعدة البيانات باستخدام الكائنات والطرق بدلاً من كتابة استعلامات SQL الخام. يجعل Eloquent تفاعلات قاعدة البيانات تبدو طبيعية وبديهية.
إنشاء أول نموذج لك
استخدم أمر Artisan لإنشاء نموذج جديد:
# إنشاء نموذج
php artisan make:model Product
# إنشاء نموذج مع هجرة
php artisan make:model Product -m
# إنشاء نموذج مع هجرة ومصنع وباذر
php artisan make:model Product -mfs
# إنشاء نموذج مع جميع الخيارات
php artisan make:model Product --all
ينشئ هذا ملف نموذج في app/Models/Product.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
}
اصطلاحات التسمية في Eloquent
يتبع Eloquent اصطلاحات التسمية التي تقلل من التكوين. فهم هذه الاصطلاحات يساعدك على كتابة كود أنظف:
// اصطلاح اسم الجدول
// النموذج: Product → الجدول: products (جمع، أحرف صغيرة)
// النموذج: Category → الجدول: categories
// النموذج: OrderItem → الجدول: order_items (snake_case)
// اصطلاح المفتاح الأساسي
// افتراضي: عمود 'id' (عدد صحيح تلقائي التزايد)
// اصطلاح الطوابع الزمنية
// افتراضي: أعمدة created_at و updated_at
تخصيص الاصطلاحات
يمكنك تجاوز هذه الاصطلاحات عند الحاجة:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
// اسم جدول مخصص
protected $table = 'my_products';
// مفتاح أساسي مخصص
protected $primaryKey = 'product_id';
// مفتاح أساسي غير متزايد
public $incrementing = false;
// مفتاح أساسي غير عددي (مثل UUID)
protected $keyType = 'string';
// تعطيل الطوابع الزمنية
public $timestamps = false;
// أسماء أعمدة الطوابع الزمنية المخصصة
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
// اتصال قاعدة بيانات مخصص
protected $connection = 'mysql2';
}
حماية التعيين الشامل
التعيين الشامل هو ميزة أمان تحمي من الثغرات. يجب عليك تحديد السمات التي يمكن تعيينها بشكل جماعي:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
// Fillable: نهج القائمة البيضاء (موصى به)
protected $fillable = [
'name',
'description',
'price',
'quantity',
'category_id',
];
// Guarded: نهج القائمة السوداء
// protected $guarded = ['id', 'created_at'];
// السماح لجميع السمات (غير موصى به)
// protected $guarded = [];
}
protected $guarded = []; دون فهم الآثار الأمنية. حدد دائمًا السمات القابلة للتعبئة بشكل صريح أو احمِ الحقول الحساسة بعناية.
عمليات CRUD مع Eloquent
إنشاء السجلات
<?php
use App\Models\Product;
// الطريقة 1: إنشاء وحفظ
$product = new Product;
$product->name = 'Laptop';
$product->description = 'High-performance laptop';
$product->price = 999.99;
$product->quantity = 50;
$product->save();
// الطريقة 2: التعيين الشامل مع create()
$product = Product::create([
'name' => 'Mouse',
'description' => 'Wireless mouse',
'price' => 29.99,
'quantity' => 100,
]);
// الطريقة 3: firstOrCreate() - البحث أو الإنشاء
$product = Product::firstOrCreate(
['name' => 'Keyboard'],
['price' => 79.99, 'quantity' => 30]
);
// الطريقة 4: updateOrCreate() - البحث والتحديث أو الإنشاء
$product = Product::updateOrCreate(
['name' => 'Monitor'],
['price' => 299.99, 'quantity' => 20]
);
قراءة السجلات
<?php
use App\Models\Product;
// استرجاع جميع السجلات
$products = Product::all();
// البحث بالمفتاح الأساسي
$product = Product::find(1);
// البحث أو الفشل (يرمي 404 إذا لم يُعثر عليه)
$product = Product::findOrFail(1);
// البحث بعمود آخر
$product = Product::where('name', 'Laptop')->first();
// الحصول على سجلات متعددة بشروط
$products = Product::where('price', '<', 100)->get();
// عد السجلات
$count = Product::where('quantity', '>', 0)->count();
// التحقق من وجود سجل
$exists = Product::where('name', 'Laptop')->exists();
// الحصول على القيم القصوى/الدنيا
$maxPrice = Product::max('price');
$minPrice = Product::min('price');
// الحصول على المجموع/المتوسط
$totalValue = Product::sum('price');
$avgPrice = Product::avg('price');
تحديث السجلات
<?php
use App\Models\Product;
// الطريقة 1: البحث والتحديث
$product = Product::find(1);
$product->price = 899.99;
$product->save();
// الطريقة 2: التحديث الشامل
Product::where('quantity', '<', 10)
->update(['status' => 'low_stock']);
// الطريقة 3: التحديث أو الإنشاء
$product = Product::updateOrCreate(
['name' => 'Tablet'],
['price' => 499.99, 'quantity' => 25]
);
// الزيادة/النقصان
$product = Product::find(1);
$product->increment('quantity'); // إضافة 1
$product->increment('quantity', 5); // إضافة 5
$product->decrement('quantity', 3); // طرح 3
// الزيادة مع تحديثات إضافية
$product->increment('views', 1, [
'last_viewed_at' => now()
]);
حذف السجلات
<?php
use App\Models\Product;
// الطريقة 1: البحث والحذف
$product = Product::find(1);
$product->delete();
// الطريقة 2: الحذف بالمفتاح الأساسي
Product::destroy(1);
Product::destroy([1, 2, 3]);
// الطريقة 3: الحذف بشروط
Product::where('quantity', 0)->delete();
// الحذف القسري (تجاوز الحذف الناعم)
$product->forceDelete();
نطاقات الاستعلام
تتيح لك النطاقات تحديد قيود الاستعلام الشائعة التي يمكنك إعادة استخدامها بسهولة في جميع أنحاء تطبيقك.
النطاقات المحلية
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['name', 'price', 'quantity', 'is_active'];
// نطاق: المنتجات النشطة
public function scopeActive($query)
{
return $query->where('is_active', true);
}
// نطاق: المنتجات في المخزون
public function scopeInStock($query)
{
return $query->where('quantity', '>', 0);
}
// نطاق: المنتجات باهظة الثمن (مع معامل)
public function scopeExpensive($query, $minPrice = 100)
{
return $query->where('price', '>=', $minPrice);
}
// نطاق: نطاق السعر
public function scopePriceBetween($query, $min, $max)
{
return $query->whereBetween('price', [$min, $max]);
}
}
استخدام النطاقات في استعلاماتك:
// استخدام نطاق واحد
$products = Product::active()->get();
// ربط نطاقات متعددة
$products = Product::active()
->inStock()
->expensive(200)
->get();
// الجمع مع طرق الاستعلام الأخرى
$products = Product::active()
->priceBetween(50, 200)
->orderBy('name')
->limit(10)
->get();
الحذف الناعم
يسمح لك الحذف الناعم "بحذف" السجلات دون إزالتها فعليًا من قاعدة البيانات. يتم وضع علامة عليها بطابع زمني deleted_at.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
protected $fillable = ['name', 'price', 'quantity'];
}
// استخدام الحذف الناعم
$product = Product::find(1);
$product->delete(); // يضع طابع زمني deleted_at
// الاستعلامات تستبعد تلقائيًا السجلات المحذوفة ناعمًا
$products = Product::all(); // غير المحذوفة فقط
// تضمين السجلات المحذوفة ناعمًا
$products = Product::withTrashed()->get();
// الحصول فقط على السجلات المحذوفة ناعمًا
$products = Product::onlyTrashed()->get();
// استعادة سجل محذوف ناعمًا
$product = Product::withTrashed()->find(1);
$product->restore();
// الحذف القسري (دائم)
$product->forceDelete();
deleted_at إلى هجرتك باستخدام $table->softDeletes();
الموصلات والمعدلات
تتيح لك الموصلات تنسيق قيم سمات Eloquent عند استردادها. تتيح لك المعدلات تنسيق القيم عند تعيينها.
الموصلات (Getters)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Product extends Model
{
// موصل للاسم (أحرف كبيرة تلقائيًا)
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => strtoupper($value),
);
}
// موصل للسعر (منسق)
protected function price(): Attribute
{
return Attribute::make(
get: fn (float $value) => number_format($value, 2),
);
}
// موصل افتراضي (سمة محسوبة)
protected function fullDescription(): Attribute
{
return Attribute::make(
get: fn () => "{$this->name} - \${$this->price}",
);
}
}
// الاستخدام
$product = Product::find(1);
echo $product->name; // LAPTOP
echo $product->price; // 999.99
echo $product->full_description; // LAPTOP - $999.99
المعدلات (Setters)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Product extends Model
{
// معدل للاسم (تخزين كأحرف صغيرة)
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
// معدل للسعر (التأكد من القيمة الموجبة)
protected function price(): Attribute
{
return Attribute::make(
set: fn (float $value) => max(0, $value),
);
}
}
// الاستخدام
$product = new Product;
$product->name = 'GAMING MOUSE'; // يُخزن كـ 'gaming mouse'
$product->price = -50; // يُخزن كـ 0
تحويل السمات
تحويل السمات إلى أنواع بيانات شائعة تلقائيًا:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $casts = [
'price' => 'decimal:2',
'quantity' => 'integer',
'is_active' => 'boolean',
'options' => 'array',
'specifications' => 'json',
'published_at' => 'datetime',
'discount_rate' => 'float',
];
}
// الاستخدام
$product = Product::find(1);
// تحويل منطقي
if ($product->is_active) {
// يعمل مع 0/1، true/false، 'yes'/'no'
}
// تحويل المصفوفة (يُخزن كـ JSON)
$product->options = ['color' => 'red', 'size' => 'large'];
$product->save();
// استرجاع المصفوفة
$color = $product->options['color'];
// تحويل التاريخ والوقت
$published = $product->published_at;
echo $published->format('Y-m-d');
echo $published->diffForHumans(); // 'منذ يومين'
قم بإنشاء نموذج Post بالميزات التالية:
- السمات القابلة للتعبئة: title, slug, content, excerpt, is_published, published_at
- الحذف الناعم ممكّن
- تحويل
is_publishedإلى boolean - تحويل
published_atإلى datetime - إنشاء نطاق
published()يصفي المنشورات المنشورة - إنشاء نطاق
recent()يرتب حسب published_at تنازليًا - إنشاء موصل لـ
excerptيحده إلى 150 حرفًا
أضف النطاقات التالية إلى نموذج المنتج الخاص بك:
scopeLowStock($query, $threshold = 10)- المنتجات ذات الكمية أقل من العتبةscopeDiscount($query, $percentage)- المنتجات ذات الخصم >= النسبة المئويةscopeCategory($query, $categoryId)- المنتجات في فئة محددةscopeSearch($query, $term)- البحث في الاسم والوصف
ثم اكتب استعلامات تجمع نطاقات متعددة.
قم بإنشاء نظام إدارة منتجات بسيط بهذه العمليات:
- إنشاء 5 منتجات بأسعار وكميات مختلفة
- البحث عن جميع المنتجات ذات السعر > 100
- تحديث كمية منتج محدد
- زيادة عدد المشاهدات لمنتج
- حذف منتج ناعمًا
- استعادة المنتج المحذوف ناعمًا
- حذف منتج نهائيًا (حذف قسري)
- حساب قيمة المخزون الإجمالية (مجموع السعر * الكمية)
أفضل الممارسات
- استخدم دائمًا حماية التعيين الشامل: حدد
$fillableأو$guardedلمنع الثغرات الأمنية. - استخدم نطاقات الاستعلام: قم بتغليف الاستعلامات الشائعة في نطاقات لإعادة الاستخدام والقراءة.
- استفد من الحذف الناعم: استخدم الحذف الناعم للسجلات التي قد تحتاج إلى استرداد.
- تحويل السمات: استخدم تحويل السمات لضمان صحة أنواع البيانات واتساقها.
- استخدم الموصلات بحكمة: لا تقم بإجراء حسابات ثقيلة في الموصلات؛ إنها تعمل في كل مرة يتم فيها الوصول إلى السمة.
- اتبع اصطلاحات التسمية: التزم باصطلاحات Laravel لكتابة كود تكوين أقل.
- استخدم findOrFail(): في وحدات التحكم، استخدم
findOrFail()لإرجاع 404 تلقائيًا للسجلات المفقودة.
الخلاصة
في هذا الدرس، أتقنت:
- إنشاء وتكوين نماذج Eloquent
- فهم اصطلاحات التسمية في Eloquent
- تنفيذ حماية التعيين الشامل
- إجراء عمليات CRUD بصيغة أنيقة
- إنشاء واستخدام نطاقات الاستعلام
- تنفيذ الحذف الناعم لإزالة البيانات بأمان
- استخدام الموصلات والمعدلات لتنسيق البيانات
- تحويل السمات إلى أنواع البيانات المناسبة
يجعل Eloquent عمليات قاعدة البيانات بديهية وتعبيرية. مع إتقان هذه الأساسيات، أنت مستعد لاستكشاف العلاقات في الدرس التالي!