Simple casting and validating

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.