سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Data Mapper Pattern در PHP چیست؟ آموزش الگوی Data Mapper با مثال عملی

Data Mapper Pattern در PHP چیست؟ آموزش الگوی Data Mapper با مثال عملی

ممدیر کل سیستم ۲۵ شهریور ۱۴۰۵ ۱۸ دقیقه مطالعه ۱ بازدید
Data Mapper Pattern در PHP چیست؟ آموزش الگوی Data Mapper با مثال عملی

در بسیاری از پروژه‌های PHP، اطلاعات برنامه مستقیماً داخل مدل‌هایی قرار می‌گیرند که با دیتابیس ارتباط دارند. این روش برای پروژه‌های ساده مناسب است، اما در پروژه‌های بزرگ‌تر ممکن است باعث وابستگی شدید منطق برنامه به ساختار دیتابیس شود.

برای مثال، در یک سیستم فروشگاهی ممکن است کلاس Product هم‌زمان مسئولیت‌های زیر را بر عهده داشته باشد:

  • نگهداری اطلاعات محصول
  • اعتبارسنجی قوانین تجاری
  • اجرای Queryهای SQL
  • ذخیره محصول در دیتابیس
  • حذف محصول
  • تبدیل داده‌های دیتابیس به Object
  • مدیریت ارتباط با جدول‌ها

این ترکیب مسئولیت‌ها باعث می‌شود تغییر ساختار دیتابیس روی منطق اصلی برنامه اثر بگذارد.

Data Mapper Pattern برای حل همین مشکل استفاده می‌شود. در این الگو، Entity یا Domain Object از دیتابیس مستقل می‌ماند و یک لایه جداگانه وظیفه انتقال داده بین Objectهای برنامه و محل ذخیره‌سازی را بر عهده می‌گیرد. <Cite refs={["turn0search0","turn0search12"]} />

Data Mapper Pattern چیست؟

Data Mapper یک الگوی طراحی برای جداسازی مدل Domain از لایه ذخیره‌سازی اطلاعات است.

در این روش، Entity فقط اطلاعات و رفتارهای مربوط به Domain را نگهداری می‌کند و از جزئیات زیر اطلاعی ندارد:

  • نام جدول دیتابیس
  • Queryهای SQL
  • نوع Database
  • PDO
  • ساختار ستون‌ها
  • روش ذخیره‌سازی
  • نحوه بازیابی اطلاعات

یک کلاس جداگانه به نام Mapper، داده‌ها را بین دیتابیس و Entity منتقل می‌کند.

ساختار ساده:


Database

   ▲

   │

Data Mapper

   ▲

   │

Domain Entity

   ▲

   │

Application

در مستندات DesignPatternsPHP نیز Data Mapper به عنوان بخشی از Data Access Layer معرفی شده است که داده‌ها را به‌صورت دوطرفه بین Persistent Storage و مدل درون حافظه منتقل می‌کند. هدف آن مستقل نگه‌داشتن Domain از دیتابیس است. <Cite refs={["turn0search0","turn0search2"]} />

اجزای اصلی Data Mapper Pattern

۱. Entity

Entity نماینده یک مفهوم واقعی در Domain است؛ مانند:

  • User
  • Product
  • Order
  • Invoice
  • Service
  • Subscription

Entity نباید مسئول اجرای Query یا اتصال به دیتابیس باشد.

۲. Data Mapper

Mapper مسئول تبدیل داده‌های دیتابیس به Entity و تبدیل Entity به داده قابل ذخیره در دیتابیس است.

۳. Data Source

Data Source محل ذخیره اطلاعات است؛ برای مثال:

  • MySQL
  • PostgreSQL
  • SQLite
  • فایل
  • API خارجی
  • Redis

۴. Repository

Repository در بعضی پیاده‌سازی‌ها برای ارائه یک Interface مناسب جهت جست‌وجو و دریافت Entityها استفاده می‌شود. Repository و Data Mapper دقیقاً یک مفهوم نیستند، اما می‌توانند در کنار هم استفاده شوند.

۵. Service یا Application Layer

این لایه از Entity و Repository استفاده می‌کند و نباید درگیر جزئیات SQL شود.

مشکل روش مستقیم

فرض کنید کلاس زیر را داریم:


<?php



declare(strict_types=1);



final class User

{

    public function __construct(

        public int $id,

        public string $name,

        public string $email

    ) {

    }



    public static function find(int $id): ?self

    {

        // اجرای Query در داخل Entity

        return null;

    }



    public function save(): void

    {

        // ذخیره مستقیم در دیتابیس

    }

}

در این مثال، کلاس User هم Entity است و هم مسئول ارتباط با دیتابیس.

مشکلات این روش:

  • تست Entity بدون دیتابیس دشوار می‌شود.
  • تغییر دیتابیس به تغییر Entity نیاز دارد.
  • منطق Domain با SQL ترکیب می‌شود.
  • مسئولیت کلاس بیش از حد زیاد می‌شود.
  • استفاده از Entity در محیط‌های مختلف سخت‌تر می‌شود.

Data Mapper تلاش می‌کند این وابستگی را حذف کند.

ساخت Entity مستقل از دیتابیس

ابتدا یک Entity ساده برای User ایجاد می‌کنیم:


<?php



declare(strict_types=1);



final class User

{

    public function __construct(

        private readonly int $id,

        private string $name,

        private string $email

    ) {

    }



    public function id(): int

    {

        return $this->id;

    }



    public function name(): string

    {

        return $this->name;

    }



    public function email(): string

    {

        return $this->email;

    }



    public function changeName(string $name): void

    {

        if (trim($name) === '') {

            throw new InvalidArgumentException(

                'User name cannot be empty.'

            );

        }



        $this->name = $name;

    }



    public function changeEmail(string $email): void

    {

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {

            throw new InvalidArgumentException(

                'Invalid email address.'

            );

        }



        $this->email = $email;

    }

}

این کلاس فقط مسئول نگهداری و مدیریت وضعیت User است. هیچ Query، PDO یا نام جدولی در آن وجود ندارد.

ساخت Data Mapper Interface

حالا Interface مربوط به Mapper را تعریف می‌کنیم:


<?php



declare(strict_types=1);



interface UserMapperInterface

{

    public function findById(int $id): ?User;



    public function insert(User $user): void;



    public function update(User $user): void;

}

این Interface مشخص می‌کند که Mapper چه عملیاتی را پشتیبانی می‌کند.

پیاده‌سازی Mapper با PDO

در این مثال از PDO استفاده می‌کنیم:


<?php



declare(strict_types=1);



final class PdoUserMapper implements UserMapperInterface

{

    public function __construct(

        private readonly PDO $pdo

    ) {

    }



    public function findById(int $id): ?User

    {

        $statement = $this->pdo->prepare(

            'SELECT id, name, email

             FROM users

             WHERE id = :id'

        );



        $statement->execute([

            'id' => $id,

        ]);



        $row = $statement->fetch(PDO::FETCH_ASSOC);



        if ($row === false) {

            return null;

        }



        return new User(

            id: (int) $row['id'],

            name: (string) $row['name'],

            email: (string) $row['email']

        );

    }



    public function insert(User $user): void

    {

        $statement = $this->pdo->prepare(

            'INSERT INTO users (id, name, email)

             VALUES (:id, :name, :email)'

        );



        $statement->execute([

            'id' => $user->id(),

            'name' => $user->name(),

            'email' => $user->email(),

        ]);

    }



    public function update(User $user): void

    {

        $statement = $this->pdo->prepare(

            'UPDATE users

             SET name = :name, email = :email

             WHERE id = :id'

        );



        $statement->execute([

            'id' => $user->id(),

            'name' => $user->name(),

            'email' => $user->email(),

        ]);

    }

}

در اینجا PdoUserMapper مسئول تبدیل داده‌های دیتابیس به Entity و تبدیل Entity به پارامترهای SQL است.

کلاس User همچنان از وجود PDO و SQL اطلاعی ندارد.

استفاده از Mapper


<?php



$pdo = new PDO(

    'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4',

    'root',

    'password',

    [

        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,

        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,

    ]

);



$mapper = new PdoUserMapper($pdo);



$user = $mapper->findById(1);



if ($user !== null) {

    echo $user->name();

}

در این کد، برنامه برای دریافت User مستقیماً SQL اجرا نمی‌کند. فقط از Mapper استفاده می‌کند.

ساخت Repository در کنار Data Mapper

برای اینکه کد برنامه از جزئیات Mapper نیز مستقل شود، می‌توان یک Repository تعریف کرد:


<?php



declare(strict_types=1);



interface UserRepositoryInterface

{

    public function findById(int $id): ?User;



    public function save(User $user): void;

}

پیاده‌سازی Repository:


<?php



declare(strict_types=1);



final class UserRepository implements UserRepositoryInterface

{

    public function __construct(

        private readonly UserMapperInterface $mapper

    ) {

    }



    public function findById(int $id): ?User

    {

        return $this->mapper->findById($id);

    }



    public function save(User $user): void

    {

        $existingUser = $this->mapper->findById($user->id());



        if ($existingUser === null) {

            $this->mapper->insert($user);



            return;

        }



        $this->mapper->update($user);

    }

}

حالا Application Layer با Repository کار می‌کند و Repository از Mapper استفاده می‌کند.

ساختار:


Controller

   │

   ▼

Application Service

   │

   ▼

UserRepository

   │

   ▼

UserMapper

   │

   ▼

Database

نمونه Application Service


<?php



declare(strict_types=1);



final class UserService

{

    public function __construct(

        private readonly UserRepositoryInterface $users

    ) {

    }



    public function renameUser(

        int $userId,

        string $newName

    ): void {

        $user = $this->users->findById($userId);



        if ($user === null) {

            throw new RuntimeException('User not found.');

        }



        $user->changeName($newName);



        $this->users->save($user);

    }

}

این Service فقط منطق کاربردی را اجرا می‌کند. هیچ اطلاعی از SQL یا جدول users ندارد.

تفاوت Data Mapper و Active Record

یکی از مهم‌ترین موضوعات در این الگو، تفاوت آن با Active Record است.

در Active Record، Object معمولاً هم نماینده یک رکورد دیتابیس است و هم عملیات ذخیره، حذف و بازیابی را انجام می‌دهد.

در Data Mapper، Entity از دیتابیس مستقل است و عملیات ذخیره‌سازی به Mapper سپرده می‌شود. <Cite refs={["turn0search0","turn0search5"]} />

ویژگیData MapperActive Record
محل QueryهاMapper یا Data Access Layerمعمولاً داخل Model
وابستگی Entity به دیتابیسکم یا مستقلزیاد
مناسب برای Domain پیچیدهمعمولاً مناسب‌ترممکن است محدودکننده شود
سادگی شروع پروژهپیچیده‌ترساده‌تر
جداسازی مسئولیت‌هابیشترکمتر
نمونه رایجDoctrineEloquent در Laravel

مثال Active Record در Laravel


<?php



use Illuminate\Database\Eloquent\Model;



final class User extends Model

{

    protected $table = 'users';

}

استفاده:


$user = User::find(1);



$user->name = 'Ali';



$user->save();

در این مدل، User هم مدل Domain و هم نماینده رکورد دیتابیس است.

رویکرد Data Mapper

در رویکرد Data Mapper، Entity مستقل است:


$user = new User(

    id: 1,

    name: 'Ali',

    email: 'ali@example.com'

);

سپس Mapper یا Repository وظیفه ذخیره آن را بر عهده می‌گیرد:


$repository->save($user);

هیچ‌کدام از متدهای save یا find داخل Entity قرار ندارند.

آیا Laravel از Data Mapper استفاده می‌کند؟

Laravel به‌صورت پیش‌فرض از Eloquent ORM استفاده می‌کند و Eloquent بیشتر به الگوی Active Record نزدیک است.

این موضوع به معنی بدبودن Eloquent نیست. Eloquent برای بسیاری از پروژه‌های Laravel ساده، سریع و کاربردی است.

اما اگر پروژه شما دارای شرایط زیر باشد، ممکن است بخواهید Entity و Persistence را بیشتر از هم جدا کنید:

  • Domain پیچیده
  • قوانین تجاری زیاد
  • چند منبع داده
  • نیاز به تست Domain بدون دیتابیس
  • استفاده از دیتابیس‌های مختلف
  • وجود Legacy Database
  • نیاز به مدل‌هایی متفاوت از ساختار جدول‌ها
  • استفاده از معماری Hexagonal یا Clean Architecture

در چنین شرایطی می‌توان از Entityهای مستقل، Repository و Mapper اختصاصی استفاده کرد.

پیاده‌سازی ساده Data Mapper در Laravel

فرض کنید یک Entity برای محصول داریم:


<?php



namespace App\Domain\Product;



final class Product

{

    public function __construct(

        private readonly int $id,

        private string $title,

        private int $price

    ) {

    }



    public function id(): int

    {

        return $this->id;

    }



    public function title(): string

    {

        return $this->title;

    }



    public function price(): int

    {

        return $this->price;

    }



    public function changePrice(int $price): void

    {

        if ($price < 0) {

            throw new \InvalidArgumentException(

                'Price cannot be negative.'

            );

        }



        $this->price = $price;

    }

}

Interface مربوط به Mapper:


<?php



namespace App\Domain\Product;



interface ProductMapperInterface

{

    public function findById(int $id): ?Product;



    public function save(Product $product): void;

}

پیاده‌سازی با Query Builder:


<?php



namespace App\Infrastructure\Product;



use App\Domain\Product\Product;

use App\Domain\Product\ProductMapperInterface;

use Illuminate\Support\Facades\DB;



final class DatabaseProductMapper implements ProductMapperInterface

{

    public function findById(int $id): ?Product

    {

        $row = DB::table('products')

            ->where('id', $id)

            ->first();



        if ($row === null) {

            return null;

        }



        return new Product(

            id: (int) $row->id,

            title: (string) $row->title,

            price: (int) $row->price

        );

    }



    public function save(Product $product): void

    {

        DB::table('products')->updateOrInsert(

            [

                'id' => $product->id(),

            ],

            [

                'title' => $product->title(),

                'price' => $product->price(),

            ]

        );

    }

}

ثبت در Service Container:


<?php



use App\Domain\Product\ProductMapperInterface;

use App\Infrastructure\Product\DatabaseProductMapper;



$this->app->bind(

    ProductMapperInterface::class,

    DatabaseProductMapper::class

);

حالا در Service:


<?php



namespace App\Application\Product;



use App\Domain\Product\ProductMapperInterface;



final class ProductService

{

    public function __construct(

        private readonly ProductMapperInterface $products

    ) {

    }



    public function increasePrice(

        int $productId,

        int $amount

    ): void {

        $product = $this->products->findById($productId);



        if ($product === null) {

            throw new \RuntimeException(

                'Product not found.'

            );

        }



        $product->changePrice(

            $product->price() + $amount

        );



        $this->products->save($product);

    }

}

در این ساختار، Domain Product از Laravel و دیتابیس مستقل است. فقط کلاس Infrastructure از Query Builder استفاده می‌کند.

مزایای Data Mapper Pattern

۱. جداسازی Domain از دیتابیس

Entityها به SQL، PDO یا ORM وابسته نیستند.

۲. تست‌پذیری بهتر

می‌توان Entity و قوانین تجاری را بدون اتصال به دیتابیس تست کرد.

۳. انعطاف در تغییر دیتابیس

اگر روش ذخیره‌سازی تغییر کند، لازم نیست منطق Domain بازنویسی شود.

۴. مناسب برای Domainهای پیچیده

وقتی قوانین تجاری زیاد هستند، مستقل‌بودن Entityها می‌تواند طراحی را واضح‌تر کند.

۵. رعایت بهتر Single Responsibility Principle

Entity مسئول Domain است و Mapper مسئول انتقال داده. این جداسازی با اصل Single Responsibility هم‌راستا است. <Cite refs={["turn0search0","turn0search12"]} />

۶. مناسب برای Legacy Database

اگر ساختار دیتابیس قدیمی و نامنظم باشد، Mapper می‌تواند آن را به مدل‌های تمیزتر Domain تبدیل کند.

۷. امکان استفاده از چند منبع داده

می‌توان Mapperهای متفاوتی برای MySQL، API، فایل یا حافظه ایجاد کرد.

معایب Data Mapper Pattern

۱. تعداد کلاس‌های بیشتر

ممکن است برای هر Entity به Mapper، Repository و DTO نیاز باشد.

۲. کدنویسی بیشتر

در مقایسه با Eloquent، پیاده‌سازی Data Mapper معمولاً کد بیشتری نیاز دارد.

۳. نیاز به طراحی دقیق

باید مشخص شود کدام مسئولیت در Entity، Mapper، Repository یا Service قرار می‌گیرد.

۴. پیچیدگی برای پروژه‌های کوچک

اگر پروژه ساده باشد، استفاده از Data Mapper ممکن است ارزش پیچیدگی اضافه را نداشته باشد.

۵. مدیریت Mapping دشوار

تبدیل داده‌های پیچیده بین جدول‌ها و Entityها می‌تواند نیازمند کد زیادی باشد.

چه زمانی از Data Mapper استفاده کنیم؟

Data Mapper در شرایط زیر انتخاب مناسبی است:

  • منطق Domain از CRUD ساده پیچیده‌تر است.
  • Entityها باید مستقل از ORM باشند.
  • تست‌کردن منطق بدون دیتابیس اهمیت دارد.
  • دیتابیس Legacy دارید.
  • ساختار Domain با ساختار جدول‌ها متفاوت است.
  • ممکن است دیتابیس یا منبع داده تغییر کند.
  • از معماری Clean Architecture یا Hexagonal استفاده می‌کنید.
  • یک Entity از چند جدول یا چند منبع داده ساخته می‌شود.

چه زمانی از Data Mapper استفاده نکنیم؟

برای یک پنل مدیریتی ساده با چند فرم CRUD، استفاده از Eloquent یا Query Builder ممکن است کافی باشد.

همچنین اگر تیم شما با Data Mapper آشنایی ندارد و پروژه نیاز به تحویل سریع دارد، بهتر است ابتدا نیاز واقعی به این معماری را بررسی کنید.

نباید صرفاً به دلیل حرفه‌ای‌به‌نظررسیدن معماری، تعداد لایه‌ها را افزایش داد.

تست Entity بدون دیتابیس

یکی از مزایای مهم Data Mapper این است که می‌توان قوانین Entity را بدون اتصال به دیتابیس تست کرد.


<?php



declare(strict_types=1);



use PHPUnit\Framework\TestCase;



final class UserTest extends TestCase

{

    public function test_user_can_change_name(): void

    {

        $user = new User(

            id: 1,

            name: 'Ali',

            email: 'ali@example.com'

        );



        $user->changeName('Reza');



        self::assertSame('Reza', $user->name());

    }



    public function test_empty_name_is_rejected(): void

    {

        $user = new User(

            id: 1,

            name: 'Ali',

            email: 'ali@example.com'

        );



        $this->expectException(InvalidArgumentException::class);



        $user->changeName('');

    }



    public function test_invalid_email_is_rejected(): void

    {

        $user = new User(

            id: 1,

            name: 'Ali',

            email: 'ali@example.com'

        );



        $this->expectException(InvalidArgumentException::class);



        $user->changeEmail('invalid-email');

    }

}

این تست‌ها بدون اجرای SQL و بدون نیاز به دیتابیس اجرا می‌شوند.

تست Mapper با Mock

برای تست Mapper می‌توان از Mock مربوط به PDO استفاده کرد، یا برای تست‌های واقعی‌تر از دیتابیس آزمایشی استفاده کرد.

در پروژه‌های Laravel می‌توان از موارد زیر استفاده کرد:

  • SQLite در حافظه
  • Database Transactions
  • Test Database
  • Mock کردن Query Builder
  • Integration Test

تست Entity معمولاً Unit Test است؛ اما تست Mapper اغلب به Integration Test نزدیک‌تر است، چون به لایه ذخیره‌سازی وابستگی دارد.

اشتباهات رایج در Data Mapper

اشتباه اول: قرار دادن SQL داخل Entity

اگر Entity مستقیماً Query اجرا کند، جداسازی اصلی Data Mapper از بین می‌رود.

اشتباه دوم: تبدیل Mapper به کلاس بسیار بزرگ

Mapper نباید تمام منطق تجاری برنامه را در خود جای دهد. وظیفه اصلی آن انتقال داده است.

اشتباه سوم: ترکیب بی‌دلیل Data Mapper و Active Record

اگر پروژه از Eloquent استفاده می‌کند، مخفی‌کردن Eloquent پشت چند لایه اضافی لزوماً به معنی Data Mapper واقعی نیست.

اشتباه چهارم: قرار دادن اعتبارسنجی در Mapper

قوانین تجاری مانند حداقل قیمت، وضعیت سفارش یا محدودیت تغییر وضعیت بهتر است در Domain یا Service قرار بگیرند.

اشتباه پنجم: نادیده‌گرفتن تراکنش‌ها

اگر ذخیره یک Entity شامل چند Query است، باید تراکنش دیتابیس در لایه مناسب مدیریت شود.

اشتباه ششم: تبدیل مستقیم همه داده‌ها به آرایه

اگر Domain پیچیده است، استفاده از آرایه‌های بدون ساختار می‌تواند باعث ازبین‌رفتن مزایای Entityهای مستقل شود.

اشتباه هفتم: ساخت لایه‌های غیرضروری

برای پروژه‌های کوچک، Entity، Mapper، Repository، Service و Factory هم‌زمان ممکن است بیش از نیاز باشند.

ساختار پیشنهادی پوشه‌ها در Laravel

یک ساختار ساده برای پروژه‌ای که از Data Mapper استفاده می‌کند:


app/

├── Domain/

│   └── Product/

│       ├── Product.php

│       └── ProductMapperInterface.php

│

├── Application/

│   └── Product/

│       └── ProductService.php

│

├── Infrastructure/

│   └── Product/

│       └── DatabaseProductMapper.php

│

└── Providers/

    └── AppServiceProvider.php

در این ساختار:

  • Domain شامل Entity و قوانین اصلی است.
  • Application شامل Use Caseها و سرویس‌های کاربردی است.
  • Infrastructure شامل دیتابیس و پیاده‌سازی‌های فنی است.
  • Providers وابستگی‌ها را در Service Container ثبت می‌کند.

Data Mapper و معماری Clean Architecture

Data Mapper با معماری‌های لایه‌ای، Clean Architecture و Hexagonal Architecture سازگاری خوبی دارد؛ زیرا وابستگی به سمت Domain وارد نمی‌شود.

در یک طراحی مناسب:


Infrastructure → Application → Domain

Domain نباید برای اجرای قوانین اصلی خود به دیتابیس یا Framework وابسته باشد.

البته این موضوع به معنی حذف کامل Laravel نیست. می‌توان از Laravel در لایه Application و Infrastructure استفاده کرد و Domain را تا حد امکان مستقل نگه داشت.

نمونه کامل Data Mapper در PHP


<?php



declare(strict_types=1);



final class Product

{

    public function __construct(

        private readonly int $id,

        private string $title,

        private int $price

    ) {

    }



    public function id(): int

    {

        return $this->id;

    }



    public function title(): string

    {

        return $this->title;

    }



    public function price(): int

    {

        return $this->price;

    }



    public function changePrice(int $price): void

    {

        if ($price < 0) {

            throw new InvalidArgumentException(

                'Price cannot be negative.'

            );

        }



        $this->price = $price;

    }

}



interface ProductMapperInterface

{

    public function findById(int $id): ?Product;



    public function save(Product $product): void;

}



final class InMemoryProductMapper implements ProductMapperInterface

{

    /**

     * @var array<int, Product>

     */

    private array $products = [];



    public function findById(int $id): ?Product

    {

        return $this->products[$id] ?? null;

    }



    public function save(Product $product): void

    {

        $this->products[$product->id()] = $product;

    }

}



$mapper = new InMemoryProductMapper();



$product = new Product(

    id: 1,

    title: 'Laptop',

    price: 50000000

);



$mapper->save($product);



$product->changePrice(52000000);



$mapper->save($product);



$storedProduct = $mapper->findById(1);



echo $storedProduct?->price();

در این نمونه به جای دیتابیس واقعی از حافظه استفاده شده است، اما ساختار کلی همان Data Mapper است. می‌توان بعداً InMemoryProductMapper را با DatabaseProductMapper جایگزین کرد، بدون اینکه Entity تغییر کند.

جمع‌بندی

Data Mapper Pattern در PHP الگویی برای جداسازی Entity و منطق Domain از لایه ذخیره‌سازی اطلاعات است.

در این الگو:

  • Entity مسئول قوانین و وضعیت Domain است.
  • Mapper داده‌ها را بین Entity و دیتابیس منتقل می‌کند.
  • Repository می‌تواند Interface مناسب برای دسترسی به Entityها ارائه دهد.
  • Application Service منطق Use Caseها را اجرا می‌کند.
  • Infrastructure مسئول جزئیات فنی مانند PDO، Query Builder یا ORM است.

Data Mapper برای پروژه‌هایی با Domain پیچیده، دیتابیس Legacy، نیاز به تست مستقل و معماری‌های لایه‌ای مناسب است. اما برای پروژه‌های ساده CRUD، استفاده از Eloquent یا Query Builder ممکن است انتخاب ساده‌تر و منطقی‌تری باشد.

مهم‌ترین نکته این است که Data Mapper را فقط به خاطر نام یا پیچیدگی ظاهری آن انتخاب نکنید. ابتدا نیاز پروژه، اندازه Domain، سطح پیچیدگی قوانین تجاری و نیازهای تست را بررسی کنید.

سوالات متداول

Data Mapper Pattern در PHP چیست؟

Data Mapper الگویی است که انتقال داده بین دیتابیس و Entityهای برنامه را به یک لایه جداگانه می‌سپارد تا Domain از جزئیات ذخیره‌سازی مستقل بماند.

تفاوت Data Mapper و Active Record چیست؟

در Active Record، مدل معمولاً هم نماینده رکورد دیتابیس است و هم عملیات ذخیره و بازیابی را انجام می‌دهد. در Data Mapper، Entity از دیتابیس مستقل است و Mapper مسئول انتقال داده است.

آیا Eloquent از Data Mapper استفاده می‌کند؟

Eloquent در Laravel بیشتر به الگوی Active Record نزدیک است. می‌توان در Laravel معماری Data Mapper ساخت، اما باید Entity، Mapper و لایه‌های دسترسی به داده را جداگانه طراحی کرد.

آیا Data Mapper برای پروژه‌های کوچک مناسب است؟

معمولاً برای پروژه‌های کوچک CRUD ممکن است بیش از نیاز پیچیدگی ایجاد کند. این الگو زمانی ارزش بیشتری دارد که Domain پیچیده یا نیاز به جداسازی جدی از دیتابیس وجود داشته باشد.

آیا Repository و Data Mapper یکسان هستند؟

خیر. Mapper مسئول تبدیل داده بین Entity و Storage است، در حالی که Repository معمولاً یک Interface برای جست‌وجو و مدیریت Entityها ارائه می‌دهد. این دو می‌توانند در کنار هم استفاده شوند.

مزیت اصلی Data Mapper چیست؟

مهم‌ترین مزیت آن جداسازی منطق Domain از دیتابیس است. این جداسازی می‌تواند تست‌پذیری، انعطاف‌پذیری و قابلیت نگهداری پروژه را بهتر کند.

آیا Data Mapper با Laravel سازگار است؟

بله. می‌توان Entityهای مستقل از Laravel ساخت و در لایه Infrastructure از Query Builder، PDO یا حتی Eloquent برای پیاده‌سازی Mapper استفاده کرد.

آیا Data Mapper باعث افزایش سرعت برنامه می‌شود؟

هدف اصلی Data Mapper افزایش سرعت نیست؛ بلکه جداسازی مسئولیت‌ها و استقلال Domain از Persistence است. بسته به پیاده‌سازی، ممکن است لایه‌های اضافی نیز ایجاد شود.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه