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 ;)

17 comments:

alsanan said...

Andrea, I think it's tremendously unfair that your work don't get a lot of feedback, as a signal of the revolutionary of your ideas.

Congratulations. I really enjoy your work. Keep it.

alsanan said...

BTW: http://digitta.com/2009/03/jsphp-una-clase-php-cual-objeto.html I've linked/translated your idea to my blog...

Andrea Giammarchi said...

alsanan, thanks to your blog and links around the net, I can tell you at least daily visitors are every day more ;)

Congratulation in any case for digitta, I've never seen so much devotion for a blog translating everything "without robots". I read it ( I am surprised, I can even understand written Spanish :) ) and thank you for the link.

Regards

Ionut G. Stan said...

I can't believe how similar we think, at least sometimes.

I've done a similar prototype thingy in PHP in August 2008.

And, as if that alone wouldn't be enough, you've also had the idea of using functions as factories for objects.

Anyway, I had no idea about the php_operator extension. It's really nice. I wonder why there's no discussion to move in into core.

Tobiasz Cudnik said...

I really really like it. When bringing JS into PHP you can find phpQuery useful, as it mimics jQuery and loves closures and extend() method.

dagfinn said...

Thanks, this is interesting. I was made aware of your post in a comment to my own recent similar blog post. (http://is.gd/qhZJ)

I'm trying to figure out how our approaches relate, and I have a question. What specifically are you using ArrayAccess for? What's the point? Maybe I missed something, but I can't see it in your examples.

Andrea Giammarchi said...

ArrayAccess makes possible both kind of iteractions JavaScript like:
$o->key
and
$o['key']
;)

Brett said...

This looks very interesting... There's some interesting stuff you can do with the runkit PHP extension too, in terms of JS-like behavior.

Haven't used it myself, but we've been implementing the runkit functions (and the older related extensions object aggregation and classkit ).

We haven't implemented (or can't implement?) these runkit functions.

Lastly, I'm hoping Mozilla might implement this request and this one, if they're already not planned for 2.0, as they could enable some useful PHP-supported behavior in JavaScript...

Brett said...

This looks very interesting... There's some interesting stuff you can do with the runkit PHP extension too, in terms of JS-like behavior.

Haven't used it myself, but we've been implementing the runkit functions (and the older related extensions object aggregation and classkit ).

We haven't implemented (or can't implement?) these runkit functions.

Lastly, I'm hoping Mozilla might implement this request and this one, if they're already not planned for 2.0, as they could enable some useful PHP-supported behavior in JavaScript...

Andrea Giammarchi said...

Brett, runkit and operator are the only extensions we need to create a JS like environment but since PECL has some problem and these extensions are not completed, we can only pray they will implement them in the core (php 6 then, hopefully)

Brett said...

As I just mentioned at http://phpjs.org/pages/home#comment_37303 to someone who brought up the j4p5 project (parse JavaScript into PHP for server-side use), if the runkit extension at least is around, it shouldn't be too difficult I would imagine to just use a JavaScript tokenizer to directly translate from real JavaScript syntax into the PHP version (caching the results of course). I would think that in such a case, the operator extension wouldn't even be needed, as the translating script could do the job...

professional web design company india said...

Its highly informative. I would be visiting your blog hereafter regularly to gather valuable information.

Patrick said...

Hi Andrea

I was looking into server side javascript because the prototype pattern seemed so interesting. It's great to see how it can be put together in a more common language.

Thanks

Anonymous said...

I found this site using [url=http://google.com]google.com[/url] And i want to thank you for your work. You have done really very good site. Great work, great site! Thank you!

Sorry for offtopic

Anonymous said...

...please where can I buy a unicorn?

Andrea Giammarchi said...

rotfl

Anonymous said...

Yes, it is solved.