Traits и т.п.

Две недели не писал, а пиздюлей мне никто так и не дал. Ну ладно, вот, допустим, трейты. Вещь вполне себе, как оказывается, терпимая.

Вообще, все нововведения в PHP неизменно радуют. Нужно только отвлечься от того, что все они сделаны через жопу.

Теоретики плачут, что Traits, это никакое тебе не множественное наследование в высоком понимании этого термина. А самый, что ни на есть, банальный автоматизированный копипаст.

Однако, если посмотреть на языки с нормальным множественным наследованием, оно всё равно там в большинстве случаев используется именно для копипаста. Да и вообще, чуть ли не вся история развития программирования, это история автоматизации копипаста.


Вот, допустим, я люблю такое извращение:

namespace Module;
 
/**
 * @property-read \Module\OneService $one
 * @property-read \Module\TwoService $two
 */
class Module extends \BaseModule
{
 
    public function moduleMethod() {
        // ...
    }
 
    public function __get($key) {
        return $this->getService($key);
    }
 
    private function getService($key) {
        if (!\array_key_exists($key, $this->services)) {
            throw new \RuntimeException('Service "'.$key.'" is not found in Module');
        }
        $service = $this->services[$key];
        if (!\is_object($service)) {
            $service = new $service($this);
            $this->services[$key] = $service;
        }
        return $service;
    }    
 
    private $services = array(
        'one' => 'Module\OneService',
        'two' => 'Module\TwoService',
    );
}

То есть, в системе существуют некие модули в виде объектов, в них есть вложенные сервисы, к которым можно обращаться, как к полям модуля-агрегатора:

$system->moduleOne->serviceTwo->subserviceThree->method();

А список вложенных сервисов просто прописывается в отдельной переменной в виде «ключ» => «имя класса». При запросе создаётся объект и в том же массиве кэшируется.

Копипастить подобное надоедает, а базовый класс не сделать, так как все наши модули-агрегаторы могут наследоваться откуда угодно.

А с трейтами всё заебок:

trait SubservicesAggregator
{
 
    public function __get($key) {
        return $this->getService($key);
    }
 
    private function getService($key) {
        if (!\array_key_exists($key, $this->services)) {
            throw new \RuntimeException('Service "'.$key.'" is not found in Module');
        }
        $service = $this->services[$key];
        if (!\is_object($service)) {
            $service = new $service($this);
            $this->services[$key] = $service;
        }
        return $service;
    }  
 
}
namespace Module;
 
/**
 * @property-read \Module\OneService $one
 * @property-read \Module\TwoService $two
 */
class Module extends \BaseModule
{
    use \SubservicesAggregator;
 
    public function moduleMethod() {
        // ...
    }
 
    private $services = array(
        'one' => 'Module\OneService',
        'two' => 'Module\TwoService',
    );
}

Ну, вернее, как всегда, не всё заебок. Например, нельзя сделать так:

/**
 * ...
 * @property-read string $three
 */
class Module extends \BaseModule
{
    use \SubservicesAggregator;
 
    /**
     * __get() не только выдаёт вложенные сервисы, но и определяет какие-то дополнительные поля.
     */
    public function __get($key) {
        if ($key == 'three') {
            return 'three';
        }
        return parent::__get($key); // Не нашли специфического поля - передаём управление СервисАгрегатору
    }    
}

Так не пойдёт, потому что parent, это BaseModule, а не SubserviceAggragator.

Либо так делать:

use \SubservicesAggregator {
    __get as getSubservicesAggregator;
}
 
public function __get($key) {
    if ($key === 'three') {
        return '1';
    }
    return $this->getSubservicesAggregator($key);
}

Либо, в нашем случае, просто:

public function __get($key) {
    if ($key === 'three') {
        return '1';
    }
    return $this->getService($key);
}

Обратите внимание, что здесь мы вызываем getService(), которые в трейте определён, как private. Всё потому, что это ни разу не наследование, а просто копипаст.

В этом примере мы никак не изменили внешних интерфейсов, а только повторно использовали механизмы внутренней реализации. И незачем нам тут множественное наследование.

Как всегда, после PHP мы остаёмся с чувством полного удовлетворения и лёгкого омерзения. До новых встреч!

4 комментария »

  • Спасибо, теперь я хоть узнал что это такое

    adw0rd, 20.02.2013, 10:05

  • Тебе то зачем?

    vasa_c, 20.02.2013, 13:06

  • Да заявляли что в 5.4 это появилось и это одна из крутых фич, может я путаю чего. Было интересно. Действительно полезная штука

    adw0rd, 20.02.2013, 13:24

  • Ну пользоваться я точно не буду, но учту что это есть в PHP

    adw0rd, 20.02.2013, 13:25

Leave a comment