Thursday, December 30, 2010

PHP User defined Object casting

PHP being a dynamic language, makes type juggling transparent, and in most cases is something you don't need to worry about. At least in small projects! When you have huge projects, static typing is something most appreciated, but you've to learn to live without, if you need to develop in PHP.

In rare cases, it's handy to do Object casting, which is something PHP is not able to do natively. Examples are objects transfered via JSON or read from the Database that you want to map to proper PHP objects ( e.g. StdClass to MyPHPCoolClass ) or ValueObject casting. If PHP doesn't have the mechanism to do it natively, just write your own code to do so.

Here's my aproach, with checking for inheritance:

First the SuperClass:

class SuperClass {
    public $attr1 = 10;
    public $attr2;
    
    public function __construct() {
        $this->attr2 = new SubVO();
    }

    public function showClass() {
        print "Object is: " . get_class($this) . "\n";
    }
}


And now the SubClass:

class SubClass extends SuperClass {
    
    public $subAttr1 = 'SubClass';
    
    public function __construct() {
        parent::__construct();
    }

    public function amIsubClass() {
        print "Yes I am a subclass\n";
    }
}



And a class for a dummy object, used as attribute of the SuperClass:

class SubVO {
    public $subo = "I want some €";
}


My first aproach was to create a new object of the new class and copy all attributes between them. This solution was very fast, even five times faster than the later. But has one major problem/advantage: attribute objects are copied only as references.


function castReferenced($object, $className) {
    $newObject = new $className();
    foreach($object as $k => $val) {
        $newObject->{$k} = $val;
    }
    return $newObject;
}


If you need the object to be totally duplicated you should use the second aproach, which is slower. It cheats using a replace on a string of serialized object:

function castDuplicate($object, $className) {
    $serialized = serialize($object);
    $objectClass = get_class($object);
    $objectClassLen = strlen($objectClass);
    $start = $objectClassLen + strlen($objectClassLen) + 6;
    $newObjectSerialized = 'O:' . strlen($className) . ':"' . $className . '":';
    $newObjectSerialized .= substr($serialized, $start);
    $newObject = unserialize($newObjectSerialized);
    if (! is_a($object, $className) && ! is_a($newObject, $objectClass)) {
        throw new Exception('Object cannot be casted to class "' . $className . '"');
    } elseif(is_a($object, $className)) {
        foreach($newObject as $k => $val) {
            if (! property_exists($className, $k)) unset($newObject->{$k});
        }
    }
    return $newObject;
}

I also added a way to remove excess of attributes when casting from SubClass to SuperClass.
If you use only objects with attributes that are PHP native types, the first aproach is better considering the gain in speed.