Trait در PHP یکی از قابلیتهای مهم برنامهنویسی شیگرا است که به شما اجازه میدهد یک مجموعه از Methodها و ویژگیهای قابل استفاده مجدد را بین چند Class به اشتراک بگذارید.
فرض کنید در یک پروژه چند Class مختلف داریم که همگی به قابلیت ثبت Log نیاز دارند:
UserService
OrderService
PaymentService
ProductService
قرار نیست این Classها الزاماً از یکدیگر ارثبری کنند؛ اما ممکن است همه آنها به یک رفتار مشترک مثل:
log()
نیاز داشته باشند.
در چنین شرایطی میتوانیم این رفتار مشترک را در یک Trait قرار دهیم و سپس Trait را در Classهای مختلف استفاده کنیم.
مثلاً:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
و سپس:
class OrderService
{
use HasLogging;
}
به این ترتیب OrderService به Method موجود در Trait دسترسی خواهد داشت.
در این مقاله یاد میگیریم:
- Trait در PHP چیست؟
- چرا Trait ایجاد شده است؟
- چگونه یک Trait بسازیم؟
useدر Trait چه کاربردی دارد؟- چگونه یک Trait را در چند Class استفاده کنیم؟
- آیا Trait میتواند Property داشته باشد؟
- آیا Trait میتواند Constructor داشته باشد؟
- چگونه Methodهای Trait را تغییر دهیم؟
- تعارض بین Traitها را چگونه حل کنیم؟
insteadofوasدر Trait چیست؟- تفاوت Trait با Inheritance چیست؟
- تفاوت Trait و Interface چیست؟
- Trait در Laravel چه کاربردی دارد؟
- چه زمانی از Trait استفاده کنیم و چه زمانی از آن استفاده نکنیم؟
# Trait در PHP چیست؟
Trait یک مکانیزم برای استفاده مجدد از کد بین چند Class است.
برای تعریف Trait از کلمه کلیدی:
trait
استفاده میکنیم.
مثلاً:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
اکنون میتوانیم این Trait را داخل یک Class استفاده کنیم:
class UserService
{
use HasLogging;
}
کلمه:
use
در اینجا به معنای استفاده کردن از Trait است.
حالا:
$service = new UserService();
$service->log('User created');
Method مربوط به Trait در اختیار UserService قرار دارد.
# چرا Trait در PHP ایجاد شده است؟
برای درک کاربرد Trait، ابتدا باید مشکل اصلی را ببینیم.
فرض کنید سه Class داریم:
UserService
OrderService
PaymentService
هر سه Class به یک Method مشترک نیاز دارند:
log()
یکی از راهها استفاده از Inheritance است.
اما ممکن است رابطه منطقی بین این Classها وجود نداشته باشد.
مثلاً:
UserService
OrderService
PaymentService
همه Service هستند، اما الزاماً منطقی نیست که یکی Parent دیگری باشد.
در چنین شرایطی Trait میتواند رفتار مشترک را جدا کند:
HasLogging
↓
┌──────────────┐
↓ ↓
UserService OrderService
↓
PaymentService
البته از نظر طراحی، هر Class فقط زمانی باید Trait را استفاده کند که واقعاً آن رفتار را نیاز داشته باشد.
# ساخت اولین Trait در PHP
یک فایل به نام:
HasLogging.php
ایجاد کنید.
محتوای فایل:
<?php
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
سپس در Class:
class UserService
{
use HasLogging;
}
اکنون:
$service = new UserService();
$service->log('User created.');
خروجی:
User created.
# استفاده از یک Trait در چند Class
یکی از مهمترین مزایای Trait این است که میتوان یک Trait را در چند Class استفاده کرد.
مثلاً:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
Class اول:
class UserService
{
use HasLogging;
}
Class دوم:
class OrderService
{
use HasLogging;
}
Class سوم:
class PaymentService
{
use HasLogging;
}
هر سه Class به Method log() دسترسی دارند.
# Trait میتواند چند Method داشته باشد
Trait محدود به یک Method نیست.
مثلاً:
trait HasLogging
{
public function info(string $message): void
{
echo "INFO: " . $message;
}
public function error(string $message): void
{
echo "ERROR: " . $message;
}
public function warning(string $message): void
{
echo "WARNING: " . $message;
}
}
اکنون:
class OrderService
{
use HasLogging;
}
میتواند از هر سه Method استفاده کند:
$order = new OrderService();
$order->info('Order created');
$order->warning('Stock is low');
$order->error('Payment failed');
# Trait میتواند Property داشته باشد؟
بله.
Trait میتواند Property نیز تعریف کند.
مثلاً:
trait HasStatus
{
protected string $status = 'pending';
public function getStatus(): string
{
return $this->status;
}
public function setStatus(string $status): void
{
$this->status = $status;
}
}
حالا:
class Order
{
use HasStatus;
}
میتوانیم بنویسیم:
$order = new Order();
$order->setStatus('paid');
echo $order->getStatus();
# Trait و Propertyهای مدرن PHP
در نسخههای جدید PHP میتوان Traitها را با Type Declaration و ویژگیهای مدرن زبان ترکیب کرد.
مثلاً:
trait HasUuid
{
protected string $uuid;
public function getUuid(): string
{
return $this->uuid;
}
}
اما هنگام طراحی Trait باید توجه کنیم که Trait به چه Propertyهایی از Class مصرفکننده وابسته است.
اگر Trait انتظار داشته باشد Class استفادهکننده Property خاصی داشته باشد، بهتر است این وابستگی به شکل واضح و قابل فهم طراحی شود.
# Trait میتواند Constructor داشته باشد؟
بله، Trait میتواند Constructor تعریف کند.
مثلاً:
trait HasConfiguration
{
protected array $config = [];
public function __construct(array $config = [])
{
$this->config = $config;
}
}
اما در پروژههای واقعی باید در مورد Constructor Traitها با دقت عمل کرد، چون Class مصرفکننده نیز ممکن است Constructor خودش را داشته باشد.
اگر چند بخش مختلف بخواهند مسئول Constructor باشند، طراحی میتواند پیچیده شود.
در بسیاری از موارد بهتر است منطق Initialization را به Methodهای مشخص یا Dependency Injection منتقل کنیم.
# Trait و Methodهای Abstract
Trait میتواند Methodهای abstract نیز تعریف کند.
این قابلیت زمانی کاربرد دارد که Trait انتظار دارد Class استفادهکننده یک رفتار مشخص را ارائه کند.
مثلاً:
trait HasLogger
{
abstract protected function getLoggerName(): string;
public function log(string $message): void
{
echo $this->getLoggerName() . ': ' . $message;
}
}
اکنون Class استفادهکننده باید Method مورد نیاز Trait را پیادهسازی کند:
class UserService
{
use HasLogger;
protected function getLoggerName(): string
{
return 'UserService';
}
}
در این حالت Trait یک قرارداد داخلی برای Class مصرفکننده ایجاد کرده است.
البته اگر هدف شما تعریف یک Contract عمومی بین چند Implementation است، معمولاً Interface انتخاب مناسبتری است.
# استفاده از چند Trait در یک Class
یک Class میتواند چند Trait داشته باشد.
مثلاً:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
و:
trait HasCaching
{
public function cache(string $key): void
{
echo "Caching: " . $key;
}
}
حالا:
class ProductService
{
use HasLogging, HasCaching;
}
اکنون:
$product = new ProductService();
$product->log('Product loaded');
$product->cache('products');
هر دو قابلیت در اختیار Class هستند.
# تعارض بین Traitها در PHP
یکی از نکات مهم هنگام استفاده از چند Trait، امکان وجود Methodهای همنام است.
فرض کنید:
trait LoggerA
{
public function log(): void
{
echo "Logger A";
}
}
و:
trait LoggerB
{
public function log(): void
{
echo "Logger B";
}
}
حالا:
class Service
{
use LoggerA, LoggerB;
}
PHP نمیداند کدام log() را باید استفاده کند.
در چنین شرایطی باید تعارض را مشخص کنیم.
# حل تعارض Trait با insteadof
برای انتخاب یکی از Methodها از:
insteadof
استفاده میکنیم.
مثلاً:
class Service
{
use LoggerA, LoggerB {
LoggerA::log insteadof LoggerB;
}
}
اکنون log() مربوط به LoggerA استفاده خواهد شد.
# استفاده از Method Trait با نام دیگر؛ as
میتوانیم Method مربوط به Trait را با یک نام دیگر نیز در دسترس قرار دهیم.
مثلاً:
class Service
{
use LoggerA, LoggerB {
LoggerA::log insteadof LoggerB;
LoggerB::log as logFromB;
}
}
حالا:
$service->log();
از LoggerA استفاده میکند.
و:
$service->logFromB();
Method مربوط به LoggerB را اجرا میکند.
# تغییر Visibility متد Trait
با as میتوان Visibility یک Method را نیز تغییر داد.
مثلاً:
trait HasLogging
{
public function log(): void
{
echo "Logging";
}
}
میتوانیم در Class بنویسیم:
class Service
{
use HasLogging {
log as protected;
}
}
در این حالت Method log() در این Class با Visibility protected در دسترس خواهد بود.
# Trait و Inheritance چه تفاوتی دارند؟
Trait و Inheritance هر دو میتوانند به استفاده مجدد از کد کمک کنند، اما مفهوم آنها متفاوت است.
Inheritance
در Inheritance یک رابطه Parent/Child داریم:
Animal
↓
Dog
مثلاً:
class Dog extends Animal
{
}
یعنی Dog یک نوع Animal است.
Trait
در Trait یک رفتار را بین Classها به اشتراک میگذاریم:
HasLogging
↓
UserService
OrderService
PaymentService
مثلاً:
class OrderService
{
use HasLogging;
}
در اینجا نمیگوییم OrderService نوعی HasLogging است.
میگوییم OrderService قابلیت Logging را استفاده میکند.
# Trait و Interface چه تفاوتی دارند؟
Interface برای تعریف Contract است.
مثلاً:
interface PaymentGateway
{
public function pay(float $amount): bool;
}
این Interface میگوید Class باید pay() را ارائه دهد.
اما Trait برای اشتراکگذاری Implementation یا رفتار قابل استفاده مجدد استفاده میشود:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
بنابراین:
Interface → قرارداد
Trait → اشتراکگذاری رفتار
Inheritance → رابطه والد و فرزند
این سه مفهوم را نباید با یکدیگر اشتباه گرفت.
# آیا Trait جایگزین Inheritance است؟
خیر.
Trait جایگزین مستقیم Inheritance نیست.
هرکدام مسئله متفاوتی را حل میکنند.
اگر رابطه واقعی Parent/Child دارید، Inheritance میتواند مناسب باشد.
اگر میخواهید یک قابلیت مشخص را بین چند Class مستقل به اشتراک بگذارید، Trait میتواند گزینه خوبی باشد.
اگر میخواهید یک Contract مشترک ایجاد کنید، Interface انتخاب مناسبی است.
# Trait در Laravel
اگر با Laravel کار میکنید، احتمالاً با Traitهای زیادی مواجه خواهید شد.
Laravel و پکیجهای PHP از Traitها برای ارائه قابلیتهای قابل استفاده مجدد در Classهای مختلف استفاده میکنند.
یکی از نمونههای آشنا در Laravel، استفاده از Traitهایی مانند:
use HasFactory;
در Modelها است.
مثلاً:
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
}
در اینجا HasFactory یک Trait است که قابلیت مربوط به Factory را به Model اضافه میکند.
# Traitهای سفارشی در Laravel
شما نیز میتوانید در پروژه Laravel Trait اختصاصی ایجاد کنید.
مثلاً ساختار:
app/
├── Traits/
│ └── HasActivityLog.php
فایل:
<?php
namespace App\Traits;
trait HasActivityLog
{
public function activity(string $message): void
{
// Activity log logic
}
}
سپس:
use App\Traits\HasActivityLog;
class OrderService
{
use HasActivityLog;
}
اکنون قابلیت مورد نظر در اختیار OrderService قرار میگیرد.
# مثال واقعی؛ Trait برای ایجاد UUID
فرض کنید چند Model در پروژه شما به UUID نیاز دارند.
به جای تکرار یک سری Method در همه Modelها، میتوانیم Trait بسازیم:
trait HasUuid
{
public function generateUuid(): string
{
return (string) \Illuminate\Support\Str::uuid();
}
}
حالا:
class Order
{
use HasUuid;
}
و:
class Invoice
{
use HasUuid;
}
هر دو Class قابلیت مورد نظر را دریافت میکنند.
البته در پروژه Laravel باید برای مواردی مثل UUID و Model Events از امکانات استاندارد Framework نیز آگاه باشید و صرفاً برای هر رفتار کوچکی Trait جدید ایجاد نکنید.
# مثال واقعی؛ Trait برای فرمت کردن داده
فرض کنید چند Service نیاز به یک قابلیت مشترک دارند:
trait FormatsData
{
protected function formatPrice(float $price): string
{
return number_format($price);
}
}
حالا:
class ProductService
{
use FormatsData;
}
و:
class InvoiceService
{
use FormatsData;
}
هر دو Service میتوانند از:
$this->formatPrice(1250000);
استفاده کنند.
# Trait و Namespace
در پروژههای حرفهای PHP، Traitها نیز مانند Classها باید Namespace مناسبی داشته باشند.
مثلاً:
namespace App\Traits;
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
سپس در Class:
namespace App\Services;
use App\Traits\HasLogging;
class OrderService
{
use HasLogging;
}
این ساختار در پروژههای بزرگ باعث میشود سازماندهی کد بهتر باشد.
# نامگذاری Traitها در PHP
نام Trait باید نشاندهنده قابلیتی باشد که ارائه میکند.
نامهای مناسب:
HasLogging
HasUuid
HasRoles
FormatsData
InteractsWithApi
TracksActivity
نامهای نامناسب:
Helper
Common
Functions
MyTrait
TestTrait
البته انتخاب نام باید با معماری پروژه هماهنگ باشد.
# چه زمانی از Trait استفاده کنیم؟
Trait زمانی انتخاب خوبی است که:
چند Class مستقل رفتار مشترکی دارند
مثلاً:
UserService
OrderService
PaymentService
و هر سه به قابلیت Logging نیاز دارند.
رفتار کاملاً مشخص و قابل تفکیک است
مثلاً:
HasUuid
HasLogging
HasCache
نمیخواهید صرفاً برای Reuse کردن کد، یک Hierarchy ایجاد کنید
اگر رابطه Parent/Child ندارید، ایجاد Inheritance فقط برای اشتراک کد میتواند طراحی نامناسبی باشد.
# چه زمانی از Trait استفاده نکنیم؟
Trait ابزار بسیار قدرتمندی است، اما استفاده بیش از حد از آن میتواند معماری پروژه را پیچیده کند.
برای هر چند خط کد یک Trait نسازید
مثلاً ایجاد Trait صرفاً برای یک Method بسیار ساده ممکن است ارزش زیادی نداشته باشد.
Trait را به یک Class بسیار خاص وابسته نکنید
اگر Trait فقط با یک Class مشخص کار میکند، شاید بهتر باشد رفتار مربوطه در همان Class یا یک Service مستقل قرار بگیرد.
Traitهای بسیار بزرگ نسازید
Traitی که دهها Method نامرتبط دارد، احتمالاً مسئولیتهای زیادی را با هم ترکیب کرده است.
# Trait و Composition
در معماری مدرن نرمافزار، یکی از نکات مهم این است که Trait تنها راه استفاده مجدد از کد نیست.
گاهی Composition انتخاب بهتری است.
مثلاً به جای:
class OrderService
{
use PaymentHelper;
}
ممکن است طراحی زیر بهتر باشد:
class OrderService
{
public function __construct(
private PaymentService $paymentService
) {
}
}
در این حالت OrderService یک PaymentService دارد و وابستگی آن به شکل واضح مشخص شده است.
این موضوع مخصوصاً در پروژههای بزرگ Laravel اهمیت پیدا میکند.
# مزایا و معایب Trait در PHP
مزایای Trait
Trait میتواند باعث:
- کاهش تکرار کد
- استفاده مجدد از رفتار
- جلوگیری از Inheritanceهای غیرضروری
- سازماندهی بهتر قابلیتهای مشترک
- سادهتر شدن بعضی ساختارهای OOP
شود.
معایب Trait
استفاده بیش از حد از Trait میتواند:
- وابستگیهای پنهان ایجاد کند.
- پیدا کردن منبع واقعی Methodها را دشوار کند.
- Traitهای بزرگ و پیچیده ایجاد کند.
- تست و نگهداری کد را دشوارتر کند.
- مسئولیتهای مختلف را بین چند Class پخش کند.
بنابراین Trait باید به عنوان یک ابزار طراحی استفاده شود، نه راهحلی برای تمام مشکلات Reuse کردن کد.
# اشتباهات رایج در استفاده از Trait
استفاده از Trait به عنوان پوشه Helper
گاهی توسعهدهنده تمام Functionهای عمومی پروژه را داخل Trait قرار میدهد.
این کار معمولاً باعث ایجاد ساختاری نامنظم میشود.
Traitهای بسیار بزرگ
اگر یک Trait تعداد زیادی رفتار غیرمرتبط داشته باشد، بهتر است آن را به چند قابلیت مستقل تقسیم کنید.
استفاده از Trait برای حل هر نوع وابستگی
گاهی Dependency Injection یا Composition انتخاب بهتری است.
نامگذاری نامشخص
Trait باید مشخص کند چه قابلیتی را ارائه میکند.
# بهترین روش استفاده از Trait در PHP
برای استفاده حرفهای از Traitها:
- Trait را کوچک و متمرکز نگه دارید.
- هر Trait را حول یک قابلیت مشخص طراحی کنید.
- وابستگیهای Trait را واضح نگه دارید.
- از Trait برای ایجاد رابطه Parent/Child استفاده نکنید.
- برای تعریف Contract از Interface استفاده کنید.
- برای Dependencyهای واقعی از Dependency Injection و Composition استفاده کنید.
- در پروژههای Laravel، قبل از ایجاد Trait جدید بررسی کنید آیا Framework یا پکیج مورد استفاده قابلیت مشابهی ارائه میدهد یا خیر.
# جمعبندی Trait در PHP
Trait در PHP مکانیزمی برای اشتراکگذاری رفتار و کد قابل استفاده مجدد بین چند Class است.
برای ایجاد Trait از:
trait
و برای استفاده از آن در Class از:
use
استفاده میکنیم.
مثلاً:
trait HasLogging
{
public function log(string $message): void
{
echo $message;
}
}
و:
class OrderService
{
use HasLogging;
}
Traitها میتوانند Method، Property و حتی Methodهای abstract داشته باشند.
همچنین PHP امکان استفاده از چند Trait و حل تعارض Methodها با ابزارهایی مانند:
insteadof
و:
as
را فراهم میکند.
مهمترین نکته این است که Trait، Interface و Inheritance سه مفهوم متفاوت هستند:
Inheritance
→ رابطه Parent و Child
Interface
→ تعریف Contract
Trait
→ اشتراکگذاری رفتار
در پروژههای واقعی PHP و Laravel، انتخاب بین این ابزارها باید بر اساس معماری و مسئولیتهای Classها انجام شود.
در مقاله بعدی این مجموعه، سراغ Abstract Class در PHP میرویم و بررسی میکنیم Abstract Class چیست، چه تفاوتی با Interface دارد و چگونه میتوان از آن برای ساخت Classهای پایه در پروژههای واقعی استفاده کرد.
# سوالات متداول Trait در PHP
Trait در PHP چیست؟
Trait قابلیتی در PHP است که امکان اشتراکگذاری Methodها و ویژگیهای قابل استفاده مجدد را بین چند Class فراهم میکند.
چگونه در PHP یک Trait بسازیم؟
با استفاده از کلمه کلیدی trait:
trait HasLogging
{
public function log(): void
{
echo "Logging";
}
}
چگونه از Trait در یک Class استفاده کنیم؟
با کلمه کلیدی use:
class UserService
{
use HasLogging;
}
آیا یک Class میتواند چند Trait داشته باشد؟
بله:
class Service
{
use HasLogging, HasCaching;
}
آیا Trait میتواند Property داشته باشد؟
بله. Trait میتواند Property و Method داشته باشد.
insteadof در Trait چیست؟
insteadof برای حل تعارض زمانی استفاده میشود که چند Trait یک Method با نام یکسان داشته باشند.
as در Trait چیست؟
as میتواند Method یک Trait را با نام دیگری در دسترس قرار دهد یا Visibility آن را تغییر دهد.
تفاوت Trait و Interface چیست؟
Interface برای تعریف Contract استفاده میشود، در حالی که Trait بیشتر برای اشتراکگذاری Implementation و رفتار قابل استفاده مجدد بین Classها کاربرد دارد.
تفاوت Trait و Inheritance چیست؟
Inheritance یک رابطه Parent/Child ایجاد میکند، اما Trait یک قابلیت مشترک را در اختیار Class قرار میدهد و رابطه Parent/Child ایجاد نمیکند.
آیا Trait در Laravel استفاده میشود؟
بله. Laravel و اکوسیستم PHP از Traitهای مختلف برای ارائه قابلیتهای قابل استفاده مجدد استفاده میکنند و شما نیز میتوانید Traitهای اختصاصی برای پروژه Laravel خود ایجاد کنید.
# مقالات مرتبط
برای لینکسازی داخلی این مقاله، لینکهای زیر را در قسمتهای مرتبط قرار بده:
- OOP در PHP
- Class و Object در PHP
- Inheritance در PHP
- Interface در PHP
- Abstract Class در PHP
- Dependency Injection در PHP
- SOLID در PHP
- Design Pattern در PHP
- Namespace در PHP
- Autoload در PHP
- Composer چیست؟
در بخش ابتدایی یا جمعبندی مقاله نیز یک لینک به صفحه Pillar اصلی قرار بده:
آموزش برنامه نویسی وب از صفر تا پیشرفته؛ PHP، Laravel، MySQL و توسعه وب





