السمات Traits في PHP

من موسوعة حسوب
< PHP
(بالتحويل من PHP/OOP/traits)

اعتمدت اللغة منذ الإصدار 5.4.0 طريقة لإعادة استخدام الشيفرة تدعى السمات Traits.

تعدّ السمات طريقة لإعادة استخدام الشيفرة في اللغات التي لا تدعم الوراثة المتعددة مثل PHP. وتهدف السمات إلى إزالة بعض القيود التي تفرضها الوراثة المفردة وذلك بتمكين المطوّر من استخدام مجموعة من التوابع بحرية في عدد من الأصناف المستقلة عن بعضها في هيكلية أصناف مختلفة. وتحمل كلّ من السمات والأصناف دلالات تهدف إلى التقليل من التعقيد وتجنب المشاكل الشائعة التي ترتبط بالوراثة المتعددة والدوال المساعدة Mixins.

تشبه السمة الصنف، ولكنّها الهدف من استخدامها هو تجميع وظائف مختلفة بطريقة متناسقة. لا يمكن تهيئة السمة بمفردها، وتعدّ إضافة إلى مفهوم الوراثة التقليدي وتتيح التركيب الأفقي للسلوك، أي تطبيق أعضاء الصنف دون الحاجة إلى الوراثة.

المثال 1: مثال على السمات

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

الأسبقية

تنال الأعضاء التي تُضاف من قبل السمة الأسبقية على الأعضاء الموروثة من صنف أساسي وتتجاوزها. يكون ترتيب الأسبقية بالصورة التالية: يتجاوز الأعضاء من الصنف الحالي توابع السمة والتي تتجاوز بدورها التوابع الموروثة.

المثال 2: مثال على ترتيب الأسبقية

يتجاوز التابع المضاف في MyHelloWorld من السمة SayWorld على التابع الموروث من الصنف الأساس. ويحدث الأمر ذاته للتوابع المعرفة في الصنف MyHelloWorld. ويكون ترتيب الأسبقية بأن تتجاوز التوابع الخاصة بالصنف الحالي التوابع الخاصة بالسمة، والتي تتجاوز بدورها التوابع الخاصة بالصنف الأساس.

<?php
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();
?>

تعطي الشيفرة السابقة المخرجات التالية:

Hello World!

المثال 3: مثال آخر على ترتيب الأسبقية

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

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

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

تعطي الشيفرة السابقة النتيجة التالية:

Hello Universe!

السمات المتعددة

يمكن إدراج سمات متعددة في الصنف وذلك بسردها في عبارة use مفصولة عن بعضها بعضًا بفاصلة.

المثال 4: استخدام سمات متعددة

<?php
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();
?>

تعطي الشيفرة السابقة النتائج التالية:

Hello World!

حلّ التضارب

إن أدرجت سمتان تابعًا بنفس الاسم، تطلق اللغة خطأً من نوع fatal، في حال عدم حلّ هذا التضارب بشكل صريح.

ولحلّ مشكلة تضارب الأسماء الحاصلة بين سمتين مستخدمتين في الصنف نفسه يجب استخدام العامل insteadof لتحديد التابع الذي نرغب في استخدامه. ولما كانت وظيفة هذه العامل استبعاد التوابع فقط، فيمكن استخدام العامل as لإضافة اختصار لأحد التوابع. انتبه إلى أنّ معامل as لا يغير اسم التابع ولا يؤثر على أي تابع آخر.

المثال 5: حل التضارب

في هذا المثال يستخدم الصنف Talker السمتين A و B، ولما كان هناك تضارب في التوابع الخاصة بهاتين السمتين، فسيستخدم الصنف التابع smallTalk من السمة B، والتابع bigTalk من السمة A.

يستفيد الصنف Aliased_Talker من العامل as ليكون قادرًا على استخدام التابع bigTalk من السمة B مع الاختصار الإضافي talk.

<?php
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;
    }
}
?>

ملاحظة: قبل الإصدار السابع من اللغة، كان التعريف عن خاصّية في الصنف تحمل اسم خاصّية أخرى في السمة التي يستخدمها الصنف، يؤدي إلى جعل اللغة ترمي خطأً من نوع E_STRICT إن كان تعريف الصنف متوافقًا (له نفس قابلية الرؤية والقيمة الابتدائية).

تغير قابلية رؤية التابع

يمكن استخدام العامل as لتعديل قابلية رؤية التابع في الصنف الذي يستخدم السمة.

المثال 6: تغيير قابلية رؤية التابع

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

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


class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

السمات المكوّنة من سمات أخرى

تستطيع السمات استخدام سمات أخرى كما هو الحال بالضبط مع الأصناف، ويمكن استخدام سمة واحدة أو أكثر عند تعريف السمة ويمكن أن تتكوّن السمة جزئيًا أو كلّيًا من أعضاء معرّفين في تلك السمات.

المثال 7: السمات المكوّنة من سمات أخرى

<?php
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();
?>

تعطي الشيفرة السابقة النتيجة التالية:

Hello World!

الأعضاء المجرّدة في السمات

تدعم السمات استخدام التوابع المجرّدة لتفرض التوابع الواجب وجودها على الصنف الذي يستخدمها.

المثال 8: عرض المتطلبات باستخدام توابع مجرّدة

<?php
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;
    }
}
?>

الأعضاء الساكنون في السمات

يمكن تعريف أعضاء وتوابع ساكنة في السمة.

المثال 9: متغيرات ساكنة

<?php
trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

المثال 10: التوابع الساكنة

<?php
trait StaticExample {
    public static function doSomething() {
        return 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();
?>

الخصائص

يمكن أيضًا تعريف الخصائص في السمات.

المثال 11: تعريف الخصائص

<?php
trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;
?>

في حال عرّفت السمة خاصّية ما فلن يعود الصنف بعدها قادرًا على تعريف خاصية بذات الاسم إلا إذا كانت متوافقة (نفس قابلية الرؤية ونفس القيمة الابتدائية)، ويؤدي وجود خاصية تحمل الاسم ذاته إلى إطلاق خطأ من نوع fatal. قبل الإصدار 7.0.0 من اللغة إن جرى تعريف خاصية في الصنف تمتلك نفس قابلية الرؤية ونفس القيمة الابتدائية التي تمتلكها الخاصية التباعة للسمة فإن اللغة تطلق ملاحظة من نوع E_STRICT.

المثال 12: حلّ التضارب

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

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // Allowed as of PHP 7.0.0; E_STRICT notice formerly
    public $different = true; // Fatal error
}
?>

مصادر