Translate: 
EnglishFrenchGermanItalianPolishPortugueseRussianSpanish

Strong data typing in PHP, part II: autoboxing and indestructable objects

In an earlier article on the strong typing I’ve described the typehint mechanism that provides data type enforcement for the values sent to the methods and functions. Unfortunately said implementation does not protect against another problem associated with the dynamic typing of variables: a lack of type enforcement when overwritting value of an existing variable.

In order to control the type of data, I decided to introduce to PHP the concept of autoboxing known from other languages such as C# and Java.

What is autoboxing?

To quote the English Wikipedia:

Autoboxing is the term for treating a value type as a reference type without any extra source code. The compiler automatically supplies the extra code needed to perform the type conversion.

So in short, autoboxing allows to automatically wrap the primitive types (integer, bool, float, string) into objects.

Of course PHP has no built in support for autoboxing, and does not provide a simple method for monitoring changes applied to existing variables (there is one exception however: magical variables in classes). Fortunately for us, the PHP is written in C, C++, so it inherits some of the most powerful functionalities found in those languages, namely: operations on pointers and references.

Where can I download the working solution?

nominacja 2 miejsceThe working library can be downloaded from Softpedia (without login) or phpclasses (logon required) or directly from my blog repository (no logon required).

How can I enable autoboxing in PHP?

First of all, you need to create an array that holds pointers to PHP variables and a method to create new pointers during variables’ declaration process:

class VariablesManager
{
	protected static $counter;
 
	public static $memory = array();
 
	public static function & getNewPointer($dataType) {
		$name = ++self::$counter;
		VariablesManager::$memory[$name] = $dataType;
 
		if(is_object($dataType) && in_array('setPointer', get_class_methods($dataType))) {
			$dataType->setPointer($name);
		}
		return VariablesManager::$memory[$name];
	}
 
	private function __construct() { }
}

The pointers list is stored in a VariablesManager::$memory array, while the method responsible for pointer creation is called VariablesManager::getNewPointer(). This method takes a simple instance of existing variable as an input argument to make it wrappable by other objects.

In order to avoid any confusion, a private constructor forces us to use VariablesManager class statically.

Once we have the main class, we can focus on another, which will be inherited by all wrapped objects. This class is called AutoBoxedObject:

abstract class AutoBoxedObject
{
	protected $ref;
 
	public function __destruct() {
		if($this->ref === null) {
			return;
		}
		if(VariablesManager::$memory[$this->ref] instanceof self) {
			VariablesManager::$memory[$this->ref]->setPointer($this->ref);
 
		} else if(is_scalar(VariablesManager::$memory[$this->ref])){
			$val = VariablesManager::$memory[$this->ref];
			$class = get_class($this);;
 
			VariablesManager::$memory[$this->ref] = new $class($val);
			VariablesManager::$memory[$this->ref]->setPointer($this->ref);
		}
	}
 
	public function setPointer($name) {
		$this->ref = $name;
	}
}

As you can see, this class has two public methods: setPointer() and __destruct(), and one auxiliary variable $ref storing the reference to a wrapped variable.

The public destructor however is the most important method of this class, its main task is to check the reason of the object destruction: if the variable holding $this object is being unset, or overwritten by a new value. The check is done by reading the value pointed by the appropriate pointer stored in the VariablesManager::$memory.

When the variables’ value has been overwitten, the above pointer no longer points to the $this instance, but to the new value. This allows us to catch this value during the destruction of an old object, and wrap it into new object of the same type.

This provides us with opportunity to validate a new value, and as a side effect – makes autoboxed objects indestructable.

This class contains one more thing not described before, the setPointer() method. This is a simple setter, which is used to pass variables’ pointer to the new value during the destruction of the old one.

How to use autoboxing?

In order to create an indestructable object, we only need to do two simple things: implement a new data type (declare a new class) and create an access function. In this case let it be a String class:

class String extends AutoBoxedObject
{
	public $value;
 
	public function __construct($value) {
		$this->value = $value;
	}
 
	public function __toString() {
		return "$this->value";
	}
 
	public function toUpperCase() {
		return strtoupper($this->value);
	}
}
 
function & string($value = null) {
	$x = & VariablesManager::getNewPointer(new String($value));
	return $x;
}

The String class does not differ from other standard PHP classes but it needs to meet two simple conditions: must inherit AutoBoxingObject, and call parent destructor if it has been overriden.

The &string() access function is used to make (declare the type of) a new object, generate and store pointer to the instance, and return it as a function result. It is important to remember, that this function returns pointer to the object, and not the object itself.

How does it work in practice?

Once you have all the necessary classes and functions, you can try out the autoboxing in practice:

$y = & string("aaa");
// lets check, that $y is an object
var_dump($y);
 
// now we are overwritting $y variable with a scalar value of "zzz"
$y = "zzz";
// var_dump() shows us, that "zzz" is still an object, not the scalar type as in regular PHP
var_dump($y);
// the line below should raise a fatal error, because "zzz" was a scalar type (string), but it will be ok, because $y is still an object (thanks to autoboxing)
var_dump($y->toUpperCase());

Here is the output of the script:

object(String)#1 (2) {
  ["value"]=>
  string(3) "aaa"
  ["ref":protected]=>
  int(1)
}
object(String)#2 (2) {
  ["value"]=>
  string(3) "zzz"
  ["ref":protected]=>
  int(1)
}
string(3) "ZZZ"

How to enforce strong data typing?

It’s really easy. With the autoboxing mechanism it can be achieved by validating the data sent to the constructors of classes that inherit from the AutoboxingObject class.

class Integer extends AutoBoxedObject
{
	public $value;
 
	public function __construct($value) {
		if(!is_int($value)) { throw new Exception("Invalid data type"); }
		$this->value = $value;
	}
 
	public function __toString() {
		return (string)$this->value;
	}
 
	public function getValue() {
		return $this->value;
	}
}

Of course, in order to enforce the data type of primitive values (integer, float, etc), you have to create the appropriate classes in which they will be wrapped.

Ready-made scripts that implement this functionality can be downloaded from the links given at the beginning of the article.

This is a sample source code taken from the library example file:

Summary

The autoboxing mechanism, which I presented above has three main advantages: fits PHP with strong data typing functionality, allows to wrap primitive data types into objects, and does not require any server-side C, C++ PHP modules.

I wrote this code in 2005-2006 for the purposes of my framework and the String class compatible with Java. This makes it the first, and to this day the only working solution of this type. Unfortunately there is no hint, whenever autoboxing will be implemented in the PHP language by its developers, but as you can see, it is not difficult to do so.

Is the strong data typing useful for PHP? From this moment you can check by yourself.


Other articles about breaking the limits of PHP

Tags: ,

9 Responses to “Strong data typing in PHP, part II: autoboxing and indestructable objects”

  1. Artur Graniszewski says:

    Yes, I’ve seen this. But spl_types module is used only for type hinting of arguments passed to the functions and methods.

    I’ve implemented something different: full autoboxing, and strong typing not only in arguments passed to functions, but even in simple string/math operations.

  2. Mihai Stancu says:

    Hello,

    First off i want to congratulate you. The idea is very ingenious!

    The first thing i tried after i tested your design was

    $y = & string(“aaa”);
    var_dump($y);
    $x = $y;

    Which adds to the refcount which in turn makes the environment not call the destructor, all fails from there.

    I also downloaded the archvie and the production version has the same problem.

    Have you found a workaround for this? If not what is the convention used in order to avoid this caveat? Can you restrict referencing to a variable?

  3. Artur Graniszewski says:

    Hi,
    thank you for testing this solution. As I wrote in the article, autoboxing works only because PHP was written in C, so it can mimick its references and pointers.

    In your case you have to assign $y to $x variable in a -like fashion using the reference operator like this:

    $y = & string(„aaa”);
    var_dump($y);
    $x = & $y;

    If you want to do a copy of $y (not a reference), you have to use strong typing everywhere:

    $y = & string(„aaa”);
    $x = & string();
    var_dump($y);
    $x = $y;

    You will probably need to add some simple type testing in the String class __constructor(), so it will allow to use String class as an input argument as a valid data type.

  4. Mihai Stancu says:

    I had noticed that the explicit reference does not create the problem.

    It’s a pity this problem arose, the solution was so elegant, and the whole point of auto-boxing is to make some operations look and feel more natural.

    Employing the necessary steps to use it would certainly shorten the total count of the lines of code but in a team project someone is bound to break the toy and bug-fixing would be obscure.

    I was also looking at the SPL_Types they do not only implement the wrapper classes for type-hinting they also have auto-boxing. Unfortunately they current package is not compatible with my version of PHP, i made some manual changes in the source-code to aid compatibility but it didn’t seam to work.

  5. Mihai Stancu says:

    Hello,

    I’ve had some more time to play with this new toy, and i found another pitfall: mathematical operators used directly on such objects do not trigger the “__toString()” magic function and trigger an error that the Object could not be converted to Int.

    I’ve been looking for workarounds and patches, have you dealt with this yourself?

  6. Artur Graniszewski says:

    Yes, I found this pitfall too and I have only one solution.

    You can do something like this:

    $number = & Int();

    $number = “$anotherNumber” + “$someOtherNumber”;

    Yes, I know – this is a strange solution.

  7. Warbo says:

    What’s wrong with good old Peano Arithmetic?

    interface PeanoNumber {
    public function value();
    public function plus($n);
    public function times($n);
    }

    class Zero implements PeanoNumber {
    public function value() { return 0; }
    public function plus($n) { return $n; }
    public function times($n) { return $this; }
    }

    class Successor implements PeanoNumber {
    private $of;
    function __construct($of) { $this->of = $of; }
    public function value() { return 1 + $this->of->value(); }
    public function plus($n) { return new Successor($this->of->plus($n)); }
    public function times($n) { return $n->plus($n->times($this->of)); }
    }

    $zero = new Zero();
    $one = new Successor($zero);
    $two = new Successor($one);

  8. Rubens de Souza SIlva says:

    Congratulations Artur Graniszewski!
    It’s the best way try typing that I saw!

    I tryed many solutions, but everything stoped when to change value of variable.

    I hope PHP6 can make magic methods _toInt, _toFloat, _toBool…

    (Forgive my English)

Leave a Reply