I hammered out some example mediator code.
It comes in the form of a CAttribute class - an abstract base class for abstract data types, e.g. anything that cannot be represented by simple PHP variable types.
Note that this code has not been tested, and I have not yet attempted to integrate this with CActiveRecord - it’s just an idea at this point.
The integration would consist of a simple modification to CActiveRecord::setAttribute(), which would check if the attribute being set is a CAttribute, and if it is, it would call setValue() on it, rather than overwriting the attribute itself.
Modifications would be required (I’m not entirely sure yet where), when attributes are written into an UPDATE or INSERT statement, there needs to be a check to see if the attribute is CAttribute, and if it is, get the mediator for the current DB connection and call CAttributeMediator::getSqlValue() which converts the attribute to the SQL representation required for the connection’s driver.
Likewise, after a SELECT, when a CActiveRecord is constructed, CAttributeMediator::setSqlValue() needs to be used to convert the SQL string into a CAttribute representation.
I think I could implement most of this without actually hacking any part of the Yii codebase, just creating overrides, so I might continue with this proof of concept to see if I can pull that off - unless someone can convince me that this idea is somehow retarded in the first place
But most ORMs have a mediation layer of some sort - in the case of PHP it’s even more necessary than in some other languages, because PHP only has a few primitive types of it’s own; a timestamp isn’t even discernible from an integer as such, a file path isn’t discernible from any other string, and so on.
Having more abstract type implementations, however, is only really interesting if we can somehow mediate between SQL and form posts, without relying on manual conversion everywhere - it’s tedious, and error prone
<?php
/**
* Abstract base class for an attribute class.
*
* An attribute object encapsulates an abstract value, which cannot be represented
* by a single, native PHP variable-type.
*/
abstract class CAttribute
{
/**
* Returns the mediator for the attribute
* @see CAttributeMediator
*/
public function getMediator($dbConnection=null)
{
if ($dbConnection===null)
$dbConnection = Yii::app()->db;
$driver = $dbConnection->getDriverName();
static $mediators=array();
$className = get_class($this).ucfirst($driverName).'Mediator';
if (isset($mediators[$className]))
return $mediators[$className];
if (class_exists($className))
return $mediators[$className] = new $className;
else
throw new CException("CAttribute::getMediator() : no mediator {$className} was found");
}
/**
* Returns a native PHP value that represents the internal value of the attribute
*/
abstract public function getValue();
/**
* Sets the internal value of this attribute to a native PHP value, or a string posted by an HTML <form>
* @param mixed a native PHP value
* @param boolean if true, $value will be handled as a string posted by an HTML <form>
*/
abstract public function setValue($value,$post=false);
}
/**
* Abstract base class for an attribute mediator.
*
* An attribute medidator is responsible for conversion of an abstract value,
*/
abstract class CAttributeMediator
{
/**
* Returns an SQL string representation of the internal value of the given attribute.
* @param CAttribute the attribute to convert to an SQL string
* @return mixed the SQL string representation of the value, or null if the attribute is unset
*/
abstract public function getSqlValue($attribute);
/**
* Sets the internal value of the attribute according to an SQL string value.
* @param CAttribute the attribute to be set
* @param string the SQL string value to be interpreted and applied to the given attribute
*/
abstract public function setSqlValue($attribute, $string);
}
/**
* This example attribute-type encapsulates a PHP timestamp.
*/
class CDatetimeAttribute extends CAttribute
{
protected $_timestamp;
public function getValue()
{
return $this->_timestamp;
}
public function setValue($value,$post=false)
{
if ($post)
return $this->_timestamp = !empty($value) ? strtotime($value) : null;
if (is_numeric($value) || $timestamp===null)
return $this->_timestamp = value;
throw new CException("CDatetimeAttribute::setValue() : incompatible value '{$value}'");
}
public function __isset()
{
return $this->_timestamp > 0;
}
}
/**
* The example mediator converts to and from a MySQL DATETIME formatted string representation
*/
class CDatetimeAttributeMysqlMediator extends CAttributeMediator
{
public function getSqlValue($attribute)
{
$value = $attribute->getValue();
return !empty($value) ? date('Y-m-d H:i:s', $value) : null;
}
public function setSqlValue($attribute, $string)
{
$attribute->setValue(
$string!='' ? strtotime($string) : null
);
}
}