I’ve been following Vic Cherubini‘s series on Zend Developer Zone about his ORM “DataModeler,” mostly to get some insight in to another programmer’s ORM design decisions, as well as to see if he had any nifty ideas I could appropriate for SmartObjects. All in all, I think DataModeler is an interesting project. It’s definitely a lot simpler to set up than most other ORMs, but there’s one thing Vic does that confuses me:
[codesyntax lang="php"]
<?php
//previously, User was defined as a DataModeller Model
$user = new User;
$user->setName('Vic Cherubini')
->setAge(26)
->setHeight(74)
->setJobTitle('Software Mechanic');
?>
[/codesyntax]
That’s right, it uses setters, and for the love of me, I can’t figure out why. Getters and setters, for the unaware, are something some other programming languages use to get at variables on an object. They aren’t required in PHP. The two most common reasons for using them in PHP are:
- It allows users to set the value of private variables.
- It allows the programmer to enforce some sanity checking on the variables when they’re set.
As for the former argument, I have no idea why you’d want to allow someone to modify a private variable and yet not make that variable public or protected instead. Maybe I’m missing something but that seems rather silly to me.
The latter argument carries a lot more weight. We all love our data integrity, for sure. But PHP provides two magic methods ( __get() and __set() ) that let you override how variables are handled by your objects. Why you wouldn’t use those… Just compare these two objects:
[codesyntax lang="php"]
<?php
class foo {
private $bar;
public function setBar($value) {
$this->bar = $value; //sanity checking here.
}
public function getBar($value) {
return $this->bar;
}
}
class ding {
public function __set($name, $value) {
$this->$name = $value;
}
public function __get($name) {
return $this->$name;
}
}
?>
[/codesyntax]
Footprint-wise they’re pretty similar. You could add doc strings to foo->bar, and not to ding->bat but all in all fairly even. It’s the person using your object that’ll have a slightly easier time of it:
[codesyntax lang="php"]
<?php
//which do you like better?
$foo = new foo();
$foo->setBar('high');
$ding = new ding();
$ding->bat = "Edith Bunker";
?>
[/codesyntax]
I suppose at this point it’s six of one, a half dozen of the other. However, if you have a lot of properties and are trying to keep things DRY, there’s a clear winner:
[codesyntax lang="php"]
<?php
class foo {
private $bar;
private $bar2;
private $bar3;
private $bar4;
private $bar5;
public function setBar($value) {
$this->bar = $value; //sanity checking here.
}
public function getBar($value) {
return $this->bar;
}
public function setBar2($value) {
$this->bar2 = $value; //sanity checking here.
}
public function getBar2($value) {
return $this->bar2;
}
public function setBar3($value) {
$this->bar3 = $value; //sanity checking here.
}
public function getBar3($value) {
return $this->bar3;
}
public function setBar4($value) {
$this->bar4 = $value; //sanity checking here.
}
public function getBar4($value) {
return $this->bar4;
}
}
class ding {
public function __set($name, $value) {
$this->$name = $value;
}
public function __get($name) {
return $this->$name;
}
}
?>
[/codesyntax]
So what if you want to have the utility of sanity checking your variables and phpDocumenting them, while retaining the clean and simple style of our magic methods? Well, you could put your data checks in the __set method, but that can get hairy if you’ve got a lot of variables:
[codesyntax lang="php"]
<?php
class ding {
public function __set($name, $value) {
if ($name == 'foo' && is_int($value)) {
$this->foo = $value;
} else if ($name == 'bar' && is_array($value)) {
$this->bar = $value;
} else if ($name == 'bat' && $value == 'Edith Bunker') {
$this->bat = TRUE;
}
}
}
?>
[/codesyntax]
A better solution would be to write validator methods per data type, then suss out which type the variable being set is supposed to be, and then validate it. This looks like a job for Reflection!
[codesyntax lang="php"]
<?php
abstract class validateMe {
protected $variableValidations;
public function __construct() {
if (!is_array($this->variableValidations)) {
$this->variableValidations = $this->_getValidations();
}
}
public function __set($name, $value) {
$newName = '_'.$name;
if ($this->_validate($newName, $value)) {
$this->$newName = $value;
}
}
public function __get($name) {
$newName = '_'.$name;
return $this->$newName;
}
protected function _validate($name, $value) {
if (isset($this->variableValidations[$name])) {
$validator = $this->variableValidations[$name];
$method = $validator['type'].'Validate';
return $this->$method($value, $validator);
}
return TRUE; // or FALSE if you don't want to save any variables that don't have defined validations.
} // end _validate()
private function _getValidations() {
$ref = new ReflectionClass(get_class($this));
$props = $ref->getProperties();
$retval = array();
foreach ($props as $prop) {
$name = $prop->getName();
$varType = "unknown";
$optional = FALSE;
$args = "";
$doc = explode("\n",$prop->getDocComment());
foreach ($doc as $docLine) {
if (preg_match('/\@var\s+([A-Za-z0-9]+)\s*(.*)/', $docLine, $matches)) {
$varType = $matches[1];
if (preg_match('/\[optional\]\s*(.*)/', $matches[2], $submatches)) {
$optional = TRUE;
$args = $submatches[1];
} else {
$args = $matches[2];
}
}
}
$retval[$name] = array(
'type' => $varType,
'optional' => $optional,
'args' => $args
);
}
return $retval;
} // end _getValidations()
}
?>
[/codesyntax]
Well, that’s nice and all, but what does it do? First, it lets you declare class properties as _propertyName but access them as propertyName. With proper doc strings, you get validation! Just write methods called <datatype>Validate (eg. stringValidate) and you’re off to the races. Here’s an example class that extends the abstract validateMe:
[codesyntax lang="php"]
<?php
class ding extends validateMe {
/**
* @var array Array of options
*/
$_options = array();
/**
* @var boolean [optional] Is this person a dingbat?
*/
$_bat;
/**
* @var int between 10 and 100
*/
$_number;
public function arrayValidate($value, $options) {
return is_array($value) || $options['optional'];
}
public function booleanValidate($value, $options) {
switch ($var) {
case $var == TRUE:
case $var == 1:
case strtolower($var) == 'true':
case strtolower($var) == 'on':
case strtolower($var) == 'yes':
case strtolower($var) == 'y':
$out = TRUE;
break;
default: $out = FALSE;
}
return $out || $options['optional'];
}
public function intValidate($value, $options) {
if (preg_match('/between\s*(\d+)and\s*(\d+)/i', $options['args'], $matches)) {
if (is_numeric($value)) {
if ((int)$value == $value) {
return ($matches[1] <= $value && $value <= $matches[2]) || $options['optional']; // is it between?
} else {
return FALSE || $options['optional']; // numeric, but not an integer
}
} else {
return FALSE || $options['optional']; // not numeric
}
} else {
return (is_numeric($value) && ((int)$value == $value)) || $options['optional'];
}
}
}
?>
[/codesyntax]
Now in this case, this isn’t any less verbose than the getter/setter alternative would be. But this is an example to show how crazy you could get. Here’s a more real world example, a user object:
[codesyntax lang="php"]
<?php
class user extends validateMe {
/**
* @var string /^[A-Za-z0-9_-]+$/ User name, alphanumerics - and _ are allowed.
*/
private $_username;
/**
* @var string /^[A-Za-z]+$/ Last name, alpha allowed.
*/
private $_surname;
/**
* @var string /^[A-Za-z]+$/ First name, alpha allowed.
*/
private $_givenname;
/**
* @var string /^\d{3}[-]*\d{2}[-]*\d{4}$/ Social Security Number
*/
private $_ssn; public function stringValidate($value, $options) {
if (preg_match('/(\/[^\/]+\/).*/', $options['args'], $matches)) {
// we have a regex
return (is_string($value) && preg_match($matches[1], $value)) || $options['optional'];
} else {
return is_string($value) || $options['optional'];
}
}
}
?>
[/codesyntax]
And you’ve validated and documented all of your variables, without writing individual getter/setter functions for each. Mmm… That’s a spicy meatball!
This is actually pretty close to how the new anObject is going to work. SmartObjects, though, are getting a super fancy validation plugin that does this, and also will check the DB structure for validity. “DESCRIBE `tablename`” anyone?
Tags: php, Programming