My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Sunday, June 22, 2008

From the future, a PHP JavaScript like Number class, with late static binding and operator overloading

PHP and operator overloading


Not everyone knows that with PHP, and since 2006, it is possible to implement operator overloading, thanks to a PECL extension.
One common PHP VS other language flame, is about PHP poor OOP expressiveness and power.
Another common request from skilled PHP developers, is the possibility to create a class that is able to manage every situation during code execution.
For example, try to think about a Number class, and try to use every native Magic method to perform a simple task like this one:

$i = new Number(123);
$i += 2;
echo $i; // 125

Above code is simply not implementable with current version of PHP 5.2 or greater, if we do not install php_operator.so or .dll.
Unfortunately, this extension is still experimental, and not documented at all.
That is why I have created a full class, based entirely on this extension, and for this post only, compatible with PHP 5.2 (then, without usage of late static binding).

The JavaScript like Number class


Next code, is a complete example of how we could implement operator overloading, to perform every kind of task operator related:

/** JavaScript Number like class
* @author Andrea Giammarchi
* @site webreflection.blogspot.com
* @license Mit Style
*/
class Number {
protected $__value__;
public function __construct($__value__ = 0){
switch(true){
case intval($__value__) == $__value__:
case floatval($__value__) == $__value__:
$this->__value__ = $__value__;
break;
default:
$this->__value__ = (int)$__value__;
break;
}
}
public function __add($__value__){
return new Number($this->__value__ + Number::__value__($__value__));
}
public function __sub($__value__){
return new Number($this->__value__ - Number::__value__($__value__));
}
public function __mul($__value__){
return new Number($this->__value__ * Number::__value__($__value__));
}
public function __div($__value__){
return new Number($this->__value__ / Number::__value__($__value__));
}
public function __mod($__value__){
return new Number($this->__value__ % Number::__value__($__value__));
}
public function __sl($__value__){
return new Number($this->__value__ << Number::__value__($__value__));
}
public function __sr($__value__){
return new Number($this->__value__ >> Number::__value__($__value__));
}
/* in this class, same behavior of __toString
public function __concat($__value__){
return (string)$this->__value__.$__value__;
}
#*/
public function __bw_or($__value__){
return new Number($this->__value__ | Number::__value__($__value__));
}
public function __bw_and($__value__){
return new Number($this->__value__ & Number::__value__($__value__));
}
public function __bw_xor($__value__){
return new Number($this->__value__ ^ Number::__value__($__value__));
}
/* extension error. Anyway, in this class, it is not implemented - two (new Number) are always different
public function __is_identical($__value__){
return $this === $__value__;
}
public function __is_not_identical($__value__){
return $this !== $__value__;
}
#*/
public function __is_equal($__value__){
return $this->__value__ == Number::__value__($__value__);
}
public function __is_not_equal($__value__){
return $this->__value__ != Number::__value__($__value__);
}
public function __is_smaller($__value__){
return $this->__value__ < Number::__value__($__value__);
}
public function __is_smaller_or_equal($__value__){
return $this->__value__ <= Number::__value__($__value__);
}
/* waiting for the patch
public function __is_greater($__value__){
return $this->__value__ > Number::__value__($__value__);
}
public function __is_greater_or_equal($__value__){
return $this->__value__ >= Number::__value__($__value__);
}
#*/
public function __bw_not(){
return new Number(~$this->__value__);
}
/* undefined behaviour, waiting for documentation
public function __bool(){
return $this->__value__ != 0;
}
public function __bool_not(){
return $this->__value__ == 0;
}
#*/
public function __assign_add($__value__){
$this->__value__ += Number::__value__($__value__);
return $this;
}
public function __assign_sub($__value__){
$this->__value__ -= Number::__value__($__value__);
return $this;
}
public function __assign_mul($__value__){
$this->__value__ *= Number::__value__($__value__);
return $this;
}
public function __assign_div($__value__){
$this->__value__ /= Number::__value__($__value__);
return $this;
}
public function __assign_mod($__value__){
$this->__value__ %= Number::__value__($__value__);
return $this;
}
public function __assign_sl($__value__){
$this->__value__ <<= Number::__value__($__value__);
return $this;
}
public function __assign_sr($__value__){
$this->__value__ >>= Number::__value__($__value__);
return $this;
}
public function __assign_concat($__value__){
throw new Exception('unable to concatenate "'.$__value__.'" with a value of an instanceof '.Number);
}
public function __assign_bw_or($__value__){
return new Number($this->__value__ | Number::__value__($__value__));
}
public function __assign_bw_and($__value__){
return new Number($this->__value__ & Number::__value__($__value__));
}
public function __assign_bw_xor($__value__){
return new Number($this->__value__ ^ Number::__value__($__value__));
}
public function __pre_inc(){
++$this->__value__;
return $this;
}
public function __pre_dec(){
--$this->__value__;
return $this;
}
public function __post_inc(){
return new Number($this->__value__++);
}
public function __post_dec(){
return new Number($this->__value__--);
}
public function __toString(){
return (string)$this->__value__;
}
static protected function __value__($__value__){
return $__value__ instanceof Number ? $__value__->__value__ : $__value__;
}
public function toExponential($decimal = 0){
return sprintf(func_num_args() ? '%.'.$decimal.'e' : '%e', $this->__value__);
}
public function toFixed($decimal = 0){
return (string)(0 < func_num_args() ?
round($this->__value__ * ($decimal = pow(10, $decimal))) / $decimal :
round($this->__value__)
);
}
public function toPrecision($decimal = 0){
if(0 < func_num_args()){
$length = strlen((int)$this->__value__);
if($decimal < $length)
$result = $this->toExponential($decimal);
elseif(strlen($this->__value__) <= $decimal)
$result = str_pad($this->__value__, $decimal, '0', STR_PAD_RIGHT);
else
$result = $this->toFixed($decimal - $length);
}
else
$result = (string)$this->__value__;
return $result;
return sprintf(func_num_args() ? '%.'.$decimal.'e' : '%e', $this->__value__);
}
public function toLocaleString(){
return is_int($this->__value__) ?
(string)$this->__value__ :
number_format($this->__value__, strlen($this->__value__) - strpos($this->__value__, '.') - 1)
;
}
public function toSource(){
return 'new Number('.$this->__value__.')';
}
public function toString(){
return (string)$this->__value__;
}
public function valueOf(){
return $this->__value__;
}
}

With above class we can use type hint checks with functions or method:

function atLeastOne(Number $total){
return 0 < $total;
}

if(atLeastOne(new Number(1)))
echo 'here we go';

We can do almost everything we could do with a primitive int or float value, but we have methods too, while we cannot implement them in a primitive value:

$f = new Number(123);
$f += new Number(0.345);
echo $f->toFixed(2); // 123.35

This case is about numbers, but nothing block us to create classes like, for example, a Date one, where to know the difference between two dates you could simply do:

echo new Date() - new Date($twohoursago); // 7200


The introduction of late static binding


The biggest limit ever, in PHP OOP, is the implementation of self keyword, that does not allow us to extend easily a class.
For example, the Number class, cannot be extended in a reasonable way, because since every method use static public self::__value__ one to get correct information from received value, we should override every method to let them point into different static one.
Indeed, it is natural to think about an Int, and a Float class, but we cannot create them quickly and logically, while using this Number class implementation from the future it is possible to create Int and Float in few lines of code:

class Int extends Number {
static protected $__CLASS__ = __CLASS__;
public function __construct($__value__ = 0){
$this->__value__ = static::__value__($__value__);
}
static protected function __value__($__value__){
return intval($__value__ instanceof Number ? $__value__->__value__ : $__value__);
}
}

class Float extends Number {
static protected $__CLASS__ = __CLASS__;
public function __construct($__value__ = 0){
$this->__value__ = static::__value__($__value__);
}
static protected function __value__($__value__){
return floatval($__value__ instanceof Number ? $__value__->__value__ : $__value__);
}
}


GO, OPERATOR, GO!!!


We have just seen that operator overloading could open a lot of doors for PHP OOP development, and as is for the SPL, the best thing PHP developers have done since PHP 5 release, and as was for the Go, PHP5 Go!!! period, I do like to be able to create classes that are powerful, expressive, and without boring limits for our fantasy, control, and finally to let us be truly proud to develop with such amazing language. Next step? A mix of runkit extension, operator, and SPL, to create a real JavaScript implementation in pure PHP ( how could I call them, Phino? )

4 comments:

  1. What a nifty piece of software you have there!

    And thanks for your mention on the php_operator extension. Didn't knew about it at all.

    ReplyDelete
  2. you, guy, you are a genius. Your posts are (All of them, is incredible) are very advanced and have a lot of meat. Congratulations. You have the best blog I've discovered in this year.

    ReplyDelete
  3. You rock! This is absolutely amazing.. i've been looking for operator overloading in PHP for such a long time...

    ReplyDelete
  4. Cheers guys :)
    Now we have to convince Zend and PHP team to integrate operator in next PHP release.
    Go, operator, GO!

    ReplyDelete

Note: Only a member of this blog may post a comment.