اسمي أبو عمر، قضيت سنوات طويلة من عمري بين الأكواد والخوادم، خصوصاً في عالم الذكاء الاصطناعي والتقنيات الحديثة. ومن أحب المهام إلى قلبي هو إجراء المقابلات التقنية، ليس لأني أحب “حشر” المبرمجين بالأسئلة، بل لأني أستمتع برؤية عقلية هندسية منظمة قادرة على حل المشاكل ببراعة.
دعوني أحكي لكم قصة حدثت معي قبل فترة. كنا نبحث عن مطور Laravel سنيور لمشروع ضخم وحساس في غزة. تقدم لنا عدد من الشباب، وقابلت أحدهم، شاب صغير في السن، ولكن عندما بدأ يتحدث، شعرت أني أجلس مع مهندس معماري وليس مجرد مبرمج. لم تكن إجاباته “نعم” و “لا” أو مجرد ترديد لما في التوثيق الرسمي.
كل إجابة كانت عبارة عن قصة صغيرة، فيها “لماذا” قبل “كيف”، وفيها ذكر لموقف عملي حدث معه وكيف حله.
وقتها، لم أستطع إلا أن أبتسم وأنا أستمع إليه، وفي منتصف المقابلة تقريباً كنت أعرف أنه الشخص الذي نريده. قلت له: “يا ابني، أنت مقبول، دعنا فقط نكمل الدردشة”.
اليوم، قررت أن أجمع لكم أهم الأسئلة التي طرحتها عليه وعلى غيره من المبدعين، مع الإجابات النموذجية التي تدل على فهم عميق، وليس مجرد حفظ. هذا الدليل ليس فقط لمن لديه مقابلة، بل لكل مطور حابب أن يطور عقليته البرمجية.
القسم الأول: في عمق معمارية لارافيل (Core Architecture)
هنا نميز الخبير من المبتدئ. الفهم العميق لكيفية عمل الإطار من الداخل هو أساس كل شيء.
1. اشرح لي دورة حياة الطلب (Request Lifecycle) وكأنني المتصفح؟
الإجابة النموذجية: “تمام يا أبو عمر. تخيل حالك كمتصفح ضغطت على رابط. هذه هي الرحلة خطوة بخطوة:
- نقطة الدخول (Entry Point): أول محطة يصلها طلبك هي ملف
public/index.php. هذا هو البوّاب الرئيسي. مهمته بسيطة: تحميل ملفات الـ autoloader عبر Composer وإنشاء نسخة من التطبيق (Application Instance) منbootstrap/app.php. - نواة التطبيق (Kernel): يتم تمرير الطلب إلى نواة HTTP الموجودة في
app/Http/Kernel.php. هذه النواة هي “الشرطي” الذي ينظم السير. أهم شيء تقوم به هو تمرير الطلب عبر مجموعة من الـ Middleware (نقاط التفتيش) العالمية التي حددناها. - مقدمو الخدمات (Service Providers): قبل توجيه الطلب، يقوم الكيرنل بتحميل مقدمي الخدمات (Service Providers) المسجلين في
config/app.php. هذه هي “ورشة التجهيز” التي تقوم بتسجيل كل خدمات التطبيق (مثل قواعد البيانات، الطوابير، الكاش) في حاوية الخدمات (Service Container). - التوجيه (Routing): الآن، يستلم الـ Router (دليل الطرق) الطلب ويبحث في ملفات التوجيه (
routes/*.php) عن المسار المطابق ليوجهه إلى الـ Controller أو الـ Closure المناسب. - المتحكم والاستجابة (Controller & Response): أخيراً، يقوم الـ Controller بتنفيذ منطق العمل، وقد يتفاعل مع الـ Models، ويجهز الـ Response (سواء كانت View, JSON, أو Redirect)، ثم يعيدها مرة أخرى عبر نفس سلسلة الـ Middleware (ولكن بالترتيب العكسي) قبل إرسالها إلى المتصفح.”
2. ما الفرق الجوهري بين Service Container و Service Provider؟
الإجابة النموذجية: “ببساطة، هما وجهان لعملة واحدة لإدارة التبعيات (Dependency Management). يمكننا تلخيص الفرق في جدول:
| المفهوم | Service Container (حاوية الخدمات) | Service Provider (مقدم الخدمة) |
|---|---|---|
| الدور | هو “المدير” أو “الصندوق السحري”. وظيفته إنشاء وإدارة وتقديم الكائنات (الكلاسات) عند الحاجة، وحل تبعياتها تلقائياً (Dependency Injection). | هو “دليل التعليمات” لهذا الصندوق. وظيفته هي تعليم الحاوية كيفية إنشاء خدمة معينة. |
| آلية العمل | عندما تطلب منه خدمة (app()->make('MyService'))، يبحث عن كيفية بنائها وينشئها لك. |
يحتوي على دالتين رئيسيتين: register() لربط (bind) الواجهة بالتنفيذ الفعلي، و boot() لتنفيذ أي كود يعتمد على خدمات أخرى تم تسجيلها. |
| مثال | عندما تقوم بعمل Type-hint لكلاس في الـ Controller’s constructor، يقوم الـ Container تلقائياً بإنشاء هذا الكلاس وتمريره لك. | AppServiceProvider هو مثال بسيط. عندما تحتاج لربط واجهة PaymentGatewayInterface بكلاس StripeGateway، تقوم بذلك في دالة register. |
باختصار، الـ Provider يجهز “الوصفة”، والـ Container “يطبخها” عند الطلب. لا يمكن لتطبيق لارافيل أن يعمل بدون هذه الآلية.”
3. كيف تعمل الـ Facades خلف الكواليس؟ وهل هي Anti-Pattern؟
الإجابة النموذجية: “الـ Facade هي واجهة بسيطة وثابتة (Static) لكلاس معقد مسجل في الـ Service Container. خلف الكواليس، عندما نستدعي ميثود ستاتيك مثل Cache::get('key')، لارافيل تستخدم الـ Magic Method المسماة __callStatic(). هذه الميثود تقوم بالآتي:
- تحدد اسم الربط (binding name) في الـ Container من خلال دالة
getFacadeAccessor(). - تطلب من الـ Service Container إنشاء الكائن الحقيقي المرتبط بهذا الاسم (مثلاً، مدير الكاش).
- تستدعي الميثود المطلوبة (
get('key')) على هذا الكائن.
هل هي Anti-Pattern؟ الجواب يعتمد على السياق. الإفراط في استخدامها يعتبر ممارسة سيئة (Anti-Pattern) لأنها تخفي التبعيات الحقيقية للكلاس وتجعل الكود مقترناً بشدة (Tight Coupling)، مما يصعّب عملية الاختبار (Testing) حيث تحتاج إلى استخدام Facade Mocks.
نصيحة أبو عمر: في الـ Controllers والـ Blade Views، استخدام الـ Facades مقبول لسهولة القراءة والوصول السريع. أما داخل الـ Service Classes، الـ Repositories، أو أي منطق عمل أساسي، استخدم الحقن المباشر للتبعية (Constructor Injection). هذا يجعل الكود الخاص بك صريحاً، معزولاً، وقابلاً للاختبار بسهولة.
4. ما هي الـ Middleware وما هي استخداماتها العملية غير المصادقة (Auth)؟
الإجابة النموذجية: “الـ Middleware هي طبقات أو “فلاتر” يمر بها الطلب قبل أن يصل إلى الـ Controller أو بعد أن يخرج منه. هي مثل حراس الأمن على أبواب المبنى. غير التحقق من هوية المستخدم (Auth)، استخداماتها كثيرة جداً ومفيدة، مثلاً:
- تنقية المدخلات (Sanitization): إنشاء Middleware يقوم بعمل
trim()لكل المدخلات النصية القادمة في الطلب. - التعامل مع CORS: إضافة الـ Headers المطلوبة للسماح بالطلبات من نطاقات مختلفة (APIs).
- تسجيل النشاطات (Logging): تسجيل كل طلب API مهم يتم على النظام مع بياناته لغايات التدقيق.
- تحديد لغة التطبيق: قراءة
Accept-Languageheader من الطلب وتعيين لغة التطبيق بناءً عليها باستخدامApp::setLocale(). - تحديد عدد الطلبات (Rate Limiting): لحماية الـ API من هجمات الـ Brute Force أو الاستخدام المفرط.
- وضع الصيانة (Maintenance Mode): إنشاء Middleware مخصص يسمح للمطورين فقط بالوصول للتطبيق أثناء فترة الصيانة.
القسم الثاني: قواعد البيانات و Eloquent (Database & Performance)
هنا يظهر الفرق بين من يكتب كود “يعمل” ومن يكتب كود “يعمل بكفاءة وسرعة”.
5. كيف تكتشف وتعالج مشكلة N+1 Query؟
الإجابة النموذجية: “مشكلة N+1 تحدث عندما نجلب قائمة من البيانات (هذا أول استعلام، الـ “1”)، ثم داخل loop، لكل عنصر في القائمة، ننفذ استعلاماً جديداً لجلب بيانات مرتبطة به (هذه هي الـ “N” استعلامات). لو عندي 100 مقال، سأقوم بتنفيذ 101 استعلام لجلب المقالات مع أسماء كتابها!
الاكتشاف: أفضل طريقة هي استخدام أدوات مثل Laravel Telescope أو Laravel Debugbar، التي تعرض لك كل الاستعلامات التي تحدث في الطلب الواحد وعددها. يدوياً، يمكن استخدام DB::listen() لطباعة الاستعلامات أثناء التطوير.
الحل: الحل بسيط جداً وهو استخدام التحميل المسبق (Eager Loading) عن طريق دالة with(). بدلاً من كتابة Post::all()، نكتب Post::with('author')->get(). هكذا لارافيل تجلب كل المقالات باستعلام واحد، وكل الكتاب المرتبطين بهم باستعلام ثانٍ فقط. المجموع استعلامان بدلاً من 101.”
// Bad: N+1 Problem
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // A query is executed for each post
}
// Good: Eager Loading
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name; // No extra queries!
}
كما يمكن استخدام التحميل المسبق الكسول (Lazy Eager Loading) باستخدام دالة load() إذا احتجت لتحميل العلاقة بعد جلب الـ Models الأساسية.
6. متى تفضل استخدام Query Builder على Eloquent ORM؟
الإجابة النموذجية: “أنا أستخدم Eloquent في 90% من الحالات لأنه يوفر كوداً نظيفاً ومقروءاً وسهل الصيانة، ويمنحني كل ميزات الـ Models مثل العلاقات، الـ Mutators، والـ Events. لكن، ألجأ إلى الـ Query Builder في حالات محددة تتطلب أداءً أو تحكماً أعلى.”
| الحالة | لماذا Query Builder أفضل؟ |
|---|---|
| التقارير المعقدة | عندما أحتاج لعمل JOIN بين عدة جداول مع GROUP BY, HAVING, و sub-queries معقدة. الـ Query Builder يعطيني تحكماً أكبر وصياغة أقرب للـ SQL الأصلي، مما ينتج استعلامات أسرع. |
| التحديثات والحذف الضخم (Batch Operations) | لو أردت تحديث 10,000 سجل مرة واحدة، استخدام Eloquent (عبر loop) سيستهلك ذاكرة كبيرة لأنه ينشئ Model لكل سجل. الـ Query Builder (DB::table(...)->update(...)) ينفذ التحديث مباشرة على قاعدة البيانات بأقل استهلاك للموارد. |
| عندما لا أحتاج لـ Models | أحياناً كل ما أحتاجه هو مجموعة بيانات خام من جدول غير مرتبط بـ Model، أو عندما لا أحتاج للـ overhead الخاص بـ Eloquent (مثل الـ casting والـ events). |
7. اشرح العلاقات متعددة الأشكال (Polymorphic Relations) بمثال واقعي؟
الإجابة النموذجية: “العلاقات متعددة الأشكال هي حل عبقري من لارافيل لمشكلة شائعة. تخيل نظاماً فيه مقالات (Posts) وفيديوهات (Videos)، وكلاهما يمكن للمستخدمين التعليق عليه. الطريقة التقليدية تتطلب عمل جدول post_comments وجدول video_comments، وهذا كود مكرر وصعب الصيانة.
الحل هو جدول comments واحد، يحتوي على الحقول التالية: id, body, commentable_id, و commentable_type.
هنا يكمن السحر: commentable_id يخزن الـ ID الخاص بالمقال أو الفيديو، و commentable_type يخزن اسم الموديل (مثلاً ‘App\Models\Post’). هكذا، موديل الـ Comment أصبح قادراً على الارتباط بأي موديل آخر. هذا يوفر جداول ويجعل الكود أنظف وأكثر مرونة وقابلية للتوسع في المستقبل.”
// In Comment Model
public function commentable()
{
return $this->morphTo();
}
// In Post Model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// In Video Model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
8. لماذا نستخدم معاملات قاعدة البيانات (Database Transactions)؟
الإجابة النموذجية: “نستخدمها لضمان سلامة وتكامل البيانات (Data Integrity) في العمليات التي تتكون من عدة خطوات. أفضل مثال هو عملية تحويل رصيد: يجب خصم مبلغ من حساب المستخدم (أ) وإضافته لحساب المستخدم (ب). هاتان العمليتان يجب أن تنجحا معاً، أو تفشلا معاً.
تخيل لو خصمنا المبلغ من (أ) وتعطل النظام قبل إضافته لـ (ب)؟ لقد ضاعت الأموال! الـ Transaction تضمن أن كل العمليات داخلها تتم بنجاح (Commit)، أو إذا فشلت أي عملية منها، يتم التراجع عن كل العمليات التي سبقتها (Rollback)، وكأن شيئاً لم يحدث. في لارافيل، استخدامها سهل جداً مع DB::transaction.”
DB::transaction(function () {
DB::table('users')->where('id', 1)->decrement('balance', 100);
DB::table('users')->where('id', 2)->increment('balance', 100);
});
للمنطق المعقد، يمكن استخدام التحكم اليدوي: DB::beginTransaction(), DB::commit(), و DB::rollBack().
9. ما الفرق بين الفهرسة (Indexing) والمفاتيح الخارجية (Foreign Keys)؟
الإجابة النموذجية: “كلاهما ضروري في تصميم قواعد البيانات، لكنهما يخدمان أهدافاً مختلفة تماماً.”
| المفهوم | Foreign Key (المفتاح الخارجي) | Index (الفهرس) |
|---|---|---|
| الهدف الأساسي | ضمان سلامة العلاقات (Referential Integrity) بين الجداول. | تحسين أداء الاستعلامات (Query Performance). |
| الوظيفة | هو قيد (Constraint) يمنع إضافة قيمة في حقل (مثل post_id في جدول التعليقات) لا وجود لها في الجدول المرجعي (جدول المقالات). يحمي البيانات من التناقض. |
هو هيكل بيانات إضافي (مثل B-Tree) يسمح لقاعدة البيانات بالعثور على السجلات بسرعة خيالية بناءً على العمود المفهرس، خصوصاً في عمليات SELECT مع WHERE أو JOIN. |
| التأثير على الأداء | يبطئ عمليات الكتابة (INSERT, UPDATE) بشكل طفيف لأنه يجب التحقق من القيد في كل مرة. |
يسرّع القراءة بشكل كبير، لكنه يبطئ الكتابة بشكل طفيف لأنه يجب تحديث الفهرس مع كل عملية كتابة. |
القسم الثالث: أنماط التصميم وجودة الكود (Design Patterns & Code Quality)
هنا يظهر “المهندس” الذي يبني أنظمة قابلة للصيانة والتطوير، وليس مجرد “مكدس” أكواد.
10. كيف تطبق مبدأ المسؤولية الواحدة (SRP) في Laravel؟
الإجابة النموذجية: “مبدأ المسؤولية الواحدة (Single Responsibility Principle) ينص على أن كل كلاس يجب أن يكون له سبب واحد فقط للتغيير. في لارافيل، أكبر خطأ هو الـ Fat Controller الذي يفعل كل شيء: يستقبل الطلب، يتحقق من البيانات، ينفذ منطق العمل، ويتعامل مع قاعدة البيانات.
لتطبيق SRP، أوزع المسؤوليات كالتالي:
- التحقق من المدخلات (Validation): أنقله من الـ Controller إلى Form Request Class مخصص.
- منطق العمل المعقد (Business Logic): أنقله إلى Service Class. هذا الكلاس هو المسؤول عن تنفيذ المهمة الفعلية (مثل
CreateUserService). - الاستعلامات المعقدة: أستخدم Eloquent Scopes أو Repository Pattern (في حالات محددة) لعزل منطق الاستعلامات.
- المهام البسيطة والمباشرة: ظهر نمط جديد يسمى Action Classes، وهي كلاسات صغيرة لها دالة
executeواحدة، تقوم بمهمة واحدة فقط.
هكذا، يصبح دور الـ Controller بسيطاً جداً وواضحاً: مجرد “منسق” يستقبل الطلب من الـ Form Request، يستدعي الـ Service أو الـ Action المناسب، ويعيد الـ Response. الكود يصبح نظيفاً، قابلاً للاختبار، وسهل الفهم.”
11. متى تستخدم نمط المستودع (Repository Pattern)؟
الإجابة النموذجية: “بصراحة، في معظم مشاريع لارافيل، الـ Repository Pattern يعتبر تعقيداً إضافياً (Overkill) لأن Eloquent بحد ذاته يعتبر تطبيقاً لهذا النمط بشكل أو بآخر (Active Record). لكن، هناك حالتان استراتيجيتان ألجأ فيهما لاستخدامه:
- عندما أحتاج لطبقة Caching مركزية ومعقدة: إذا كان لدي منطق كاش معقد للاستعلامات (مثل التخزين المؤقت، ثم محاولة الجلب من مصدر آخر، ثم التخزين)، فإن وضع هذا المنطق في Repository يفصل بين منطق جلب البيانات ومنطق الكاش بشكل نظيف.
- عندما يكون هناك احتمال لتغيير مصدر البيانات: لو كنت أبني نظاماً قد يقرأ بياناته من MySQL اليوم، ولكن في المستقبل قد يقرأها من API خارجي، أو ملفات JSON، أو قاعدة بيانات NoSQL. الـ Repository يعمل كواجهة موحدة (Interface) تخفي تفاصيل مصدر البيانات عن باقي التطبيق (
UserRepositoryInterface).
غير هاتين الحالتين، أكتفي بالـ Service Classes مع Eloquent مباشرة للحفاظ على بساطة الكود.”
12. ما فائدة استخدام كائنات نقل البيانات (DTOs – Data Transfer Objects)؟
الإجابة النموذجية: “الـ DTOs هي كلاسات بسيطة وغير قابلة للتغيير (immutable) وظيفتها الوحيدة هي نقل البيانات بين طبقات التطبيق بشكل منظم ومحدد النوع (Strictly Typed). بدلاً من تمرير مصفوفة (array) عشوائية من الـ Controller إلى الـ Service، وأظل أتساءل ما هي المفاتيح الموجودة فيها، أنا أنشئ DTO.
مثلاً، CreateUserDTO. هذا الكلاس يحتوي على الخصائص التي أحتاجها لإنشاء مستخدم (name, email, password) مع تحديد أنواعها. هذا يحقق فوائد هائلة:
- وضوح الكود: تعرف بالضبط ما هي البيانات التي يتم تمريرها.
- أمان الأنواع (Type Safety): يمنع الأخطاء المنطقية الناتجة عن أخطاء إملائية في مفاتيح المصفوفة أو أنواع بيانات خاطئة.
- سهولة التعديل (Refactoring): إذا تغيرت البيانات المطلوبة، كل ما عليك هو تعديل الـ DTO، وسيساعدك الـ IDE على إيجاد كل الأماكن التي تحتاج لتعديل.
ومع ميزات PHP 8 مثل الـ Constructor Property Promotion، أصبح تعريفها سهلاً جداً. هي خطوة صغيرة تحدث فرقاً كبيراً في جودة وصيانة الكود.”
class CreateUserDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $password,
) {}
}
القسم الرابع: الأداء والتوسع (Performance & Scalability)
تطبيقك يعمل بشكل جيد مع 10 مستخدمين، ولكن هل سيصمد أمام 100 ألف مستخدم؟ هنا تكمن الخبرة.
13. كيف تجعل تطبيق لارافيل قابلاً للتوسع الأفقي (Horizontally Scalable)؟
الإجابة النموذجية: “المبدأ الأساسي للتوسع الأفقي (إضافة خوادم جديدة) هو أن يكون التطبيق Stateless، أي لا يخزن أي حالة خاصة بالطلب على الخادم نفسه. كل طلب يجب أن يكون مستقلاً بذاته. لتحقيق ذلك في لارافيل، يجب عزل كل ما هو خاص بالحالة (State) في خدمات مشتركة:
- الجلسات (Sessions): نقل تخزين الجلسات من
file(الافتراضي) إلى مكان مشترك مثلredisأوdatabase. - الكاش (Cache): استخدام نظام كاش مركزي مشترك مثل Redis أو Memcached بدلاً من الكاش المعتمد على الملفات.
- الملفات المرفوعة (Uploads): عدم تخزينها على القرص المحلي للخادم. يجب استخدام خدمات تخزين سحابية (Cloud Storage) مثل Amazon S3 أو DigitalOcean Spaces.
- طوابير العمل (Queues): استخدام سائق (driver) للطوابير يعتمد على خدمة مركزية مثل Redis أو SQS بدلاً من
sync.
عندما يكون التطبيق Stateless، يمكننا ببساطة إضافة خوادم ويب جديدة خلف موازن تحميل (Load Balancer) وكل شيء سيعمل بسلاسة.”
14. ما هي استراتيجيات التخزين المؤقت (Caching) التي تعتمدها؟
الإجابة النموذجية: “الكاش ليس مجرد Cache::rememberForever. استراتيجيتي متعددة الطبقات لتعظيم الأداء:
- كاش الاستعلامات (Query Caching): للبيانات التي لا تتغير كثيراً، مثل قائمة الدول أو التصنيفات. أستخدم
Cache::rememberلتخزين نتيجة الاستعلام لفترة معينة. - كاش الكائنات (Object Caching): تخزين كائنات Eloquent كاملة بعد معالجتها. هذا مفيد للصفحات الشخصية للمستخدمين أو صفحات المنتجات.
- كاش الأجزاء (Fragment Caching): في قوالب Blade، يمكن عمل كاش لجزء معين من الصفحة لا يتغير كثيراً، مثل الـ sidebar الذي يعرض آخر المقالات، باستخدام
@cachedirective. - الكاش باستخدام الوسوم (Tag-based Caching): هذه أهم تقنية للإدارة الدقيقة للكاش. عندما أخزن بيانات متعلقة بمستخدم معين، أعطيها وسماً مثل
user.{$id}.posts. عندما يعدل المستخدم أي مقال، بدلاً من مسح كل الكاش، أنا أمسح فقط الكاش الذي يحمل هذا الوسم باستخدامCache::tags('...')->flush(). هذا يعطي تحكماً دقيقاً جداً ويمنع مسح بيانات غير ضرورية.
15. متى تستخدم طوابير العمل (Queues)؟
الإجابة النموذجية: “القاعدة بسيطة: أي عملية تأخذ أكثر من 500 ميلي ثانية يجب أن توضع في Queue. الهدف هو الحفاظ على تجربة مستخدم سريعة وفورية، وعدم جعل المستخدم ينتظر انتهاء عملية طويلة.
أمثلة عملية:
- إرسال إيميلات الترحيب أو الفواتير.
- معالجة الصور بعد رفعها (تغيير الحجم، إضافة علامة مائية).
- توليد التقارير المعقدة التي تتطلب استعلامات كثيرة.
- استدعاء خدمات خارجية (Third-party APIs) قد تكون بطيئة في الاستجابة.
- معالجة Webhooks القادمة من خدمات أخرى.
بهذه الطريقة، التطبيق يعيد استجابة للمستخدم فوراً (مثلاً: “تم استلام طلبك وجاري معالجته”)، والعملية الثقيلة تتم في الخلفية بواسطة worker متخصص بدون أن يشعر المستخدم بأي بطء.”
16. ما هو الفرق بين القفل المتفائل (Optimistic Locking) والقفل المتشائم (Pessimistic Locking)؟
الإجابة النموذجية: “كلاهما يستخدم لمنع ما يسمى بـ Race Conditions، أي عندما يحاول مستخدمان تعديل نفس السجل في نفس اللحظة (مثلاً، مقعد سينما واحد يحاول شخصان حجزه). الفرق يكمن في الفلسفة والتطبيق.”
| النوع | Pessimistic Locking (القفل المتشائم) | Optimistic Locking (القفل المتفائل) |
|---|---|---|
| الفلسفة | “متشائم”، يفترض أن التضارب سيحدث بالتأكيد. | “متفائل”، يفترض أن التضارب نادر الحدوث. |
| آلية العمل | يقوم بقفل السجل في قاعدة البيانات (Row Lock) ويمنع أي عملية أخرى من القراءة أو الكتابة عليه حتى تنتهي العملية الحالية. في لارافيل نستخدم sharedLock() أو lockForUpdate(). |
لا يقفل السجل، ولكنه يضيف عمود version أو يستخدم updated_at. قبل الحفظ، يتحقق إذا كانت النسخة التي قرأها هي نفس النسخة الحالية. إذا تغيرت، يرمي خطأ ويترك للتطبيق مهمة التعامل معه. |
| حالات الاستخدام | مناسب جداً للأنظمة المالية الحساسة، حجز التذاكر، أو أي عملية لا يمكن تحمل التضارب فيها أبداً. | مناسب لمعظم تطبيقات الويب (مثل تحديث مقال أو ملف شخصي) حيث يكون احتمال التضارب منخفضاً. أداؤه أفضل بكثير. |
القسم الخامس: PHP الحديثة والأمان (Modern PHP & Security)
المبرمج الخبير يواكب التطورات ولا يزال يكتب كود PHP 5.
17. ما الجديد في PHP 8.x الذي يحسن كود Laravel بشكل مباشر؟
الإجابة النموذجية: “PHP 8 قدمت تحسينات رائعة غيرت طريقة كتابتنا للكود وجعلته أكثر وضوحاً وأماناً:
- Constructor Property Promotion: اختصرت تعريف الـ DTOs والـ Services بشكل هائل. أصبح تعريف الخاصية وإسنادها في الـ constructor يتم في سطر واحد.
- Named Arguments: جعلت استدعاء الدوال أكثر وضوحاً. لم نعد بحاجة لتذكر ترتيب الباراميترات، خصوصاً في الدوال التي تقبل الكثير منها مثل
response()->json(). - Enums (من PHP 8.1): أخيراً! بدلاً من استخدام السلاسل النصية السحرية مثل ‘pending’, ‘paid’، أصبح بإمكاننا تعريف حالات ثابتة وواضحة، مما يقلل الأخطاء ويزيد من مقروئية الكود.
- Match Expression: بديل أذكى وأكثر أماناً للـ
switch، فهو يتطلب معالجة كل الحالات ويقوم بمقارنة صارمة (strict comparison). - Nullsafe Operator (
?->): يمنع الأخطاء عند محاولة الوصول لخاصية على كائن قد يكونnull، ويبسط الكود بشكل كبير.
18. كيف تحمي التطبيق من ثغرات التعيين الشامل (Mass Assignment)؟
الإجابة النموذجية: “هذه من أخطر الثغرات وأسهلها. تحدث عندما نمرر كل بيانات الطلب $request->all() مباشرة إلى Model::create(). المهاجم يمكنه إضافة حقل is_admin=1 في طلبه ويحصل على صلاحيات مدير.
الحماية تتم بخطوتين لا ثالث لهما:
- الحماية على مستوى الموديل: نحدد بدقة الحقول المسموح بتعبئتها عبر خاصية
$fillableفي الموديل. أو، وهو الأفضل، نترك$fillableفارغة ونستخدم$guarded = ['*']لمنع التعيين الشامل تماماً. - الحماية على مستوى المتحكم (الأهم): لا نثق أبداً ببيانات الطلب الخام. نستخدم دائماً البيانات التي تم التحقق منها فقط من الـ Form Request، عبر
$request->validated(). هذه الدالة ترجع فقط الحقول التي تم تعريفها والتحقق منها في مصفوفةrulesالخاصة بالـ Form Request.
القاعدة الذهبية: لا تستخدم $request->all() أو $request->except() أبداً في عمليات الإنشاء أو التحديث. استخدم دائماً $request->validated().”
19. ما هي الـ Traits ومتى تصبح خطيرة؟
الإجابة النموذجية: “الـ Traits هي آلية لإعادة استخدام الكود بشكل أفقي في لغة لا تدعم الوراثة المتعددة (Multiple Inheritance) مثل PHP. هي مفيدة جداً لمشاركة وظائف صغيرة ومركزة بين كلاسات غير مرتبطة ببعضها، مثل SoftDeletes أو Notifiable في لارافيل.
لكنها تصبح خطيرة عندما يساء استخدامها:
- عندما تصبح “مكب نفايات”: يتم رمي كود ضخم وغير مترابط داخل Trait واحد، مما يخفي التعقيد ويجعل تتبع الأخطاء كابوساً. الـ Trait يجب أن يتبع مبدأ المسؤولية الواحدة أيضاً.
- عندما تخلق تبعيات خفية: عندما تبدأ الـ Traits بالاعتماد على بعضها البعض، أو على خصائص وميثودات يجب أن تكون موجودة في الكلاس الذي يستخدمها. هذا يخلق اقتراناً خفياً وهشاً يجعل الكود صعب الصيانة.
يجب أن تكون الـ Trait صغيرة، مركزة، ومستقلة قدر الإمكان.”
20. كيف تقوم بتأمين مصادقة الـ API؟
الإجابة النموذجية: “يعتمد على نوع العميل (Client) الذي سيتفاعل مع الـ API:
- للتطبيقات الطرف الأول (First-party) مثل تطبيقات الصفحة الواحدة (SPAs) التي تخدمها لارافيل نفسها، أو تطبيقات الموبايل الخاصة بنفس الشركة: أستخدم Laravel Sanctum. هو خفيف، بسيط، ويوفر طريقتين:
- Stateful Authentication: يعتمد على نظام الجلسات (Cookie-based) الخاص بلارافيل، وهو آمن جداً وسهل الإعداد للـ SPAs.
- Stateless Authentication: يعتمد على Tokens (API Tokens) بسيطة، وهو مناسب لتطبيقات الموبايل أو السكربتات.
- للتطبيقات الخارجية (Third-party) أو عندما أحتاج صلاحيات معقدة (Scopes): أستخدم Laravel Passport. هو تطبيق كامل لـ OAuth2 server. هو أثقل وأكثر تعقيداً، لكنه يعطي قوة هائلة في تحديد الصلاحيات (مثل “السماح لهذا التطبيق بقراءة بيانات المستخدم فقط، وليس تعديلها”) والسماح لتطبيقات أخرى بالتفاعل مع الـ API الخاص بي نيابة عن المستخدم بشكل آمن.
وبالطبع، بغض النظر عن الحزمة المستخدمة، دائماً أفرض استخدام HTTPS لتشفير الاتصال، وأطبق Rate Limiting لمنع هجمات تخمين كلمات المرور أو الاستخدام المفرط.”
الخلاصة 💡
يا جماعة، هذه الإجابات لم تكن مجرد كلمات محفوظة، بل كانت نابعة من تجربة وممارسة حقيقية. في غزة، في مصر، في السعودية، في الأردن، في كل مكان، الشركات الكبيرة لا تبحث عن “كاتب أكواد”، بل تبحث عن “مهندس يحل المشاكل” (Problem Solver).
إذا كنت تستطيع الإجابة على هذه الأسئلة بفهم عميق لـ “لماذا” وليس فقط “كيف”، فأنت جاهز ليس فقط لاجتياز أي مقابلة، بل لقيادة فريق تقني وتحمّل مسؤولية مشاريع كبيرة.
أتمنى لكم كل التوفيق. وهل واجهت سؤالاً صعباً في مقابلة من قبل؟ شاركه معنا في التعليقات لنستفيد جميعاً!

