rfc:unset_bool

PHP RFC: unset(): return bool if the variable has existed

Introduction

The purpose of this RFC is to make unset return something meaningful. People also shouldn't have to wonder why they can't use unset if it isn't as a standalone expression.

This removes also an inconsistency: the function's return value is void, states the docs. It is until now the only function (language construct, function-like) (apart from echo, which already has an alternative: print) which has no return value.

Proposal

Change unset()'s behaviour to:

  • return true if deletion was successful
  • return false if deletion failed (e.g. there was nothing to delete or the deleting function has failed)

The feature could be useful in if's like:

public function deleteKey ($key) {
	if (unset($this->array[$key])) {
		// do some cleanup
	}
}

This is the way we do it today:

public function deleteKey ($key) {
	if (isset($this->array[$key])) {
		unset($this->array[$key]);
		// do some cleanup
	}
}

But even now we can't be sure if the variable was unset, because the __unset() magic method may not have deleted it. With the patch __unset() can return a value (which is internally casted to bool) and inform the caller of unset() if the deletion was successful.

The main advantage is that you can do constructs like this: https://git.php.net/?p=php-src.git;a=blob;f=Zend/zend_object_handlers.c#l818 I am just asking myself: when you use it in the Engine, why don't you offer the possibility to PHP?

Execution example

$var = 1;
var_dump(unset($var)); // bool(true)
var_dump(unset($var)); // bool(false)
 
$var = 1;
var_dump(unset($var, $undefined)); // evaluates to unset($var) && unset($undefined) == bool(false) (like isset() works)
 
$dim[1] = "set";
var_dump(unset($dim[2])); // bool(false)
var_dump(unset($dim[1])); // bool(true)
 
class object {
	private $property;
 
	public function __unset($var) {
		if ($var == "prima")
			return false;
		if ($var == "secunda")
			return unset($this->property);
		if ($var == "tertia")
			return "some garbage";
	}
}
$class = new object;
$class->prop = 0;
var_dump(unset($class->prop)); // bool(true)
var_dump(unset($class->prima)); // bool(false)
var_dump(unset($class->secunda)); // bool(true)
var_dump(unset($class->tertia)); // bool(true)
var_dump(unset($class->quarta)); // bool(false) (a function execution without return returns NULL, which is, casted to boolean, false)

Use case

class User {
	private $data = [];
 
	// here a __set and a __get function for $data
 
	public function __unset ($key) {
		switch ($key) {
			case "id":
				return false;
			default:
				return unset($this->data[$key]);
	}
}
 
class UserManager {
	private $Users = [];
 
	public function __construct($data) {
		// some code to initialize the $this->Users array
	}
 
	public function reset($userId, $key) {
		if (!unset($this->Users[$userId]->$key)) {
			throw new Exception("You can't reset the `$key` of '{$this->Users[$userId]->name}'");
		}
	}
}
 
$manager = new UserManager($data);
 
$manager->reset($id, "id"); // now you have a meaningful debugging output through classes as current behaviour would be to do nothing - silently.

This genre of code could be also in Frameworks to indicate to a coder that the variable can be accessed, but not deleted.

Implementation Details

Every deleting function called by the VM_HANDLERs ZEND_UNSET_* now return int: SUCCESS or FAILURE. All the functions which didn't were previously void functions, so only adding a return value shouldn't affect any existing code (only exception: see one section below).

The zend_do_unset function was adapted to zend_do_isset_or_is_empty in behaviour. As it's mostly reusing existing code, it shouldn't be error-prone.

The rest was adding loads of “result = SUCCESS/FAILURE;” into the various void functions.

Backward Incompatible Changes

There may break some extensions which have their own unset_property and/or unset_dimension class-handler. This can easily be fixed by changing the return value from void to int and returning SUCCESS (or FAILURE).

Proposed PHP Version(s)

Should be implemented in the next 5.x (e.g. 5.5 if it's delayed due to ZO+ or 5.6).

Microbenchmark

time ./sapi/cli/php -r 'while($i++ < 1e7) { $a = true; unset($a); }'

Unpatched: average of 5x executed:
real	0m4.935s
user	0m4.925s
sys	0m0.008s

Patched: average of 5x executed:
real	0m4.945s
user	0m4.938s
sys	0m0.005s

This is an increase of 0.15%. This is 1 nanosecond per execution more than previously.

References

Changelog

  • Version 0.5: Initial RFC
  • Version 0.51: Added example for multiple unset()'s in one
  • Version 0.52: echo may be also considered as a function...
  • Version 0.53: Added microbenchmark
  • Version 0.6: Added example real world use case
  • Version 0.61: Little clarification about failures
rfc/unset_bool.txt · Last modified: 2018/06/18 10:14 by cmb