Стандартный механизм перегрузки свойств через методы __get и __set весьма не удобен для практического использования, однако с помощью него можно создать удобный dsl для работы со свойствами. Сразу же пример использования (тут и далее используется паттерн адаптивной типизации, с которым рекомендуется предваритильно ознакомиться):
Имя поля должно начинаться с подчёркивания, а акцессоры должны иметь соответствующие префиксы. Если вы не указываете специфичный для свойства геттер и/или сеттер, то будут использованы общие get_ и set_. Интерфейс акцессоров прост: они преобразуют переданное им значение. В сеттер передаётся новое значение и результат сохраняется в поле. В геттер на вход поступает сохранённое значение и результат выдаётся во вне. Фактически акцессоры выступают в роли пре- и пост- фильтров.
К свойству можно обращаться не только как к полю, но и как к полиморфной функции. Если ей не передавать аргументов, то она работает как геттер, если передать один параметр — как сеттер поддерживающий «цепочки». Если передать больше параметров — ждите беды ;-) Для перехвата вызовов неизвестных методов, имена которых не совпадают с именами свойств, можно перегрузить метод _call, который по умолчанию просто бросает исключение.
Маленькая плюшка — преобразование объекта в строку по умолчанию делает его дамп с помощью print_r. Вообще, рекомендация по поводу __toString такая, что сей метод должен вызвращать строку наиболее полно отражающую внутреннее состояние объекта.
Ну и на закуску — класс «типизированная переменная»:
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>}