Thursday, March 26, 2009

[js.php] A JavaScript Object Like PHP Class

PHP 5.3 introduces the Closure class which is really useful and more powerful than good old lambdas via create_function.
These are main limits of Closure class:

  • you cannot serialize a Closure instance (and json_encode does not solve the problem at all)

  • you cannot inject scopes via $this unless the closure comes from a class method


While with an emulated prototype style class, something I tried months ago as well, the first point could not be a problem, to solve the second one is quite inevitable to use a Python like variable as first argument, called $self, to make the Closure both portable and re-adaptable.


JSObject :: JavaScript Object Like PHP Class


/** js.php basic JSObject implementation
* @author Andrea Giammarchi
* @blog WebReflection.blogspot.com
* @license Mit Style License
*/
class JSObject implements ArrayAccess {

// public static prototype
static public $prototype;

// ArrayAccess Interface
public function offsetExists($index){return isSet($this->$index);}
public function offsetGet($index){return $this->$index;}
public function offsetSet($index, $value){$this->$index = $value;}
public function offsetUnset($index){unset($this->$index);}

// Invoked lambdas via $this or self::$prototype
public function __call($index, array $arguments){

// inject $this as first argument
array_unshift($arguments, $this);

// invoke the callback
return call_user_func_array(

// if it has been assigned to the object itself
isset($this->$index) ?

// it has priority
$this->$index :

// otherwise invoke from self::$prototype
self::$prototype->$index
,
$arguments
);
}

// JS like behavior, ready for extends
public function __toString(){
return '[object '.get_class($this).']';
// e.g. [object JSObject]
}

// Relevant Global Object implementations
public function getPrototypeOf(){
return get_class($this);
}
public function hasOwnProperty($index){
return isset($this->$index);
}
public function isPrototypeOf(/* string */ $class){
return $this instanceof $class;
}
public function toSource(){
// we could implement __sleep and __wakeup
// to avoid closure serialization (not possible yet)
return serialize($this);
}
public function toString(){
return $this->__toString();
}


// not standard method, anyway useful
public function toJSONString(){
// we could parse properties in a foreach
// to filter meaningless closures, if any
return json_encode($this);
}
}

// the global prototype is a JSObject too
JSObject::$prototype = new JSObject;

// JSObject factory: e.g. $myobj = JSObject(); // rather than new JSObject;
function JSObject(){
return new JSObject();
}

Above class uses an interface from the great SPL, an in-core extension present since PHP 5.1
The rest is theoretically compatible with old style lambdas, static functions, or new Closure instances. Here we go with some example:

// we can assigns prototypes everywhere ...
JSObject::$prototype->getName = function($self){
return $self->name;
};

$o = JSObject();

// ... even after JSObject instances creation
JSObject::$prototype->writeAge = function($self){
echo $self->getName().' is '.$self->age.' years old.';
};

// assign properties
$o->name = 'Andrea';
$o->age = 30;

// test some JS method
echo $o->toJSONString();
// {"name":"Andrea","age":30}

echo $o;
// [object JSObject]

echo $o->toSource();
// O:8:"JSObject":2:{s:4:"name";s:6:"Andrea";s:3:"age";i:30;}

// or tests runtime methods
$o->writeAge();
// Andrea is 30 years old.

// we can assign a custom method runtime
$o->getSurname = function($self){
return 'Giammarchi';
};

// custom methods have priority over prototype
$o->getName = function($self){
return $self->name.' '.$self->getSurname();
};

$o->writeAge();
// Andrea Giammarchi is 30 years old.

Is it cool? And more is coming for backward compatibility:

// prototype via function name resolution
JSObject::$prototype->getName = 'JSObject_getName';

// the function with a prefix to avoid conflicts
function JSObject_getName($self){
return $self->name;
}

// a simple test case
$o = JSObject();
$o->name = 'Andrea';

echo $o->getName();
// Andrea

And obviously we can do the same with create_function:

// prototype via lambda
JSObject::$prototype->getName = create_function('$self','
return $self->name;
');


Conclusion


Via SPL, closures, and some convenient trick, we could completely change the code style of PHP. Performances are always a priority, in any case, but with a couple of good practices, some discarded or "language useless" piece of ECMAScript, we could use JS style as low level structure for more Object Oriented frameworks or completely different ways to code.

What's next?


It is ages that I am waiting for a stable PHP 5.3 plus PECL extensions (in particular the php_operator to add operator overloads) to be able to create a JavaScript version of PHP. I have already created similar classes and my vision is a one2one JavaScript style framework created entirely via PHP. Craziness? Non-sense? Dunno, just try to imagine a Jaxer like behavior in every host with "just" PHP support ... is it a cool idea? Well, please contact me if you are interested ;)

No comments:

Post a Comment