I’ve been messing around with some PHP this morning and came up with this. It’s an approach to a data-storing object which stores raw data and accepts input/output as something more user friendly (e.g. storing a price in pence/cents and outputting it in the decimal pound/dollar format). It doesn’t compare with efficiency of using methods like getPrice and setPrice, but is nicer to the developer and allows a custom object to be created on-the-fly. Its experimental, but worth sharing.
class DataObject{
protected $Data;
protected $Fields;
public function __construct(array $Fields, array $Data = array()){
foreach($Fields as $Name => $Options){
$this->Data[$Name] = isset($Data[$Name]) ? $Data[$Name] : null;
}
$this->Fields = $Fields;
}
public function __get($Name){
if(!isset($this->Fields[$Name])){
throw new Exception("Field does not exist: {$Name}");
}else{
$Field = $this->Fields[$Name];
if(isset($Field['get'])){
$Value = $Field['get']($this->Data[$Name]);
}else{
$Value = $this->Data[$Name];
}
if(isset($Field['type'])){
foreach(explode(' ', $Field['type']) as $T){
switch($T){
case 'date':
$Value = date('d/m/Y', $Value);
break;
}
}
}
return $Value;
}
}
public function __set($Name, $Value){
if(!isset($this->Fields[$Name])){
throw new Exception("Field does not exist: {$Name}");
}else{
$Field = $this->Fields[$Name];
if(isset($Field['set'])){
$Value = $this->Fields[$Name]['set']($Value);
}
if(isset($Field['type'])){
foreach(explode(' ', $Field['type']) as $T){
switch($T){
case 'bool':
$Value = (bool)$Value;
break;
case 'int':
$Value = (int)$Value;
break;
case 'positive':
$Value = abs($Value);
break;
case 'date':
$Value = strtotime($Value);
break;
}
}
}
$this->Data[$Name] = $Value;
}
}
public function outputList(){
echo '<h3>Data:</h3>';
echo '<ul>';
foreach($this->Fields as $FieldName => $V){
printf('<li><strong>%s:</strong> %s</li>', $FieldName, $this->{$FieldName});
}
echo '</ul>';
}
}
Usage:
class Product extends DataObject{
public function __Construct(array $Data = array()){
parent::__construct(
array(
'ID' => array(
'set' => function($Value){
throw new Exception("IDs are read only.");
},
),
'Price' => array(
'get' => function($Value){
return sprintf('%.2f', $Value / 100);
},
'set' => function($Value){
return round($Value * 100);
},
'type' => 'positive int'
),
'Qty' => array(
'type' => 'positive int',
),
'Added' => array(
'type' => 'date'
)
),
$Data
);
}
}
$Phone = new Product(array('ID' => 3241, 'Qty' => 5, 'Added' => time()));
$Phone->outputList();
/* Make some changes */
$Phone->Added = 'Yesterday';
$Phone->Price = 24.99;
$Phone->Qty *= -2; /* Will be made positive */
try{
$Phone->ID = 1;
}catch(Exception $e){
echo $e->getMessage();
}
/* Output */
$Phone->outputList();
/* Feeling generous! */
$Phone->Price *= 0.8; /* offer: 20% off. Without rounding would have value 19.992 before conversion to int */
$Phone->outputList();
This can be used to automatically cast and validate variables when they are set, which can save some hastle with user-input validation. Custom getters and setters can be defined for custom variable types, etc.