Mixins in PHP

Mixins in PHP

Итак, вдруг нам ни с того, ни с сего, но потребовалось использовать примеси в php (вот такие мы ортодоксы). Динамические языки типа Python или Ruby делают это на уровне самого языка с присущей им легкостью и изящностью. PHP этим не обладает. Однако, нашелся способ решения задачи.

Точнее, делали это и раньше. Гуглом находятся некоторые решения . Даже легендарный whytheluckystiff в каком-то мохнатом году использовал php и пытался добиться от него динамики . Но все эти решения используют eval(). Терпеть этого не могу.

Для того, чтобы отказаться от eval() используем магические методы типа __call() . Два объекта — основной, и примесь. Основной объект хранит ссылки на экземпляры всех примесей. Примеси определяют методы, отсутствующие в основном объекте. В магическом методе __call() основного объекта в цикле по всем примесям определяем наличие в них требуемого метода. Если метод найден — передаем управление ему, если ни в одной из примесей метода нет — что ж,... эксепшен!

Далее. Примесь должна знать об основном объекте, и должна уметь исполнять его методы. То есть, как минимум ссылка на родительский объект должна быть, а родительский объект должен предоставлять доступ к своим свойствам и методам, к которым примесь обращается через те же магические методы.

 

Итак, определим два абстрактных класса — примесь (Mixin) и родительский объект (Caller):

 

abstract class Mixin{
private $clr;

public function __construct($caller){
$this->clr = $caller;
}

public function __get($prop){
return $this->clr->__access_get_prop($prop);
}


public function __set($prop, $value){
return $this->clr->__access_set_prop($prop, $value);
}


public function __call($method, $value){
return $this->clr->__access_call($method, $value);
}
}

abstract class Caller{
protected $mixins = array(); // массив экземпляров примсей

protected function __call($method, $value){
$found = false;
foreach($this->mixins as $cname=>$co){
if(method_exists($co, $method)){
return $co->$method();
$found = true;
}
}
if(!$found){
throw new Exception("call to unfdefined method ".$method);
}
}

public function __access_get_prop($prop){
if(property_exists($this, $prop)){
return $this->$prop;
}
}

public function __access_set_prop($prop, $value){
if(property_exists($this, $prop)){
return $this->$prop = $value;
}
}

public function __access_call($method, $value){
if(method_exists($this, $method)){
return call_user_func_array(array($this, $method), $value);
}
}
}




/*
* использование
*/

// конкретная примесь с присущим ей функционалом
class SayHello extends Mixin{
public function say(){
return "Hello ".$this->name;
}
public function sayLoud(){
return 'Hello '.$this->loud().'!!!';
}
public function addPrefix(){
$this->name = "Mr. ".$this->name;
}
}

// конкретный родительский класс, расширенный примесью
class Person extends Caller {
public $name;

function __construct($name){
$this->name = $name;
// тут создаем экземпляр(ы) примеси(ей)
$this->mixins[] = new SayHello($this);
}

public function loud(){
return '<b>'.$this->name.'</b>';
}

public function saySomething(){
$this->addPrefix();
return '<h2>'.$this->name.'</h2>';
}

}

$p = new Person("Schleicher");

echo $p->say(); // Hello Schleicher
echo $p->sayLoud(); // Hello <b>Schleicher</b>!!!
echo $p->saySomething(); // <h2>Mr. Schleicher</h2>

 

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

 

Достоинства

  1. Имеем рабочую схему Mixins без использования eval()

 

Недостатки:

  1. все равно это дыра в производительности, но ей мы всегда жертвуем в угоду более простой разработки.
  2. Все методы примеси должны быть public, хотя по логике это не всегда так.
  3. каждый родительский класс должен создать экземпляры всех своих примесей. Впрочем, он может использовать общие для всех экземпляры, получая их с помощью Registry
  4. Может быть есть что-то еще, необходимо всестороннее тестирование.


С помощью этой схемы работают комментарии на этом сайте. В данном случае, родителскими классами выступают модули (текстовый — на текстовых страницах разделов Outdoot и Web, модуль событий — в разделе блога). Все они используют в качестве примеси компонент комментариев и приобретают его функциональность. Разумеется, функциональность комментариев можно было вынести в класс-предок и текстов и новостей, некий абстрактный модуль, но это конкретные детали. Целесообразность применения примесей не является темой этой статьи.

Комментарии (9)

mem: 1170 total: 11 module: 5 xsl: 3