Seeder و Factory و Tinker في Laravel: ثلاث أدوات يسيء كثير من الناس فهمها
في كثير من المشاريع، يتم التعامل مع Seeder و Factory و Tinker كأدوات مساعدة فقط. شيء نستخدمه في البداية، ثم نتركه.
لكن مع الوقت يتضح أن هذه الأدوات ليست مجرد اختصارات، بل جزء من طريقة التفكير في بناء بيئة تطوير حقيقية يمكن إعادة إنتاجها، واختبارها، وفهمها.
المشكلة أن كثيرًا من الاستخدامات المنتشرة لها سطحية:
- Seeder لحشو بيانات عشوائية
- Factory لتوليد أي شيء بسرعة
- Tinker لتجربة أوامر متفرقة
بينما الاستخدام الجيد لها أعمق من ذلك بكثير.
أولًا: Seeder ليس “بيانات تجريبية” فقط
Seeder في Laravel وظيفته الأساسية ليست ملء قاعدة البيانات بشكل عشوائي، بل إعادة بناء حالة معروفة ومتوقعة داخل المشروع.
وهذا فرق مهم.
هناك نوعان من البيانات عادة:
- بيانات أساسية يجب أن توجد دائمًا
- بيانات اختبار أو تطوير
Seeder الجيد يفرّق بين الاثنين.
متى تستخدم Seeder فعلًا؟
- إنشاء الأدوار والصلاحيات الأساسية
- إدخال إعدادات أولية للنظام
- إدخال حساب مدير افتراضي في بيئة التطوير
- تجهيز جداول مرجعية مثل الدول، المدن، الحالات، الأنواع
هذا النوع من البيانات ليس “dummy data”، بل جزء من البنية المنطقية للتطبيق.
مثال Seeder منطقي
php artisan make:seeder RolesSeeder
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class RolesSeeder extends Seeder
{
public function run(): void
{
$roles = ['admin', 'editor', 'author'];
foreach ($roles as $role) {
Role::firstOrCreate(['name' => $role]);
}
}
}
هنا نلاحظ شيئين مهمين:
- الـ Seeder لا يفترض أن الجدول فارغ
- يستخدم
firstOrCreateبدل الإدخال الأعمى
وهذا يجعل تشغيله أكثر من مرة آمنًا.
Seeder الجيد يجب أن يكون قابلًا لإعادة التشغيل
واحدة من الأخطاء الشائعة أن يكتب المطور Seeder يفشل إذا شُغّل مرتين.
مثال سيئ:
Role::create(['name' => 'admin']);
إذا كان السجل موجودًا أصلًا، سيفشل التنفيذ.
الهدف من Seeder ليس “أدخل مرة وانتهى”، بل أن يكون جزءًا من تجهيز البيئة.
لهذا من الأفضل استخدام:
- firstOrCreate
- updateOrCreate
- upsert
ثانيًا: Factory ليست “توليد بيانات عشوائية” فقط
Factory في Laravel غالبًا تُفهم على أنها طريقة لإدخال أسماء وهمية وعناوين بريد وخلاص. لكن دورها الحقيقي أكبر:
هي أداة لوصف شكل الكيان في بيئات التطوير والاختبار.
Factory الناجحة لا تولد بيانات عشوائية فقط، بل تبني نموذجًا واقعيًا للبيانات.
مثال Factory أساسي
php artisan make:factory PostFactory --model=Post
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'status' => 'draft',
];
}
}
هنا الـ Factory لا تبني Post فقط، بل تبني User مرتبطًا به تلقائيًا. وهذا مهم لأن الكيان في التطبيق الحقيقي نادرًا ما يكون معزولًا.
States: الجزء الذي يجعل Factory مفيدة فعلًا
القيمة الحقيقية للـ Factory تظهر عند استخدام states.
بدل Factory واحدة تولد كل شيء بنفس الشكل، يمكن تعريف حالات مختلفة للكيان.
public function published(): static
{
return $this->state(fn () => [
'status' => 'published',
'published_at' => now(),
]);
}
public function archived(): static
{
return $this->state(fn () => [
'status' => 'archived',
]);
}
الاستخدام:
Post::factory()->published()->create();
Post::factory()->archived()->count(5)->create();
هنا تبدأ الـ Factory تتحول من “مولد بيانات” إلى أداة تمثل السيناريوهات المختلفة داخل التطبيق.
بعد فترة، ستكتشف أن Factory جزء من التفكير في الاختبارات
المطور الذي يكتب Factory بشكل جيد، يسهّل على نفسه كتابة الاختبارات بشكل كبير.
بدل هذا:
$user = User::create([
'name' => 'user',
'email' => '[email protected]',
...
]);
يمكنك كتابة:
$user = User::factory()->create();
ثم إذا احتجت حالة خاصة:
$user = User::factory()->suspended()->create();
وهنا يصبح الاختبار أقرب للمعنى، وأقل ازدحامًا بالتفاصيل.
العلاقات داخل Factory: هنا يظهر النضج في الاستخدام
Laravel يسمح لك ببناء علاقات معقدة جدًا عبر Factory.
$user = User::factory()
->hasPosts(3)
->create();
أو:
$post = Post::factory()
->for(User::factory()->state([
'name' => 'user'
]))
->create();
أو حتى العلاقات many-to-many:
$post = Post::factory()
->hasAttached(Tag::factory()->count(3))
->create();
هذه ليست مجرد راحة في الكتابة، بل تجعل بيئة الاختبار والتطوير أقرب بكثير إلى الواقع.
ثالثًا: Tinker ليس لعبة… هو نافذة مباشرة على التطبيق
كثير يستخدم Tinker كأنه مجرد shell لتجربة أوامر سريعة. لكن إذا فهمته جيدًا، فهو واحد من أهم أدوات الفهم والاستكشاف في Laravel.
php artisan tinker
عند الدخول، أنت لست داخل PHP فقط، بل داخل التطبيق نفسه:
- Container محمّل
- Eloquent جاهز
- Facades متاحة
- Config متاح
- Models قابلة للتجربة مباشرة
أفضل استخدامات Tinker
1) استكشاف العلاقات
$user = App\Models\User::first();
$user->posts;
$user->roles;
بدل كتابة route أو controller فقط للتجربة، يمكنك فهم البيانات مباشرة.
2) تجربة Queries قبل وضعها في الكود
App\Models\Post::where('status', 'published')
->latest()
->take(5)
->get();
هذا مفيد جدًا قبل بناء Query داخل Repository أو Service.
3) اختبار سلوك Model أو Accessor أو Scope
$post = App\Models\Post::first();
$post->slug;
App\Models\Post::published()->count();
4) تنفيذ عمليات إصلاح سريعة أثناء التطوير
App\Models\User::whereNull('email_verified_at')
->update(['email_verified_at' => now()]);
لكن هنا يجب الانتباه الشديد، لأن Tinker يعطيك وصولًا مباشرًا لقاعدة البيانات.
الفرق بين Seeder و Factory و Tinker
رغم أنها أدوات متجاورة في Laravel، إلا أن كل واحدة تحل مشكلة مختلفة:
| الأداة | وظيفتها | أفضل استخدام |
|---|---|---|
| Seeder | بناء حالة بيانات معروفة | تهيئة النظام والبيئات |
| Factory | وصف شكل الكيانات وبياناتها | الاختبارات والبيانات التطويرية |
| Tinker | استكشاف التطبيق حيًا | التجربة والتحليل والفحص |
المشكلة تبدأ عندما تستخدم أداة في غير مكانها.
مثلًا:
- استخدام Seeder لصنع بيانات اختبار فوضوية
- استخدام Tinker لإدخال بيانات يفترض أن تكون مُمأسسة داخل Seeder
- استخدام Factory فقط كمولد Faker بدون states أو علاقات
تركيب الأدوات الثلاث معًا: هنا تبدأ الفائدة الحقيقية
القيمة الحقيقية تظهر عندما تعمل الأدوات معًا.
مثال منطقي:
- Seeder ينشئ الأدوار الأساسية
- Factory ينشئ مستخدمين وبيانات مترابطة
- Tinker يستخدم لاستكشاف النتيجة والتأكد من صحة العلاقات
مثال:
// DatabaseSeeder
public function run(): void
{
$this->call(RolesSeeder::class);
\App\Models\User::factory()
->count(10)
->hasPosts(3)
->create();
}
ثم في Tinker:
$user = App\Models\User::with('posts')->first();
$user->posts;
هنا أنت لا تختبر الكود فقط، بل تبني بيئة كاملة يمكن فهمها والعمل عليها.
متى تفشل هذه الأدوات؟
Seeder يفشل عندما يتحول إلى ملف ضخم بلا منطق
إذا كان DatabaseSeeder يحتوي كل شيء داخل ملف واحد، فأنت لا تبني نظام تهيئة، بل تبني كتلة يصعب صيانتها.
Factory تفشل عندما تولد بيانات غير واقعية
إذا كانت البيانات عشوائية جدًا أو لا تمثل الحالات الحقيقية، فالاختبارات التي تعتمد عليها تصبح أقل قيمة.
Tinker يفشل عندما يتحول إلى بديل دائم عن الكود المنظم
إذا كنت تصلح البيانات دائمًا من Tinker بدل Seeder أو Migrations أو Commands، فأنت تنقل المنطق من النظام إلى الذاكرة الشخصية. وهذا خطر.
أفضل ممارسة: اجعل البيانات جزءًا من تصميم المشروع
المطور الجيد لا يفكر فقط في بنية الكود، بل في بنية البيانات أيضًا:
- كيف يتم إنشاء البيئة من الصفر
- كيف تتولد بيانات واقعية للاختبار
- كيف يمكن فحص النظام حيًا دون العبث العشوائي
Seeder و Factory و Tinker ليست أدوات جانبية. هي جزء من نضج المشروع نفسه.
الخلاصة
إذا نظرت إلى Seeder و Factory و Tinker كأدوات مساعدة فقط، ستستخدمها بشكل سطحي. أما إذا تعاملت معها كجزء من دورة حياة المشروع، فستصبح:
- Seeder وسيلة لبناء حالة ثابتة
- Factory وسيلة لوصف البيانات وسيناريوهاتها
- Tinker وسيلة لفهم التطبيق من الداخل
وهنا تبدأ فائدتها الحقيقية.