decodex | آموزش برنامه نویسی و طراحی سایت
decodex | چطور از DTO در PHP برای نظم دادن به کدها استفاده کنیم؟

چطور از DTO در PHP برای نظم دادن به کدها استفاده کنیم؟

اگر با PHP کار میکنی و کم‌کم پروژه هات بزرگ تر شدن، احتمالاً با آرایه‌های شلوغ، ورودی‌های بی‌ساختار و فانکشن‌هایی که هر لحظه، انتظار یه کلید جدید از ورودی‌ها رو دارن، دست‌وپنجه نرم کردی. Data Transfer Object یا همون DTO دقیقاً برای همین بی نظمی‌ها به وجود اومده: یه مدل ساده و مشخص برای جابه جایی داده بین لایه ها که حالا بیشتر در موردش صحبت میکنم.

تو این مقاله با هم می‌بینیم DTO دقیقاً چیه، چرا باید ازش استفاده کنیم، چطور یه DTO تمیز با PHP 8+ بسازیم، اونو به لایه‌های مختلف اپ وصل کنیم و در نهایت چند نکته حرفه‌ای برای بهتر کردن کار پیشنهاد میدم بهتون. هدف اینه بعد از خوندن این مطلب بتونی با خیال راحت از DTO توی پروژه هات استفاده کنی و نتیجه ش رو توی خوانایی، تست پذیری و کیفیت کدت ببینی.

۱) DTO چیه و کجا به درد می‌خوره؟

DTO یک کلاس ساده ست که فقط داده نگه می‌داره. نه اتصال به دیتابیس داره، نه کوئری میزنه، نه خبری از لاجیک پیچیده ای در میونه. وظیفه ش فقط انتقال داده بین لایه‌هاست؛ مثلاً بین Controller و Service یا بین Service و Repository. به خصوص وقتی ورودی ها از منابع مختلف میان (HTTP Request، Message Queue، CLI و ...)، داشتن ساختار شفاف و مشخص خیلی نجات بخشه.

در اصل DTO همون کاریه که خودمون خیلی وقت ها میکنیم! مثلا میایم میگیم اول داده رو توسط فلان متد مرتب کن، بعد بفرست برای کلاینت!

چرا به‌جای آرایه ساده از DTO استفاده کنیم؟

  • خوانایی و خود مستند سازی: وقتی کلاس و پراپرتی ها اسم دارن، دیگه حدس زدن کلیدهای آرایه لازم نیست.
  • تایپ امن (Type Safety): با Typed Properties در PHP 7.4+ و readonly در PHP 8.1+، خطای تایپ خیلی کمتر می‌شه.
  • جلوگیری از نشت تغییرات: تغییرات ورودی‌های بیرونی باعث نمی‌شن ناخواسته کل سیستم تحت تأثیر قرار بگیره.
  • تست‌پذیری بهتر: تست سرویس‌ها با ورودی‌های مشخص و ثابت راحت تره.
گزینه مزایا معایب
آرایه ساده سریع، بدون تعریف کلاس عدم ایمنی تایپ، خطاهای خاموش، سختی نگهداری
Active Record قابل ذخیره مستقیم، مناسب CRUD ساده وابستگی به دیتابیس، منطق اضافه داخل مدل
DTO ساختار مشخص، تایپ امن، مناسب انتقال بین لایه‌ها نیاز به تبدیل/هیدریشن، کلاس‌های بیشتر
DTO در PHP | decodex.ir

۲) یه DTO تمیز در PHP بسازیم (با مثال)

برای ساخت DTO در PHP 8.1+ می‌تونیم از readonly و Typed Properties استفاده کنیم تا هم تغییر پذیری کم بشه هم تایپ ها تضمین شن. بیاین یه سناریو بسازیم: کاربر جدید با آدرس اختیاری.

<?php
final class AddressDTO implements JsonSerializable
{
    public function __construct(
        public readonly string $city,
        public readonly string $street,
        public readonly ?string $postalCode = null,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            city: (string)($data['city'] ?? ''),
            street: (string)($data['street'] ?? ''),
            postalCode: isset($data['postalCode']) ? (string)$data['postalCode'] : null,
        );
    }

    public function jsonSerialize(): array
    {
        return [
            'city' => $this->city,
            'street' => $this->street,
            'postalCode' => $this->postalCode,
        ];
    }
}

final class CreateUserDTO implements JsonSerializable
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $phone = null,
        public readonly ?AddressDTO $address = null,
    ) {}

    public static function fromArray(array $data): self
    {
        $address = isset($data['address']) && is_array($data['address'])
            ? AddressDTO::fromArray($data['address'])
            : null;

        return new self(
            name: (string)($data['name'] ?? ''),
            email: (string)($data['email'] ?? ''),
            phone: isset($data['phone']) ? (string)$data['phone'] : null,
            address: $address,
        );
    }

    public function jsonSerialize(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
            'address' => $this->address,
        ];
    }
}

نکات مهم توی ساخت DTO

  • final و readonly کمک میکنن DTO تغییر ناپذیر و قابل پیش بینی باشه.
  • متد fromArray برای هیدریشن از ورودی‌ها خیلی کاربردیه.
  • JsonSerializable خروجی JSON رو تمیز و استاندارد می‌ده.

۳) اتصال DTO به لایه‌های اپلیکیشن

حالا فرض کن یه Controller داریم که درخواست کاربر رو می‌گیره و میفرسته برای سرویس ساخت کاربر. به جای پاس دادن آرایه خام، DTO پاس می‌دیم:

<?php
// Controller-like place
$payload = [
    'name' => $_POST['name'] ?? '',
    'email' => $_POST['email'] ?? '',
    'phone' => $_POST['phone'] ?? null,
    'address' => [
        'city' => $_POST['city'] ?? '',
        'street' => $_POST['street'] ?? '',
        'postalCode' => $_POST['postalCode'] ?? null,
    ],
];

$dto = CreateUserDTO::fromArray($payload);
$service->createUser($dto);

سمت سرویس هم فقط با یه آبجکت تمیز سروکار داریم:

<?php
final class UserService
{
    public function __construct(private UserRepository $repo) {}

    public function createUser(CreateUserDTO $dto): int
    {
        // اینجا هیچ Validate سنگینی نمی‌کنیم؛ فقط با DTO کار می‌کنیم
        // بیزینس لاجیک سبک: مثلاً نرمال‌سازی شماره تماس
        $phone = $dto->phone ? preg_replace('/\D+/', '', $dto->phone) : null;

        return $this->repo->insert([
            'name' => $dto->name,
            'email' => strtolower($dto->email),
            'phone' => $phone,
            'city' => $dto->address?->city,
            'street' => $dto->address?->street,
            'postal_code' => $dto->address?->postalCode,
        ]);
    }
}

کجا اعتبارسنجی کنیم؟

  • اعتبارسنجی ساختاری (required بودن فیلدها، فرمت ایمیل) می‌تونه قبل از ساخت DTO انجام بشه.
  • اعتبارسنجی دامنه‌ای (این ایمیل قبلاً ثبت شده؟) باید توی سرویس/دامین باشه.
  • DTO خودش محل اعتبارسنجی پیچیده نیست؛ بهتره تمیز نگهش داریم.
DTO در PHP | decodex.ir

۴) نکات حرفه‌ای برای استفاده از DTO

Immutable بودن و مزایاش

  • با readonly و عدم وجود Setter، مطمئنیم DTO بعد از ساخت هیچ تغییری نمی‌کنه.
  • Debug راحت‌تر: اگر جایی باگ دیدیم، دنبال اونجایی که DTO عوض شده نمی‌گردیم.

Validation کجا بهتره باشه؟

  • در ورودی وب (مثل Laravel FormRequest یا Symfony Validator) قبل از ساخت DTO، ورودی رو چک کن.
  • قوانین دامنه رو داخل سرویس/دامین نگه دار، نه خود DTO.

Hydration/Serialization خودکار

  • اگر پروژه بزرگه، ابزارهایی مثل Symfony Serializer، Laminas Hydrator، CuyZ/Valinor یا EventSauce/ObjectHydrator کمک می‌کنن.
  • هنگام استفاده از پکیج ها حواست به عملکرد و پشتیبانی باشه. بعضی پکیج‌ها ممکنه منسوخ شده باشن؛ قبل از انتخاب، ریپو و Issues رو چک کن.

DTO vs Value Object

  • DTO برای انتقال داده بین لایه‌هاست؛ قوانین رفتاری خاصی نداره.
  • Value Object نماینده یک مفهوم دامنه با قواعد و مقایسه بر اساس مقدار (مثل Email، Money) عه.
  • می‌تونی داخل DTO از Value Objectها استفاده کنی تا کیفیت داده بالا بره.

چند ضدالگو (Anti-pattern)

  • گذاشتن منطق تجاری داخل DTO: کار DTO فقط حمل داده است.
  • وابستگی DTO به ORM: DTO نباید Entity باشه و برعکس.
  • استفاده از Setter های زیاد: با سازنده و تایپ امن، داده‌ها رو تثبیت کن.

تست پذیری

  • با DTOها می‌تونی ورودی‌های سرویس رو در تست‌ها دقیق و پایدار بسازی.
  • برای سناریوهای مختلف، فیکچر یا کارخانه ساخت DTO بنویس.

جمع‌بندی

DTO ها برای نظم دادن به جریان داده توی اپلیکیشن های PHP عالی عمل میکنن. با استفاده از Typed Properties، readonly و الگوی Immutable، هم خطاهای تایپی کم میشن هم کدها قابل فهم تر و تست پذیرتر میشن. از DTO استفاده کن تا Controllerها، Serviceها و Repositoryهات حرف همدیگه رو واضح تر بفهمن و تغییرات کمتر نشت کنه.

اگر تجربه جالبی با DTO داشتی، یا سوال و ابهامی برات مونده، خوشحال می‌شم توی کامنت‌ها برام بنویسی. بگو توی پروژه‌هات کجا بیشترین کمک رو از DTO گرفتی.

دیدگاه شما

ثبت دیدگاه

اگه در مورد این مطلب نظری داری یا در همین موضوع سوالی داری، همینجا مطرح کن تا از دیدگاه ارزشمندت استفاده کنیم و انرژی بگیریم، یا سوالت رو جواب بدیم

در ضمن، شماره موبایلت تو سایت نمایش داده نمیشه و پیش ما به صورت محرمانه میمونه

کد تایید پیامک شده به شماره را وارد نمایید