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.

Wednesday, December 29, 2010

PHP "Functors" and the __invoke method

 Imagine you have some method of a class that receives as a parameter a function, maybe a callback. You could use one of the new "Closures" available in PHP 5.3, but that way you woudn't have any kind of type hinting or make the code reusable. Why don't we try using something called Functor.



From wikipedia:
"A function object, also called a functor, functional, or functionoid,[1] is a computer programming construct allowing an object to be invoked or called as though it were an ordinary function, usually with the same syntax."
This is exactly why PHP 5.3 has the new magic method __invoke. From the PHP documentation:
"The __invoke method is called when a script tries to call an object as a function."

Bare with me with the totally uselless following code snipet, which will serve to demonstrate how to use Functors. First we create an abstract superclass for the operations:


abstract class AbstractMathOperation {
    /**
     * @var integer
     */
    protected $_counter;
    /**
     * @param integer $startAt
     */
    public function  __construct( $startAt = 0 ) {
        $this->_counter = $startAt;
    }
    /**
     * @return string
     */
    public function  __toString() {
        return (string) $this->_counter;
    }
}

Next the subclasses of AbstractMathOperation:



class Adder extends AbstractMathOperation {
    /**
     * @param integer $toAdd
     * @return integer
     */
    public function __invoke( $toAdd ) {
        return $this->_counter += $toAdd;
    }
}
class Square extends AbstractMathOperation {
    /**
     * @param integer $base
     * @return integer
     */
    public function __invoke( $base ) {
        return $this->_counter = $base * $base;
    }
}

And then how to use them:



class UselessMathClass {

    public static function performOperation( AbstractMathOperation $operMethod, $num ) {
        return $operMethod($num);
    }
}

$Adder = new Adder;
$Square = new Square;

print UselessMathClass::performOperation(
        $Adder, UselessMathClass::performOperation(
                $Square, UselessMathClass::performOperation($Adder, 3)
        )
);


This way you'll have better control on the type of callback you pass as parameter.

Monday, February 15, 2010

PHP Serialize DomDocument

Did you ever tried to serialize a DomDocument Object? Well, you can't!
Unless you use a little trick, taking advantage of __sleep and __wakeup magic methods.

Lately i'm using a lot of Zend_Cache mechanism which is quite awesome and easy to use. Specially the Zend_Cache_Frontend_Class frontend, that enables you to cache the return of methods in a fairly transparent way. I had a method that returns a DomDocument Object, and kept getting error when trying to get it back from MemCache. After some investigation I found that Zend_Cache uses serialize(). This function doesn't work with DomDocument Object and another kind of internals. The solution was to make the serialize/unserialize during the __sleep/__wakeup magic methods.

You need to create a class that extends DomDocument as the following code shows:



<?php
class SerializableDomDocument extends DomDocument {

 private $_xml;


 public function __sleep() {
  $this->_xml = $this->saveXML();
  return array('_xml');
 }

 public function __wakeup() {
  $this->loadXML( $this->_xml );
 }
}
?>


This way, you can use Zend_Cache or keep XML DomDocument Objects in $_SESSION.