Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط المستودع

جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 81 من 91.

إن نمط المستودع (Repository Pattern) هو نمط تصميم يتوسط بين منطق العمل في تطبيقك وتخزين البيانات. فهو يوفر واجهة تشبه المجموعات للوصول إلى كائنات المجال (domain objects)، مع إخفاء تفاصيل كيفية تخزين البيانات أو استرجاعها فعلياً.

فكر في المستودع (repository) كمجموعة متخصصة تعرف كيفية حفظ واسترجاع الكائنات. يطلب كود تطبيقك من المستودع مستخدماً عن طريق المعرف (ID) أو يحفظ منتجاً جديداً، دون معرفة ما إذا كانت البيانات موجودة في قاعدة بيانات، أو ملف، أو واجهة برمجة تطبيقات (API). هذا الفصل يجعل الكود الخاص بك أكثر قابلية للاختبار ومرونة.

<?php
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(int $id): void;
}

class User {
    public function __construct(
        public int $id,
        public string $name,
        public string $email
    ) {}
}

يتعامل تنفيذ المستودع مع آلية التخزين الفعلية:

<?php
class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    
    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }
    
    public function findAll(): array {
        return array_values($this->users);
    }
    
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
    
    public function delete(int $id): void {
        unset($this->users[$id]);
    }
}

$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$repo->save(new User(2, "Bob", "bob@example.com"));

echo $repo->find(1)->name . "\n";
echo count($repo->findAll());

المخرجات:

Alice
2

جمال هذا النمط يكمن في أنه يمكنك تبديل التنفيذات دون تغيير منطق العمل (business logic) الخاص بك. هل تحتاج إلى الانتقال من التخزين في الذاكرة إلى قاعدة بيانات؟ قم بإنشاء فئة مستودع (repository class) جديدة تنفذ نفس الواجهة (interface). يظل كود تطبيقك دون تغيير لأنه يعتمد على الواجهة، وليس على التنفيذ الملموس.

challenge icon

التحدي

سهل

لنقم ببناء نظام مخزون منتجات باستخدام نمط المستودع (Repository Pattern). ستقوم بإنشاء فصل تام بين كائنات النطاق (domain objects) وكيفية تخزينها، مما يسمح لكود تطبيقك بالتعامل مع المنتجات دون معرفة تفاصيل التخزين.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • Product.php — أنشئ فئة Product تمثل العناصر في مخزونك. لكل منتج id (int)، و name (string)، و price (float). استخدم خاصية constructor promotion مع خصائص عامة (public) لسهولة الوصول.
  • ProductRepositoryInterface.php — حدد واجهة ProductRepositoryInterface التي تضع العقد لأي تنفيذ لتخزين المنتجات. يجب أن تعلن واجهتك عن هذه الطرق (methods):
    • find(int $id): ?Product — استرجاع منتج بواسطة المعرف (ID)، أو null إذا لم يتم العثور عليه
    • findAll(): array — الحصول على جميع المنتجات كمصفوفة
    • save(Product $product): void — تخزين أو تحديث منتج
    • delete(int $id): void — إزالة منتج بواسطة المعرف (ID)
  • InMemoryProductRepository.php — قم بتضمين كل من فئة Product والواجهة. أنشئ فئة InMemoryProductRepository التي تنفذ الواجهة باستخدام مصفوفة خاصة (private array) لتخزين المنتجات. يجب فهرسة المنتجات بواسطة معرفاتها (ID) للبحث الفعال. يجب أن تعيد طريقة findAll() كائنات المنتجات فقط (استخدم array_values() لإعادة تعيين مفاتيح المصفوفة).
  • main.php — قم بتضمين ملف المستودع. ستتلقى مدخلاً واحداً: سلسلة JSON تحتوي على مصفوفة من المنتجات لإضافتها، متبوعة بمعرف (ID) للبحث عنه.

    تنسيق JSON سيكون:

    [{"id": 1, "name": "Laptop", "price": 999.99}, {"id": 2, "name": "Mouse", "price": 29.99}]

    أنشئ InMemoryProductRepository، واحفظ جميع المنتجات من مدخل JSON، ثم استخدم المدخل الثاني (المعرف ID) للعثور على منتج معين. اطبع النتائج بهذا التنسيق:

    Total products: [count]
    Found: [name] - $[price]

    قم بتنسيق السعر إلى منزلتين عشريتين. إذا لم يتم العثور على المنتج، اطبع Found: Not found بدلاً من ذلك.

يتجلى نمط المستودع (Repository Pattern) عندما تحتاج إلى تبديل تطبيقات التخزين. يعتمد الكود الرئيسي الخاص بك فقط على الواجهة — فتبديل التخزين من الذاكرة (in-memory) إلى قاعدة بيانات سيعني إنشاء فئة مستودع جديدة دون تغيير أي من منطق التطبيق.

ورقة مرجعية

يعمل نمط المستودع (Repository Pattern) كوسيط بين منطق الأعمال وتخزين البيانات، حيث يوفر واجهة تشبه المجموعات للوصول إلى كائنات النطاق مع إخفاء تفاصيل تنفيذ التخزين.

قم بتعريف واجهة مستودع (repository interface) تحدد العقد الخاص بالوصول إلى البيانات:

<?php
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(int $id): void;
}

قم بإنشاء فئة نطاق (domain class) باستخدام ترقية المنشئ (constructor promotion):

<?php
class User {
    public function __construct(
        public int $id,
        public string $name,
        public string $email
    ) {}
}

قم بتنفيذ واجهة المستودع باستخدام آلية تخزين ملموسة:

<?php
class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    
    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }
    
    public function findAll(): array {
        return array_values($this->users);
    }
    
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
    
    public function delete(int $id): void {
        unset($this->users[$id]);
    }
}

استخدم المستودع من خلال واجهته:

$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$user = $repo->find(1);
$allUsers = $repo->findAll();

يسمح هذا النمط بتبديل التنفيذات دون تغيير منطق الأعمال — حيث يعتمد الكود الخاص بك على الواجهة، وليس على التنفيذ الملموس.

جرّب بنفسك

<?php

require_once 'InMemoryProductRepository.php';

// قراءة المدخلات
$jsonInput = trim(fgets(STDIN));
$searchId = intval(trim(fgets(STDIN)));

// تحليل مدخلات JSON إلى مصفوفة
$productsData = (array)json_decode($jsonInput, true);

// TODO: إنشاء مثيل من InMemoryProductRepository

// TODO: التكرار عبر $productsData وحفظ كل منتج
// كل عنصر يحتوي على المفاتيح 'id' و 'name' و 'price'

// TODO: الحصول على جميع المنتجات وطباعة العدد الإجمالي
// التنسيق: "Total products: [count]"

// TODO: البحث عن المنتج باستخدام $searchId
// إذا تم العثور عليه، اطبع: "Found: [name] - $[price]" (السعر منسق إلى منزلتين عشريتين)
// إذا لم يتم العثور عليه، اطبع: "Found: Not found"

?>
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming