Pull to refresh

Независимо перегружаемые свойства

Reading time7 min
Views693
Стандартный механизм перегрузки свойств через методы __get и __set весьма не удобен для практического использования, однако с помощью него можно создать удобный dsl для работы со свойствами. Сразу же пример использования (тут и далее используется паттерн адаптивной типизации, с которым рекомендуется предваритильно ознакомиться):

class Title extends ProtoObject {<br>    protected $_text= '';<br>    function set_text( $val ){<br>        return $this->aTitleString( $val );<br>    }<br>    function get_text( $val ){<br>        if( empty( $val ) ) return '[untitled]';<br>        return $val;<br>    }<br><br>    function aTitleString( $val ){<br>        aString( &$val );<br>        if( strlen( $val ) > 255 ) $val= substr( $val, 0, 252 ) . '...';<br>        return $val;<br>    }<br>}<br><br>$title= new Title;<br>$title->text= 123;<br>var_dump( $title->text ); // string(3) "123"<br>var_dump( $title->text( '' )->text() ); // string(10) "[untitled]"<br>echo $title;<br>// Title Object<br>// (<br>//     [_text:protected] =><br>// )


Архитектура


Имя поля должно начинаться с подчёркивания, а акцессоры должны иметь соответствующие префиксы. Если вы не указываете специфичный для свойства геттер и/или сеттер, то будут использованы общие get_ и set_. Интерфейс акцессоров прост: они преобразуют переданное им значение. В сеттер передаётся новое значение и результат сохраняется в поле. В геттер на вход поступает сохранённое значение и результат выдаётся во вне. Фактически акцессоры выступают в роли пре- и пост- фильтров.

К свойству можно обращаться не только как к полю, но и как к полиморфной функции. Если ей не передавать аргументов, то она работает как геттер, если передать один параметр — как сеттер поддерживающий «цепочки». Если передать больше параметров — ждите беды ;-) Для перехвата вызовов неизвестных методов, имена которых не совпадают с именами свойств, можно перегрузить метод _call, который по умолчанию просто бросает исключение.

Маленькая плюшка — преобразование объекта в строку по умолчанию делает его дамп с помощью print_r. Вообще, рекомендация по поводу __toString такая, что сей метод должен вызвращать строку наиболее полно отражающую внутреннее состояние объекта.

Ещё несколько примеров свойств


    protected $_count= 0;<br>    function set_count( $val ){<br>        $this->_message= null;<br>        return anUnsignedInt( $val );<br>    }
Хранит некоторое неотрицательное целое значение. Значения вида '-2' и 2.1 будут преобразованы к 2. Если же передать булево значение или, например, строку с буквами, то будет возбуждено исключение. Реализовать тайпкастер anUnsignedInt предлагаю самостоятельно.

    protected $_message;<br>    function set_message( $val ){<br>        throw new Exception( 'message is autogenerated property' );<br>    }<br>    function get_message( $val ){<br>        if( empty( $val ) ) $val= $this->_message= $this->title . ': ' . $this->count;<br>        return $val;<br>    }
Ленивое свойство. Вручную установить значение нельзя, так как оно является функцией от значений других полей. Однако, это свойство кэширует в себе вычисленное значение, поэтому в сеттерах полей, от которых оно зависит, следует прописать сброс кэша.

    protected $_point;<br>    function set_point( $val ){<br>        return Point::anInstance( $val );<br>    }<br>    function get_point( $val ){<br>        return clone $val;<br>    }
Сохраняет в себе объект с некоторым интерфейсом. Если передан не объект, то инстанцирует Point с передачей ему соответствующих параметров. Если параметры не верны — исключение. При чтении свойства — возвращается лишь клон, а сохраённый объект остаётся в целости и приватности. Реализацию тайпкастера оставим в качестве домашнего задания ;-)

Ну и на закуску — класс «типизированная переменная»:
class Vary extends ProtoObject {<br><br>    protected $_type;<br>    function set_type( $type ){<br>        aString( &$type );<br>        if( !function_exists( $type ) ) throw new Exception( 'unknown type' );<br>        if( $this->type ):<br>            $this->_type= $type;<br>            $this->val= $this->val;<br>        endif;<br>        return $type;<br>    }<br><br>    protected $_val;<br>    function set_val( $val ){<br>        return $val= call_user_func( $this->type, $val );<br>    }<br><br>    function __construct( $type ){<br>        $this->type= $type;<br>    }<br><br>    function __toString( ){<br>        return aString( $this->val );<br>    }<br>}<br><br>$count= new Vary( aString );<br>$count->val= '5cm per second';<br>echo $count->type; // aString<br>var_dump( $count->val ); // string(14) "5cm per second"<br>$count->type= aSoftNumber;<br>var_dump( $count->val ); // int(5)<br>echo $count; // 5<br>echo $count->val( '' ); // 0<br>var_dump( $count );<br>// object(Vary)#1 (2) {<br>//  ["_type:protected"]=><br>//  string(11) "aSoftNumber"<br>//  ["_val:protected"]=><br>//  int(0)<br>// }


Собственно виновник торжества


class ProtoObject {<br><br>static $version= 8;<br>static $description= 'common object extension';<br>static $license= 'public domain';<br><br>function __toString( ){<br>    return print_r( $this, true );<br>}<br><br>function __set( $name, $value= null ){<br>    $this->_aPropertyName( &$name );<br>    $method= 'set' . $name;<br>    if( !method_exists( $this, $method ) ) $method= 'set_';<br>    $value= $this->{ $method }( $value );<br>    $this->{ $name }= $value;<br>    return $this;<br>}<br>function set_( $val ){<br>    return $val;<br>}<br><br>function __get( $name ){<br>    $this->_aPropertyName( &$name );<br>    $method= 'get' . $name;<br>    if( !method_exists( $this, $method ) ) $method= 'get_';<br>    $value= $this->{ $name };<br>    $value= $this->{ $method }( $value );<br>    return $value;<br>}<br>function get_( $val ){<br>    return $val;<br>}<br><br>function __call( $name, $args ){<br>    try {<br>        $this->_aPropertyName( &$name );<br>    } catch( Exception $e ){<br>        return $this->_call( $name, $args );<br>    }<br>    switch( count( $args ) ){<br>        case 0: return $this->__get( $name );<br>        case 1: return $this->__set( $name, $args[0] );<br>        default: throw new Exception( 'wrong parameters count' );<br>    }<br>}<br>function _call( $name, $args ){<br>    throw new Exception( 'method not found' );<br>}<br><br>function _aPropertyName( $val ){<br>    if( $val[0] !== '_' ) $val= '_' . $val;<br>    if( !property_exists( $this, $val ) ) throw new Exception( 'property not found' );<br>    return $val;<br>}<br><br>}
Tags:
Hubs:
-14
Comments16

Articles