rfc:remove_object_auto_vivification

PHP RFC: Remove object auto-vivification

Introduction

When assigning a property to a falsy value, PHP will currently convert the falsy value into an stdClass object and emit a warning. This RFC proposes to remove this “auto-vivification” behavior, and make changes to object write semantics made possible by the removal of this functionality.

An example of the current behavior is shown in the following:

$val = null;
$val->foo = 42; // Warning: Creating default object from empty value
var_dump($val); // object(stdClass)#1 (1) { ["foo"]=> int(42) }
 
$val = "";
$val->bar = 42; // Warning: Creating default object from empty value
var_dump($val); // object(stdClass)#1 (1) { ["bar"]=> int(42) } 
 
$val = "foo";
$val->abc = 42; // Warning: Attempt to assign property 'abc' of non-object
var_dump($val); // string(3) "foo"

In the first two cases, the value is falsy and a property assignment results in the creation of an object. In the last case, the value is truthy and no implicit conversion into an object occurs.

The auto-vivification behavior places a somewhat subtle requirement on the interpretation of property assignments: Normally, when performing an $obj->prop = $val operation, this only modifies the object stored in $obj, but does not modify the storage location of $obj itself. After the operation, $obj will always point to the same object, even if that object has undergone internal state changes.

However, the existence of auto-vivification makes this not strictly true: If $obj is a falsy value, then it will be converted into an object, and that requires changing the storage location of $obj itself. This distinction doesn't matter in most cases, but can be observed when magic __get() (or ArrayAccess offsetGet) is used:

class Magic {
    public $data = [];
    public function __get($key) {
        return $this->data[$key];
    }
}
 
$magic = new Magic;
$magic->data["arrayOfObj"] = [new stdClass];
$magic->arrayOfObj[0]->prop = 123;
// Notice: Indirect modification of overloaded property Magic::$arrayOfObj has no effect
 
var_dump($magic);
// object(Magic)#1 (1) {
//   ["data"]=> array(1) {
//     ["arrayOfObj"]=> array(1) {
//       [0]=> object(stdClass)#2 (1) {
//         "prop"]=> int(123)
//       }
//     }
//   }
// }

In this example, the assignment to $magic->arrayOfObj[0]->prop generates an “indirect modification has no effect” notice, while the object it still successfully modified. The reason for this behavior is that the assignment needs to fetch $magic->arrayOfObj[0] for-write on the off-chance that a falsy value will have to be converted into an object.

Proposal

Remove object auto-vivification

Assigning a property to a falsy value will no longer result in an implicit creation of an object. Returning to the example from the introduction, the new behavior is consistent for the three cases:

$val = null;
$val->foo = 42; // Warning: Attempt to assign property 'foo' of non-object
var_dump($val); // NULL
 
$val = "";
$val->bar = 42; // Warning: Attempt to assign property 'bar' of non-object
var_dump($val); // string(0) ""
 
$val = "foo";
$val->abc = 42; // Warning: Attempt to assign property 'abc' of non-object
var_dump($val); // string(3) "foo"

Fetch LHS of property assignments for-read

When performing $a->b = $c, fetch $a for-read rather than for-write. This means that the magic __get() example above will no longer generate a notice.

The other side-effect of this change is that invalid accesses on the left hand side of the assignment will now be reported. Consider this example, where $abc is assumed to be an undefined variable:

$abc["foo"]->bar = 42;
// Before this RFC:
// Warning: Creating default object from empty value
 
// After this RFC:
// Notice: Undefined variable: abc
// Warning: Attempt to assign property 'bar' of non-object 

Previously this only generated the “creating default object” warning, because for-write fetches suppress most other types of diagnostics. With this RFC the fetch is performed for-read and the use of an undefined variable is reported properly.

SimpleXML

SimpleXML, through liberal application of overloaded object handlers, allows creating deeply nested structures using a single assignment:

$xml = new SimpleXMLElement('<collection></collection>');
$xml->movie[]->characters->character[0]->name = 'Tom Hanks';
echo $xml->asXml();

This snippet generates the following XML:

<?xml version="1.0"?>
<collection><movie><characters><character><name>Tom Hanks</name></character></characters></movie></collection>

This functionality is currently implemented under the assumption that everything is fetched for-write, while this RFC proposes to fetch the $xml->movie[]->characters->character[0] portion of the access for-read. This will result in a “Cannot use [] for reading” compile-time error.

However, even if the code is adjusted to use $xml->movie[0] rather than $xml->movie[] (which is at least legal as far as the compiler is concerned), restoring the previous behavior here will still be something of a challenge.

I'm unsure what to do about this as yet.

Backward Incompatible Changes

The removal of object auto-vivification is a backwards compatibility break, which is why this RFC targets PHP 8. All cases where the behavior will change already generate a warning in PHP 7.4.

The change to read/write semantics should be backwards compatible apart from additional warnings or notices being thrown. However, it may impact extensions that rely on the current behavior, such as the SimpleXML example given above. I'm not aware of other any specific other extension being impacted.

Vote

Vote might have to be split for just the auto-vivification change (which should be a no-brainer) and the write-semantics change (which has the unfortunate interaction with SimpleXML).

rfc/remove_object_auto_vivification.txt · Last modified: 2019/02/25 10:31 by nikic