استخدام Seeder وFactory لتوليد بيانات عشوائية في Laravel وكيفية إنشاء بيانات باللغة العربية
أثناء تطوير تطبيق باستخدام Laravel نحتاج غالباً إلى وجود كمية من البيانات داخل قاعدة البيانات حتى نستطيع اختبار صفحات التطبيق وعمليات CRUD والعلاقات بين الجداول والبحث والفلاتر وواجهات API وغيرها من وظائف النظام.
بالطبع يمكن إدخال البيانات يدوياً من خلال لوحة التحكم أو مباشرة من قاعدة البيانات، لكن ماذا لو كنا بحاجة إلى 100 مستخدم و500 مقال وآلاف السجلات لاختبار التطبيق؟
إدخال هذه البيانات يدوياً سيكون مضيعة للوقت، ولهذا يوفر Laravel أداتين مهمتين:
- Database Seeders
- Model Factories
كما تتكامل Factories مع مكتبة Faker التي تسمح لنا بتوليد أسماء وعناوين وبريد إلكتروني وأرقام هواتف ونصوص وتواريخ والكثير من البيانات الوهمية بشكل تلقائي.
وفي هذا المقال سنتعرف على Seeder وFactory، والفرق بينهما، وكيفية استخدام Faker، بالإضافة إلى كيفية توليد بيانات باللغة العربية.
ما هي Database Seeding في Laravel؟
Database Seeding هي عملية تعبئة قاعدة البيانات بمجموعة من البيانات بشكل برمجي.
بدلاً من إدخال البيانات يدوياً، يمكن أن نطلب من Laravel إنشاء البيانات المطلوبة عند تنفيذ أمر Artisan.
مثلاً:
php artisan db:seedيمكن أن يؤدي إلى إنشاء:
10 Users
100 Posts
20 Categories
500 Commentsخلال ثوانٍ.
ما هو Seeder في Laravel؟
Seeder هو Class يحتوي على التعليمات الخاصة بالبيانات التي نريد إضافتها إلى قاعدة البيانات.
توجد Seeders داخل:
database/seedersوفي مشروع Laravel يوجد بشكل افتراضي:
database/seeders/DatabaseSeeder.phpويعتبر DatabaseSeeder نقطة البداية الرئيسية لتنفيذ Seeders.
مثال بسيط على DatabaseSeeder
يمكن مثلاً إنشاء عشرة مستخدمين:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
User::factory()
->count(10)
->create();
}
}ثم ننفذ:
php artisan db:seedوسيقوم Laravel بإنشاء 10 مستخدمين في قاعدة البيانات.
ما الفرق بين Seeder وFactory؟
من المهم فهم الفرق بينهما.
| Seeder | Factory |
|---|---|
| يحدد البيانات التي سيتم إدخالها ومتى يتم إدخالها | يحدد شكل البيانات الافتراضية للـ Model |
| مناسب للبيانات الثابتة | مناسب للبيانات التجريبية والعشوائية |
| يمكنه تشغيل Factories | يستخدم Faker لتوليد البيانات |
| ينظم عملية تعبئة قاعدة البيانات | يجعل إنشاء Models تجريبية أمراً سهلاً |
غالباً يتم استخدامهما معاً:
Seeder
│
▼
Factory
│
▼
Faker
│
▼
Eloquent Model
│
▼
Databaseمثال: إنشاء بيانات لجدول Posts
لنفترض أن لدينا جدول:
postsويحتوي على:
id
title
body
active
user_id
created_at
updated_atولدينا Model:
App\Models\Postونريد إنشاء 100 مقال تجريبي.
إنشاء PostFactory
ننفذ:
php artisan make:factory PostFactory --model=Postسيتم إنشاء:
database/factories/PostFactory.phpويمكن كتابة:
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'active' => fake()->boolean(),
'user_id' => User::factory(),
];
}
}ما الذي يحدث داخل Factory؟
الدالة:
definition()تحدد القيم الافتراضية التي سيستخدمها Laravel عند إنشاء Post باستخدام Factory.
مثلاً:
'title' => fake()->sentence()ينشئ عنواناً وهمياً.
أما:
'body' => fake()->paragraphs(3, true)فيقوم بإنشاء عدة فقرات نصية.
والسطر:
'active' => fake()->boolean()ينشئ قيمة Boolean عشوائية:
true
أو
falseإنشاء علاقة المستخدم داخل Factory
لاحظ أننا استخدمنا:
'user_id' => User::factory()وهذا يعني أنه إذا لم نحدد مستخدماً، يمكن للـ Factory إنشاء User مرتبط بالـ Post.
وهذه طريقة أفضل في كثير من سيناريوهات الاختبار من كتابة:
User::all()->random()->idلأن الكود القديم يفترض مسبقاً وجود Users داخل قاعدة البيانات.
استخدام Factory
بعد إنشاء PostFactory يمكننا كتابة:
Post::factory()
->count(100)
->create();وسيقوم Laravel بإنشاء 100 Post.
استخدام Factory داخل DatabaseSeeder
افتح:
database/seeders/DatabaseSeeder.phpواكتب:
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
Post::factory()
->count(100)
->create();
}
}ثم:
php artisan db:seedإنشاء Seeder مستقل
إذا كان التطبيق يحتوي على عدة Models، فمن الأفضل عدم وضع كل شيء داخل DatabaseSeeder.
يمكن إنشاء Seeder مستقل:
php artisan make:seeder PostSeederوسيتم إنشاء:
database/seeders/PostSeeder.phpكتابة PostSeeder
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()
->count(100)
->create();
}
}تشغيل Seeder محدد
إذا أردنا تشغيل PostSeeder فقط:
php artisan db:seed --class=PostSeederبهذه الطريقة لن يتم تشغيل جميع Seeders الأخرى.
استدعاء Seeder من DatabaseSeeder
الطريقة الأكثر تنظيماً هي جعل DatabaseSeeder مسؤولاً عن تشغيل بقية Seeders.
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
UserSeeder::class,
CategorySeeder::class,
PostSeeder::class,
]);
}
}ثم:
php artisan db:seedوسيقوم Laravel بتنفيذ Seeders حسب الترتيب المكتوب.
لماذا ترتيب Seeders مهم؟
إذا كان Post يحتاج إلى User وCategory، فمن المنطقي إنشاء Users وCategories أولاً.
UserSeeder
↓
CategorySeeder
↓
PostSeederخصوصاً إذا كانت Seeders تعتمد على سجلات تم إنشاؤها مسبقاً.
إنشاء User وPosts مرتبطة به
Factories تصبح أكثر قوة عند التعامل مع العلاقات.
إذا كان User Model يحتوي على:
public function posts()
{
return $this->hasMany(Post::class);
}يمكن إنشاء مستخدم ومعه Posts:
User::factory()
->has(
Post::factory()->count(5)
)
->create();النتيجة:
User
│
├── Post
├── Post
├── Post
├── Post
└── Postإنشاء 10 مستخدمين ولكل مستخدم 5 Posts
User::factory()
->count(10)
->has(
Post::factory()->count(5)
)
->create();سينتج:
10 Users
و
50 Postsمع ربط كل Post بالمستخدم الخاص به تلقائياً.
مثال عملي: StudentFactory
لنفترض أن لدينا جدول Students يحتوي على:
id
name
email
mobile
age
gender
address
created_at
updated_atننشئ Factory:
php artisan make:factory StudentFactory --model=Studentثم:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class StudentFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'mobile' => fake()->phoneNumber(),
'age' => fake()->numberBetween(18, 45),
'gender' => fake()->randomElement([
'male',
'female',
]),
'address' => fake()->address(),
];
}
}إنشاء 100 طالب
داخل Seeder:
Student::factory()
->count(100)
->create();ثم:
php artisan db:seedما هو Faker؟
Faker هي مكتبة لتوليد بيانات وهمية يمكن استخدامها أثناء تطوير واختبار التطبيقات.
Laravel يجعل Faker متاحاً بسهولة داخل Model Factories.
يمكن استخدام:
fake()أو داخل Factory:
$this->fakerمثلاً:
fake()->name()أو:
$this->faker->name()أمثلة على بيانات Faker
اسم
fake()->name()الاسم الأول
fake()->firstName()اسم العائلة
fake()->lastName()بريد إلكتروني
fake()->safeEmail()بريد إلكتروني فريد
fake()->unique()->safeEmail()رقم هاتف
fake()->phoneNumber()عنوان
fake()->address()مدينة
fake()->city()دولة
fake()->country()كلمة
fake()->word()جملة
fake()->sentence()فقرة
fake()->paragraph()عدة فقرات
fake()->paragraphs(5, true)رقم عشوائي بين قيمتين
fake()->numberBetween(18, 60)قيمة Boolean
fake()->boolean()اختيار عنصر عشوائي
fake()->randomElement([
'male',
'female',
])تاريخ
fake()->date()تاريخ ووقت
fake()->dateTime()سنة
fake()->year()Domain
fake()->domainName()URL
fake()->url()استخدام unique() في Faker
إذا كان حقل Email يحتوي على Unique Constraint في قاعدة البيانات، يجب أن تكون البيانات المولدة فريدة.
لذلك نستخدم:
fake()->unique()->safeEmail()بدلاً من:
fake()->safeEmail()وذلك لتقليل احتمالية محاولة Factory إدخال بريد إلكتروني مكرر.
إنشاء بيانات عربية باستخدام Faker
Faker لا يقتصر على اللغة الإنجليزية، بل يدعم Locales متعددة تسمح بتوليد بيانات مناسبة للغة أو المنطقة المحددة، حسب الـ Providers المتاحة لذلك Locale.
يمكن مثلاً استخدام:
ar_SAلتوليد بيانات عربية مدعومة بواسطة هذا Locale.
تحديد Faker Locale داخل Laravel
يمكن ضبط Faker Locale من إعدادات التطبيق.
داخل:
config/app.phpيمكن تعريف:
'faker_locale' => 'ar_SA',بعدها ستستخدم Factories هذا Locale عند إنشاء البيانات الوهمية.
جعل Faker Locale قابلاً للتغيير من .env
بدلاً من تثبيت القيمة داخل Configuration، يمكن جعلها قابلة للتغيير حسب البيئة.
داخل:
config/app.phpاستخدم:
'faker_locale' => env(
'FAKER_LOCALE',
'en_US'
),ثم داخل:
.envاكتب:
FAKER_LOCALE=ar_SAوإذا أردت العودة إلى الإنجليزية:
FAKER_LOCALE=en_USمثال StudentFactory باللغة العربية
بعد تحديد:
FAKER_LOCALE=ar_SAيمكن استخدام نفس Factory:
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'mobile' => fake()->phoneNumber(),
'age' => fake()->numberBetween(18, 45),
'gender' => fake()->randomElement([
'ذكر',
'أنثى',
]),
'address' => fake()->address(),
];
}لاحظ أننا حددنا قيم Gender بالعربية يدوياً:
'ذكر'
'أنثى'لأن Locale لا يعني أن كل قيمة مخصصة لتطبيقك ستتم ترجمتها تلقائياً.
استخدام Locale معين داخل Factory فقط
في بعض المشاريع قد لا نريد تغيير Faker Locale لجميع Factories.
يمكن إنشاء Faker مخصص:
use Faker\Factory as FakerFactory;
$faker = FakerFactory::create('ar_SA');ثم:
$faker->name();
$faker->address();
$faker->phoneNumber();هذه الطريقة مفيدة إذا كنت تحتاج بيانات بلغات مختلفة داخل نفس المشروع.
توليد بيانات عربية وإنجليزية معاً
لنفترض أن جدول Categories يحتوي على:
name_ar
name_enيمكن إنشاء Faker لكل لغة:
use Faker\Factory as FakerFactory;
public function definition(): array
{
$fakerAr = FakerFactory::create('ar_SA');
$fakerEn = FakerFactory::create('en_US');
return [
'name_ar' => $fakerAr->name(),
'name_en' => $fakerEn->name(),
];
}وبالتالي نستطيع إنشاء بيانات متعددة اللغات داخل Factory نفسها.
Factory States
في بعض الأحيان نريد إنشاء نفس Model لكن بحالات مختلفة.
مثلاً Post يمكن أن يكون:
Active
Inactiveبدلاً من إنشاء Factory أخرى، يمكن استخدام Factory States.
إنشاء State للمقالات النشطة
داخل PostFactory:
public function active(): static
{
return $this->state(fn (array $attributes) => [
'active' => true,
]);
}ثم:
Post::factory()
->count(100)
->active()
->create();جميع المقالات ستكون:
active = trueإنشاء State للمقالات غير النشطة
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'active' => false,
]);
}ثم:
Post::factory()
->count(20)
->inactive()
->create();تغيير قيمة Factory عند الاستخدام
يمكن تجاوز القيم الافتراضية بسهولة.
مثلاً:
Post::factory()->create([
'title' => 'مقال تجريبي',
'active' => true,
]);سيقوم Factory بتوليد بقية الحقول، بينما نحدد نحن قيم title وactive.
الفرق بين create وmake
من النقاط المهمة في Factories الفرق بين:
create()و:
make()عند استخدام:
User::factory()->create();يتم إنشاء Model وحفظه في قاعدة البيانات.
أما:
User::factory()->make();فيتم إنشاء Model في الذاكرة فقط دون حفظه في قاعدة البيانات.
| Method | حفظ في Database |
|---|---|
| create() | نعم |
| make() | لا |
إنشاء Model واحد
User::factory()->create();إنشاء عدة Models
User::factory()
->count(50)
->create();إنشاء Model دون حفظه
$user = User::factory()->make();إنشاء عدة Models دون حفظها
$users = User::factory()
->count(10)
->make();استخدام Sequence
يمكن استخدام Sequence عندما نريد توزيع قيم محددة على البيانات.
مثلاً:
use Illuminate\Database\Eloquent\Factories\Sequence;
Post::factory()
->count(10)
->state(
new Sequence(
['active' => true],
['active' => false],
)
)
->create();ستتناوب قيمة Active بين:
true
false
true
false
...إنشاء بيانات ثابتة باستخدام Seeder
ليس كل شيء يجب إنشاؤه باستخدام Faker.
مثلاً إذا كان لدينا جدول:
order_statusesويجب أن يحتوي دائماً على:
Pending
Processing
Completed
Cancelledفهذه ليست بيانات عشوائية.
يمكن إنشاء:
php artisan make:seeder OrderStatusSeederثم:
DB::table('order_statuses')->insert([
[
'name' => 'Pending',
],
[
'name' => 'Processing',
],
[
'name' => 'Completed',
],
[
'name' => 'Cancelled',
],
]);وهذا مثال ممتاز على الاستخدام الصحيح للـ Seeder.
متى أستخدم Seeder ومتى أستخدم Factory؟
استخدم Seeder للبيانات الثابتة مثل:
- أنواع الحسابات.
- حالات الطلب.
- الدول.
- الصلاحيات الأساسية.
- الإعدادات الافتراضية.
- Roles.
واستخدم Factory للبيانات التجريبية مثل:
- Users.
- Posts.
- Comments.
- Orders.
- Students.
- Products.
استخدام Seeders وFactories معاً
يمكن مثلاً كتابة:
public function run(): void
{
$this->call([
RoleSeeder::class,
CategorySeeder::class,
]);
User::factory()
->count(20)
->has(
Post::factory()->count(5)
)
->create();
}هنا:
- Roles بيانات ثابتة.
- Categories بيانات ثابتة.
- Users بيانات تجريبية.
- Posts بيانات تجريبية.
إعادة إنشاء قاعدة البيانات وتشغيل Seeders
أثناء التطوير قد نريد حذف جميع الجداول، تشغيل Migrations من جديد، ثم تعبئة قاعدة البيانات.
يمكن استخدام:
php artisan migrate:fresh --seedهذا الأمر يقوم بشكل عام بـ:
Drop Tables
↓
Run Migrations
↓
Run DatabaseSeeder
↓
Create Seed Dataتحذير مهم حول migrate:fresh
الأمر:
php artisan migrate:freshيقوم بحذف جداول قاعدة البيانات قبل إعادة تشغيل Migrations.
لذلك يجب الحذر الشديد من تشغيله على قاعدة بيانات Production تحتوي على بيانات حقيقية.
الأمر:
php artisan migrate:fresh --seedمناسب بشكل أساسي لبيئات التطوير والاختبار التي يمكن حذف بياناتها وإعادة بنائها.
تشغيل Seeder في Production
بعض Seeders قد تحتوي على بيانات حقيقية يحتاجها النظام، مثل:
Roles
Permissions
System Settings
Countries
Order Statusesلكن يجب التمييز بينها وبين Seeders التي تنشئ بيانات وهمية للاختبار.
لا ينبغي تشغيل Factory ينشئ آلاف المستخدمين الوهميين على Production لمجرد أنه موجود داخل DatabaseSeeder.
لذلك من المهم تنظيم Seeders ومعرفة أي منها مخصص للبيانات الأساسية وأي منها مخصص لبيئة Development.
استخدام Factory في الاختبارات
Factories مفيدة جداً داخل Automated Tests.
بدلاً من إنشاء مستخدم يدوياً:
$user = User::create([
'name' => 'Test User',
'email' => 'test@example.com',
...
]);يمكن كتابة:
$user = User::factory()->create();ثم استخدامه داخل الاختبار.
مثلاً:
test('user can view dashboard', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->get('/dashboard');
$response->assertOk();
});أفضل الممارسات عند استخدام Seeders وFactories
- استخدم Factory لإنشاء البيانات التجريبية بدلاً من تكرار بيانات ثابتة داخل Loops.
- استخدم Seeders مستقلة لكل مجموعة بيانات كبيرة أو منطقية.
- اجعل
DatabaseSeederمسؤولاً عن تنسيق تشغيل بقية Seeders. - استخدم Factory Relationships بدلاً من جلب جميع السجلات إلى الذاكرة باستخدام
Model::all()عندما لا تكون هناك حاجة لذلك. - استخدم
unique()للحقول التي يجب أن تكون فريدة. - استخدم Factory States للحالات المتكررة بدلاً من إنشاء Factories متشابهة.
- لا تستخدم Faker للبيانات التي يجب أن تكون ثابتة ومعروفة مسبقاً.
- افصل بيانات Production الأساسية عن البيانات الوهمية الخاصة بالتطوير.
- احذر من استخدام
migrate:freshعلى قواعد البيانات التي تحتوي بيانات مهمة. - استخدم العلاقات الموجودة في Factories لإنشاء بيانات أقرب إلى البنية الحقيقية للتطبيق.
أخطاء شائعة
استخدام المسار database/seeds
في إصدارات Laravel الحديثة يكون المسار:
database/seedersاستخدام Loop لإنشاء مئات Models دون الحاجة
بدلاً من:
for ($i = 0; $i < 100; $i++) {
Post::create([...]);
}يفضل عندما يكون السيناريو مناسباً استخدام:
Post::factory()
->count(100)
->create();استخدام User::all()->random() داخل Factory
هذا يقوم بتحميل مجموعة Users ثم اختيار مستخدم منها، كما أنه يعتمد على وجود Users مسبقاً.
يمكن في كثير من الحالات استخدام:
'user_id' => User::factory()أو إنشاء العلاقات باستخدام Factory Relationships.
الاعتقاد أن Locale يترجم كل شيء
تغيير Faker Locale يؤثر على البيانات التي يوفر لها Faker Provider مناسب، لكنه لا يقوم بترجمة القيم الخاصة بمنطق تطبيقك تلقائياً.
مثلاً قيم:
pending
approved
rejectedلن تتحول تلقائياً إلى:
قيد الانتظار
مقبول
مرفوضهذه القيم يجب عليك تحديدها بنفسك وفق تصميم التطبيق.
مثال نهائي متكامل
يمكن أن يكون DatabaseSeeder لمشروع تجريبي:
<?php
namespace Database\Seeders;
use App\Models\Post;
use App\Models\Student;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
/*
|--------------------------------------------------------------------------
| Static Data
|--------------------------------------------------------------------------
*/
$this->call([
RoleSeeder::class,
CategorySeeder::class,
]);
/*
|--------------------------------------------------------------------------
| Test Users + Posts
|--------------------------------------------------------------------------
*/
User::factory()
->count(10)
->has(
Post::factory()
->count(5)
)
->create();
/*
|--------------------------------------------------------------------------
| Students
|--------------------------------------------------------------------------
*/
Student::factory()
->count(100)
->create();
}
}ثم كل ما نحتاج إليه في بيئة التطوير:
php artisan migrate:fresh --seedوسيقوم Laravel بإعادة إنشاء قاعدة البيانات وتعبئتها بالبيانات المحددة.
الخلاصة
يوفر Laravel نظاماً قوياً لإنشاء البيانات التجريبية وتعبئة قواعد البيانات باستخدام Seeders وModel Factories وFaker.
يمكن تلخيص مسؤولية كل جزء بالشكل التالي:
Seeder
│
│ يحدد ما الذي نريد إنشاؤه
▼
Factory
│
│ يحدد شكل بيانات Model
▼
Faker
│
│ يولد البيانات الوهمية
▼
Eloquent
│
▼
Databaseإذا كانت البيانات ثابتة وأساسية للنظام مثل Roles وحالات الطلب، فإن Seeder هو الخيار المناسب.
أما إذا كنا نحتاج إلى عشرات أو آلاف السجلات الوهمية لاختبار التطبيق، فإن Model Factories مع Faker توفر طريقة أسرع وأكثر تنظيماً.
ويمكن أيضاً تغيير Faker Locale لتوليد بيانات عربية، أو إنشاء أكثر من Faker Locale داخل Factory نفسها عندما يحتوي التطبيق على بيانات متعددة اللغات.
وأهم ما يجعل Factories قوية فعلاً هو تكاملها مع علاقات Eloquent، حيث يمكن إنشاء Users وPosts وComments وغيرها من Models المرتبطة ببعضها بطريقة قريبة جداً من البيانات الحقيقية، مما يجعلها أداة أساسية ليس فقط لتعبئة قاعدة البيانات أثناء التطوير، وإنما أيضاً لبناء اختبارات Laravel بشكل احترافي.
الشرح اكتر من رائع شكرا جزيلا على ايصال المعلومة بطريقة مبسطة
ll
شرحك جميل جدا ليش ما تعمل كورس عربي مدفوع للارفل بصراحة بتوصل معلومة وشوحك راقي