منبع اصلی نوشتار زیر در این لینک قرار دارد

PHP 5.4 Trait

یکی از قابلیتهای جدیدی که به PHP 5.4 (که بالاخره چند روز پیش نسخه نهاییش منتشر شد) اضافه شده، Trait ها هستن. خیلی زبانها اجازه ارث بری چندگانه رو میدن ولی PHP از اونها نیست. البته این قابلیت-ارث بری چندگانه-، به نظر شخصی من یکی از اون قابلیتهاییه که توی ایجاد کردن دردسر و ابهام میتونه خیلی نقش داشته باشه.
PHP توی نسخه جدید،‌برای رفع این نقیصه یه موجودیت جدید رو معرفی کرده که البته دقیقا همون ارث بری چندگانه نیست، ولی به اندازه کافی مفید هست..

خیلی وقتها کلاسهایی که مینویسیم، شباهتهایی با همدیگه دارن. مثلا برای سینگلتون من همیشه اینطور عمل میکنم :

class Singleton
{
	private static $instance;

	public static function getInstance(){
		if (!self::$instance)
			self::$instance = new self();
		return self::$instance;
	}

}

حالا اگه من تعداد زیادی کلاس داشته باشم که بخوام همه اونها سینگلتون باشن، بای همه رو از این کلاس به ارث ببرم، که عملا ممکن نیست، مثلا من نمیتون کلاس مربوط به دیتابیس و کلاس مربوط به Translation رو از یه کلاس مشترک شروع کنم.

قبلا interface ها معرفی شده بودن، ولی اونها فقط یه اعلام تابع هستن و بدنه تابع، برای هر کلاس باید دوباره توی خود کلاس نوشته بشه.
توی زبانهایی مثل C++ میشه اینو خیلی ساده حل کرد، هر کلاس میتونه بیشتر از یه پدر داشته باشه،‌و تمام. (مثلا کلاس دیتابیس میتونه همزمان از کلاس دیتابیس پایه ارث ببره و هم از اینکلاس Singleton ) ولی PHP اون روش رو انتخاب نکرد، و من خوشحالم که اینکار رو نکرد، چون قبلا صابون ارث بری چندگانه به تنم خورده بود :))

trait ها، یه موجودیت کامل نیستن. امکان نداره بشه یه شی از یه trait ساخت. اونها کلاس نیستن، بلکه قراره که بخشی از یه کلاس باشن.
مثلا برای مثال سینگلتون :

trait Singleton 
{
	private static $instance;

	public static function getInstance(){
		if (!self::$instance)
			self::$instance = new self();
		return self::$instance;
	}
}


class MySingletonClass 
{
	use Singleton;
}

$singleton = MySingletonClass::getInstance();

– یه نکته خیلی جالب، تو سایت php.net نوشته که

Static variables can be referred to in trait methods, but cannot be defined by the trait.

یعنی من نمیتونم یه متغیر استاتیک رو داخل یک trait تعریف کنم، ولی کد بالا بدون مشکل کار میکنه و این عجیبه! (همین کد بالا اجرا میشه،‌اگه به جای private عمومی هم باشه اجرا میشه و کلا مشکلی نیست.)

این خیلی ساده، باعث میشه که شما از نوشتن کد زاید بی نیاز بشید. مثالهای خیلی بهتر و جامعتری میشه ارایه داد، برای من که در عمل خیلی پیش اومده که یه قسمت از یه کلاس بارها و بارها در کلاسهای دیگه استفاده بشه. توی کامنتهای خود سایت php.net یه مثال جالب بود، و اونهم اینکه trait در حقیقت مثل Copy/Paste عمل میکنه و من تا حدی باهاش موافقم

مثالهای خود php.net هم جالبن :
یه کلاس میتونه از یه کلاس دیگه ارث ببره و هر چند تا trait هم داشته باشه مثلا :


class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();  //Result is Hello World!

از طرفی تا یه حدی مشکل تداخل هم هست. در بعضی موارد، PHP اونها رو رفع میکنه مثلا در این مورد :

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();  //Result is Hello Universe!

متد خود کلاس بعد از use اومده و اونه که اولویت داره.

یه نکته جالب دیگه اینه که میشه هر چند تا trait رو توی یه کلاس استفاده کرد :‌

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo ' World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();

اگه دو trait تو یه کلاس استفاده بشن، و بعد هر دو یه متد رو داشته باشن، با خطای fatal متوقف میشید. برای اینکه بتونید این مشکل رو حل کنید و ابهام به وجود نیاد، PHP کلمه کلیدی جدید معرفی کرده به اسم insteadof (به شباهتش با instanceof توجه کنید، فکر کنم عمدا اینطوری انتخاب شده که شبیه باشن)

علاوه بر او کلیدواژه as هم میشه برای تغییر اسم دادن یک متد از یک trait توی یک کلاس استفاده میشه مثلا :

trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}

البته as یه کاربرد دیگه هم داره و اون تغییر دسترسی یک متده . مثلا اگه یه متد توی trait عمومی باشه (public ) و شما بخواید تو کلاستون اونو محافظت شده داشته باشید :

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

یک trait خودش میتونه از trait های دیگه استفاده کنه :

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();

توی trait ها میتونیم متد غایب داشته باشیم (abstract) ولی وقتی یه trait توی یک کلاس استفاده میشه اون کلاس یا باید خودش abstract باشه و اگه نیست باید حتما اون متد رو پیاده سازی کنه :

trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}

البته trait میتونه property هم تعریف کنه،‌ولی در این صورت کلاسی که ازش استفاده میکنه نمیتونه همون property رو داشته باشه تو ساختارش، چون با هم تداخل پیدا میکنن، و این تداخل، قابل اصلاح نیست (بر خلاف تداخل متدها). البته اگه مقدار اولیه دو خصیصه یکی باشه، خطایی گرفته نمیشه. نکته قابل توجه اینه که این مشکل درباره static property ها نیست، و در اون صورت خطایی اعلام نمیشه، ولی منطقا غلطه و این خطرناکتره به نظر من :


trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // Strict Standards.
    public $different = true; // Fatal error
}



برچسب ها : , , , , , ,