استخدام 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؟

من المهم فهم الفرق بينهما.

SeederFactory
يحدد البيانات التي سيتم إدخالها ومتى يتم إدخالهايحدد شكل البيانات الافتراضية للـ 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 بشكل احترافي.