Независимо перегружаемые свойства
PHP*
Стандартный механизм перегрузки свойств через методы __get и __set весьма не удобен для практического использования, однако с помощью него можно создать удобный dsl для работы со свойствами. Сразу же пример использования (тут и далее используется паттерн адаптивной типизации, с которым рекомендуется предваритильно ознакомиться):
Имя поля должно начинаться с подчёркивания, а акцессоры должны иметь соответствующие префиксы. Если вы не указываете специфичный для свойства геттер и/или сеттер, то будут использованы общие get_ и set_. Интерфейс акцессоров прост: они преобразуют переданное им значение. В сеттер передаётся новое значение и результат сохраняется в поле. В геттер на вход поступает сохранённое значение и результат выдаётся во вне. Фактически акцессоры выступают в роли пре- и пост- фильтров.
К свойству можно обращаться не только как к полю, но и как к полиморфной функции. Если ей не передавать аргументов, то она работает как геттер, если передать один параметр — как сеттер поддерживающий «цепочки». Если передать больше параметров — ждите беды ;-) Для перехвата вызовов неизвестных методов, имена которых не совпадают с именами свойств, можно перегрузить метод _call, который по умолчанию просто бросает исключение.
Маленькая плюшка — преобразование объекта в строку по умолчанию делает его дамп с помощью print_r. Вообще, рекомендация по поводу __toString такая, что сей метод должен вызвращать строку наиболее полно отражающую внутреннее состояние объекта.
Ну и на закуску — класс «типизированная переменная»:
class Title extends ProtoObject {
protected $_text= '';
function set_text( $val ){
return $this->aTitleString( $val );
}
function get_text( $val ){
if( empty( $val ) ) return '[untitled]';
return $val;
}
function aTitleString( $val ){
aString( &$val );
if( strlen( $val ) > 255 ) $val= substr( $val, 0, 252 ) . '...';
return $val;
}
}
$title= new Title;
$title->text= 123;
var_dump( $title->text ); // string(3) "123"
var_dump( $title->text( '' )->text() ); // string(10) "[untitled]"
echo $title;
// Title Object
// (
// [_text:protected] =>
// )Архитектура
Имя поля должно начинаться с подчёркивания, а акцессоры должны иметь соответствующие префиксы. Если вы не указываете специфичный для свойства геттер и/или сеттер, то будут использованы общие get_ и set_. Интерфейс акцессоров прост: они преобразуют переданное им значение. В сеттер передаётся новое значение и результат сохраняется в поле. В геттер на вход поступает сохранённое значение и результат выдаётся во вне. Фактически акцессоры выступают в роли пре- и пост- фильтров.
К свойству можно обращаться не только как к полю, но и как к полиморфной функции. Если ей не передавать аргументов, то она работает как геттер, если передать один параметр — как сеттер поддерживающий «цепочки». Если передать больше параметров — ждите беды ;-) Для перехвата вызовов неизвестных методов, имена которых не совпадают с именами свойств, можно перегрузить метод _call, который по умолчанию просто бросает исключение.
Маленькая плюшка — преобразование объекта в строку по умолчанию делает его дамп с помощью print_r. Вообще, рекомендация по поводу __toString такая, что сей метод должен вызвращать строку наиболее полно отражающую внутреннее состояние объекта.
Ещё несколько примеров свойств
protected $_count= 0;
function set_count( $val ){
$this->_message= null;
return anUnsignedInt( $val );
}Хранит некоторое неотрицательное целое значение. Значения вида '-2' и 2.1 будут преобразованы к 2. Если же передать булево значение или, например, строку с буквами, то будет возбуждено исключение. Реализовать тайпкастер anUnsignedInt предлагаю самостоятельно. protected $_message;
function set_message( $val ){
throw new Exception( 'message is autogenerated property' );
}
function get_message( $val ){
if( empty( $val ) ) $val= $this->_message= $this->title . ': ' . $this->count;
return $val;
}Ленивое свойство. Вручную установить значение нельзя, так как оно является функцией от значений других полей. Однако, это свойство кэширует в себе вычисленное значение, поэтому в сеттерах полей, от которых оно зависит, следует прописать сброс кэша. protected $_point;
function set_point( $val ){
return Point::anInstance( $val );
}
function get_point( $val ){
return clone $val;
}Сохраняет в себе объект с некоторым интерфейсом. Если передан не объект, то инстанцирует Point с передачей ему соответствующих параметров. Если параметры не верны — исключение. При чтении свойства — возвращается лишь клон, а сохраённый объект остаётся в целости и приватности. Реализацию тайпкастера оставим в качестве домашнего задания ;-)Ну и на закуску — класс «типизированная переменная»:
class Vary extends ProtoObject {
protected $_type;
function set_type( $type ){
aString( &$type );
if( !function_exists( $type ) ) throw new Exception( 'unknown type' );
if( $this->type ):
$this->_type= $type;
$this->val= $this->val;
endif;
return $type;
}
protected $_val;
function set_val( $val ){
return $val= call_user_func( $this->type, $val );
}
function __construct( $type ){
$this->type= $type;
}
function __toString( ){
return aString( $this->val );
}
}
$count= new Vary( aString );
$count->val= '5cm per second';
echo $count->type; // aString
var_dump( $count->val ); // string(14) "5cm per second"
$count->type= aSoftNumber;
var_dump( $count->val ); // int(5)
echo $count; // 5
echo $count->val( '' ); // 0
var_dump( $count );
// object(Vary)#1 (2) {
// ["_type:protected"]=>
// string(11) "aSoftNumber"
// ["_val:protected"]=>
// int(0)
// }Собственно виновник торжества
class ProtoObject {
static $version= 8;
static $description= 'common object extension';
static $license= 'public domain';
function __toString( ){
return print_r( $this, true );
}
function __set( $name, $value= null ){
$this->_aPropertyName( &$name );
$method= 'set' . $name;
if( !method_exists( $this, $method ) ) $method= 'set_';
$value= $this->{ $method }( $value );
$this->{ $name }= $value;
return $this;
}
function set_( $val ){
return $val;
}
function __get( $name ){
$this->_aPropertyName( &$name );
$method= 'get' . $name;
if( !method_exists( $this, $method ) ) $method= 'get_';
$value= $this->{ $name };
$value= $this->{ $method }( $value );
return $value;
}
function get_( $val ){
return $val;
}
function __call( $name, $args ){
try {
$this->_aPropertyName( &$name );
} catch( Exception $e ){
return $this->_call( $name, $args );
}
switch( count( $args ) ){
case 0: return $this->__get( $name );
case 1: return $this->__set( $name, $args[0] );
default: throw new Exception( 'wrong parameters count' );
}
}
function _call( $name, $args ){
throw new Exception( 'method not found' );
}
function _aPropertyName( $val ){
if( $val[0] !== '_' ) $val= '_' . $val;
if( !property_exists( $this, $val ) ) throw new Exception( 'property not found' );
return $val;
}
}

комментарии (16)