<rmcreative>

RSS

Mixin в PHP

27 апреля 2010

В Ruby есть довольно занимательная штука, называемая mixin, позволяющая «подмешивать» в класс методы модуля. Чем-то это напоминает множественное наследование, но более точным будет назвать это делегированием методов.

В PHP достичь того же можно при помощи магического метода __call, а использовав __set, __get, __isset и __unset можно реализовать делегирование свойств.

Реализация довольно проста:

class Mixin {
    private $mixed = array();
 
    public function __get($name){       
        foreach($this->mixed as $object){
            if(property_exists($object,$name))
                return $object->$name;
        }
 
        throw new Exception("Property $name is not defined.");
    }
 
    public function __set($name,$value){
        foreach($this->mixed as $object){
            if(property_exists($object,$name))
                return $object->$name=$value;
        }   
 
        throw new Exception("Property $name is not defined.");
    }
 
    public function __isset($name){
        foreach($this->mixed as $object){
            if(property_exists($object,$name) && isset($this->$name))
                return true;
        }
 
        return false;
    }
 
    public function __unset($name){
        foreach($this->mixed as $object){
            if(property_exists($object,$name))
                $object->$name = null;
        }
    }
 
    public function __call($name,$parameters){
        foreach($this->mixed as $object){
            if(method_exists($object,$name))
                return call_user_func_array(array($object,$name),$parameters);
        }
 
        throw new Exception("Method $name is not defined.");
    }
 
    public function mix($name, $class){
        return $this->mixed[$name]=new $class();
    }   
}

Всё. Можно пользоваться:

class A extends Mixin {
 
}
 
class B {
    public $foo = "barn";
 
    function test(){
        echo "Success!\n";
    }
}
 
$a = new A();
$a->mix('b', 'B');
$a->test();
echo $a->foo;

Кстати, во многих популярных фреймворках это уже реализовано. Точно могу сказать, что есть реализации в Yii, CakePhp, Symfony, Propel и Doctrine. Называется это чаще всего behavior.

Единственный минус, который приходит в голову — отсутствие поддержки в IDE. То есть дополнения таких «подмешанных» методов мы не получим. Хотя, в PhpStorm, возможно, это когда-нибудь будет.

Комментарии RSS

  1. №2496
    Psih
    Psih 27 апр. 2010 г., 19:30:11

    В следующей major версии PHP будут либо Mixins, либо Traits. Сейчас в internals идёт бурная разработка и обсуждение этих вещей.

  2. №2497
    Neyronius
    Neyronius 27 апр. 2010 г., 19:42:43

    А приведите, пожалуйста примеры, когда было бы оправдано применение миксинов. Я так понимаю, можно в какой-то класс подмешать несколько других классов. И после этого подмешанные классы могут обращаться к публичным методам и свойствам друг друга. И где это можно применить?

  3. №2499
    gildenburg
    gildenburg 27 апр. 2010 г., 23:39:52

    ничего нового. Вот если б как-то сделать множественное наследование да так, что бы протектед-методы вызывались из потомка. А то паблик-методы я и сам в состоянии вызывать.

  4. №2500
    Sam
    Sam 28 апр. 2010 г., 0:36:02

    Psih

    Был бы благодарен за ссылку.

    Neyronius

    Например, для модели данных можно сделать такие штуки как Taggable (для тегирования модели), SoftDelete (для мягкого удаления) или Versionable (для сохранения версий модели). При этом их можно будет подключить к любой модели.

    gildenburg

    Тут надо два прокси-метода: один в mixin-е, второй в том, к чему его подмешали. Ну и вызывать соответственно из одного другой. Не очень красиво, но работать будет.

  5. №2501
    biakaveron
    biakaveron 28 апр. 2010 г., 9:01:42

    Не люблю я такие штуки... Поведение становится непредсказуемым, код - менее читаемым.

  6. №2502
    Psih
    Psih 28 апр. 2010 г., 10:46:34

    SAM

    Mixin rfc

    Судя по всему частично функионал mixin попадёт в Traits. Вобщем-то смотрите Traits, это то, что активно обсуждается и даже вроде патчи делаются.

    Traits RFC

  7. №2503
    Makc
    Makc 28 апр. 2010 г., 11:56:17

    Единственный минус, который приходит в голову — отсутствие поддержки в IDE.

    Можно в классе описать @property и @method.

    Тогда и среда прекрасно отрабатывает автодополнение для таких классов (причем с подтягиванием типа переменной).

    Самое интересное, что для ORM систем можно в итоге получать всю иеархию базы (вернее все, куда по внешним ключам можно дотянуться), просто описав поля таким образом:[url=http://docs.google.com/File?id=dchkhbfc_17fk2ft3fm_напрмер[/url].

    Работает как минимум в Eclipse PDT и NetBeans.

  8. №2504
    Makc
    Makc 28 апр. 2010 г., 11:59:31

    Что-то URL криво вставился, прошу прощения.

    Вот нормальный:

    http://docs.google.com/File?id=dchkhbfc_17fk2ft3fm_b

  9. №2505
    Sam
    Sam 28 апр. 2010 г., 13:04:42

    Makc

    Да, но описывать так не очень удобно.

    Psih

    Спасибо, почитаю.

  10. №2506
    gildenburg
    gildenburg 28 апр. 2010 г., 16:32:41

    Sam, а можете накидать пример такой эмуляции можественного наследования с вызовом протекед методов? Поприкольней миксин же будет)

  11. №2507
    Ekstazi
    Ekstazi 28 апр. 2010 г., 16:50:59

    Похоже на поведения в yii

  12. №3128
    MpaK
    MpaK 17 сент. 2010 г., 10:04:33

    Один большой минус этого искусственного паттерна и не совсем подходящего для PHP это что класс B подмешавшись к А не имеет полноценного доступа к свойствам А... Что в Ruby например имеет место быть с легкостью.

  13. №3132
    Sam
    Sam 17 сент. 2010 г., 15:00:44

    В Yii проблема решена ссылкой из B на A:

    $this->owner->myMethod();
  14. №3353
    FractalizeR
    FractalizeR 07 нояб. 2010 г., 23:36:21

    Написал небольшую библиотеку для реализации примесей в PHP с помощью либо включения, либо наследования. В принципе, все довольно неплохо работает

  15. №8498
    m2broth
    m2broth 22 окт. 2013 г., 13:13:25

    call_user_func_array(array($object,$name),$parameters); можно просто вызвать $object->$name($parameters);

  16. №9356
    User
    User 03 нояб. 2014 г., 12:10:16

    Извините, а разве в одиночных кавычках

    throw new Exception('Property $name is not defined.');

    переменная $name конкатенируется ? ( sorry for my English )))

  17. №9358
    FractalizeR
    FractalizeR 03 нояб. 2014 г., 13:20:45

    Нет, конечно :) Это опечатка

  18. №9359
    User
    User 03 нояб. 2014 г., 14:33:10

    Кажется, я понял. Здесь в исключение нужно "бросать" не значение переменной $name, а её Имя, т.е.

    throw new Exception('Property ' . $$name . ' is not defined.');

    Если я не прав, то прошу меня поправить. Спасибо.

  19. №9362
    Sam
    Sam 03 нояб. 2014 г., 22:19:45

    User, я поправил. Просто кавычки были не те.

  20. №9363
    User
    User 04 нояб. 2014 г., 10:03:02

    Извините, но может быть в коде

    function test() { echo "Success!n"; }

    просто пропущен слеш "\" ? И нужно писАть так ?

    function test() { echo "Success!\n"; }

    Мы программо-кодеры, народ педантичный. Извините )))

  21. №9365
    Sam
    Sam 04 нояб. 2014 г., 20:56:21

    Тоже поправил :) Спасибо.

  1. Почта опубликована не будет.

  2. Можно использовать синтаксис Markdown или HTML.

  3. Введите ответ в поле. Щёлкните, чтобы получить другую задачу.