class Test {public $pub = 1;protected $prot = 2;private $priv = 3;
}
$obj = new Test;$arr = (array) $obj;var_dump($arr);
class Test {public $pub = 1;protected $prot = 2;private $priv = 3;
}
$obj = new Test;$arr = (array) $obj;var_dump($arr);
array(3) {["pub"]=>int(1)["*prot"]=>int(2)["Testpriv"]=>int(3)
}
class Test {public $pub = 1;protected $prot = 2;private $priv = 3;
}
$obj = new Test;$arr = (array) $obj;var_dump($arr);
var_dump($arr["*prot"]);// Notice: Undefined index: *prot
array(3) {["pub"]=>int(1)["*prot"]=>int(2)["Testpriv"]=>int(3)
}
class Test {public $pub = 1;protected $prot = 2;private $priv = 3;
}
$obj = new Test;$arr = (array) $obj;var_dump($arr);
var_dump($arr["\0*\0prot"]);// int(2)
array(3) {["pub"]=>int(1)["\0*\0prot"]=>int(2)["\0Test\0priv"]=>int(3)
}
class A {private $prop = 'A';public function getPropA() { return $this->prop; }
}class B extends A {
protected $prop = 'B';public function getPropB() { return $this->prop; }
}class C extends B {
public $prop = 'C';public function getPropC() { return $this->prop; }
}
$obj = new C;var_dump($obj->getPropA()); // string(1) "A"var_dump($obj->getPropB()); // string(1) "C"var_dump($obj->getPropC()); // string(1) "C"
class A {private $prop = 'A';public function getPropA() { return $this->prop; }
}class B extends A {
protected $prop = 'B';public function getPropB() { return $this->prop; }
}class C extends B {
public $prop = 'C';public function getPropC() { return $this->prop; }
}
$obj = new C;var_dump($obj->getPropA()); // string(1) "A"var_dump($obj->getPropB()); // string(1) "C"var_dump($obj->getPropC()); // string(1) "C"
Refer to same property
class A {private $prop = 'A';public function getPropA() { return $this->prop; }
}class B extends A {
protected $prop = 'B';public function getPropB() { return $this->prop; }
}class C extends B {
public $prop = 'C';public function getPropC() { return $this->prop; }
}
$obj = new C;var_dump($obj->getPropA()); // string(1) "A"var_dump($obj->getPropB()); // string(1) "C"var_dump($obj->getPropC()); // string(1) "C"
Refer to same property
Private property is independent
class A {private $prop = 'A';public function getPropA() { return $this->prop; }
}class B extends A {
protected $prop = 'B';public function getPropB() { return $this->prop; }
}class C extends B {
public $prop = 'C';public function getPropC() { return $this->prop; }
}
$obj = new C;var_dump((array) $obj);
Refer to same property
Private property is independent
class A {private $prop = 'A';public function getPropA() { return $this->prop; }
}class B extends A {
protected $prop = 'B';public function getPropB() { return $this->prop; }
}class C extends B {
public $prop = 'C';public function getPropC() { return $this->prop; }
}
$obj = new C;var_dump((array) $obj);
array(2) {["\0A\0prop"]=> string(1) "A"["prop"]=> string(1) "C"
}
Refer to same property
Private property is independent
No reason to expose this internal detail …
… but libraries rely on it nowto access private properties
$array = [];$array[123] = "foo";$array["123"] = "bar";var_dump($array);
array(1) {[123]=>string(3) "bar"
}
$array = [];$array[123] = "foo";$array["123"] = "bar";var_dump($array);
array(1) {[123]=>string(3) "bar"
}
$object = new stdClass;$object->{123} = "foo";$object->{"123"} = "bar";var_dump($object);
object(stdClass)#1 (1) {["123"]=>string(3) "bar"
}
$array = [];$array[123] = "foo";$array["123"] = "bar";var_dump($array);
array(1) {[123]=>string(3) "bar"
}
$object = new stdClass;$object->{123} = "foo";$object->{"123"} = "bar";var_dump($object);
object(stdClass)#1 (1) {["123"]=>string(3) "bar"
}
Normalize to int Normalize to string
$array = [];$array[123] = "foo";$array["123"] = "bar";var_dump($array);
array(1) {[123]=>string(3) "bar"
}
$object = new stdClass;$object->{123} = "foo";$object->{"123"} = "bar";var_dump($object);
object(stdClass)#1 (1) {["123"]=>string(3) "bar"
}
Normalize to int Normalize to string
What happens if we mix both?
$array = [123 => "foo"];$object = (object) $array;
var_dump($object->{123});// Notice: Undefined property: stdClass::$123var_dump($object->{"123"});// Notice: Undefined property: stdClass::$123
$array = [123 => "foo"];$object = (object) $array;
var_dump($object->{123});// Notice: Undefined property: stdClass::$123var_dump($object->{"123"});// Notice: Undefined property: stdClass::$123
var_dump($object);
object(stdClass)#1 (1) {[123]=>string(3) "foo"
}
$array = [123 => "foo"];$object = (object) $array;
var_dump($object->{123});// Notice: Undefined property: stdClass::$123var_dump($object->{"123"});// Notice: Undefined property: stdClass::$123
var_dump($object);
object(stdClass)#1 (1) {[123]=>string(3) "foo"
}
Unnormalized integer property name
$object = new stdClass;$object->{123} = "foo";$array = (array) $object;
var_dump($array[123]);// Notice: Undefined offset: 123var_dump($array["123"]);// Notice: Undefined offset: 123
$object = new stdClass;$object->{123} = "foo";$array = (array) $object;
var_dump($array[123]);// Notice: Undefined offset: 123var_dump($array["123"]);// Notice: Undefined offset: 123
var_dump($array);
array(1) {["123"]=>string(3) "foo"
}Unnormalized integral string key
$array = [123 => "foo"];$object = (object) $array;var_dump($object->{123});
string(3) "foo"
$object = new stdClass;$object->{123} = "foo";$array = (array) $object;var_dump($array[123]);
string(3) "foo"
$array = ["key1" => 1,"key2" => 2,// ...
];
class Value {public $key1;public $key2;
}
$object = new Value;$object->key1 = 1;$object->key2 = 2;
vs.
0
100
200
300
400
500
600
700
800
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Mem
ory
usa
ge (
byt
es)
Number of properties/keys
Array Array (real) Object Object (real)
0
1
2
3
4
5
6
7
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Arr
ay s
ize
/ o
bje
ct s
ize
Number of properties/keys
Ratio Ratio (real)
Optimized for different usecases
Objects: Good for fixed set of keysArrays: Good for dynamic set of keys
Class entry
Properties array
Property 0
Property 1
…
Object
Contains [property name => property offset] map
[property name => property value] map,used if there are dynamic properties
Class entry
Properties array
Property 0
Property 1
…
Object
Contains [property name => property offset] map
[property name => property value] map,used if there are dynamic properties
Arrays:• Store keys (and hashes) explicitly• Always have power of two size (8, 16, …)
for faster insertions
class Value {public $x;
}
$obj = new Value;// $obj size: 56 bytes
foreach ($obj as $k => $v) { }// $obj size: 432 bytes
class Value {public $x;
}
$obj = new Value;// $obj size: 56 bytes
foreach ($obj as $k => $v) { }// $obj size: 432 bytes
Forces creation of properties array
class Value {public $x;
}
$obj = new Value;// $obj size: 56 bytes
foreach ($obj as $k => $v) { }// $obj size: 432 bytes
Forces creation of properties array… no way to get rid of it afterwards
// PhpParser node iteration$names = $node->getSubNodeNames();foreach ($names as $name) {
$value = $node->$name;}
// PhpParser node iteration$names = $node->getSubNodeNames();foreach ($names as $name) {
$value = $node->$name;}
Dynamic lookup is slow, but thisavoids large memory usage increase
Direct property access baseline
getProperty() method 2.2x slower
__get() magic 6.0x slower
Userland internal userland is slow
class Test {public function __get($name) {
return $this->$name;}
} Does not recurse into __get()Will access property directly
class Test {public function __get($name) {
return $this->$name;}
} Does not recurse into __get()Will access property directly
Recursion guards are property name + accessor type specific
class Test {public function __get($name) {
return $this->$name;}
} Does not recurse into __get()Will access property directly
Recursion guards are property name + accessor type specific
In __get("foo"):• $this->foo will access property• $this->bar will call __get("bar")• $this->foo = 42 will call __set("foo", 42)
["foo" => 0,"bar" => 0,
]
Recursion guards:
Never cleaned up
PHP 7.1: Recursion guard array not used ifmagic accessors used only for one property at a time
class Test {public $prop;
}
$obj = new Test;unset($obj->prop);var_dump($obj->prop);// Notice: Undefined property: Test::$prop
class Test {public $prop;
}
$obj = new Test;unset($obj->prop);var_dump($obj->prop);// Notice: Undefined property: Test::$prop
Once unset, __get() will be called on access-> Lazy initialization
class Test {public $prop;public function __construct() {
unset($this->prop);}public function __get($name) {
echo "__get($name)\n";$this->$name = "init";return $this->$name;
}}
$obj = new Test;var_dump($obj->prop);var_dump($obj->prop);
class Test {public $prop;public function __construct() {
unset($this->prop);}public function __get($name) {
echo "__get($name)\n";$this->$name = "init";return $this->$name;
}}
$obj = new Test;var_dump($obj->prop);var_dump($obj->prop);
Calls __get()
Does not call __get()
class A {public function method() {
/* ... */}
}class B extends A {
public function method() {parent::method();/* ... */
}}
class A {public function method() {
/* ... */}
}class B extends A {
public function method() {A::method();/* ... */
}}
class A {public function method() {
/* ... */}
}class B extends A {
public function method() {A::method();/* ... */
}}
Scoped instance call:Call A::method() with current $this
class A {public function method() { /* ... */ }
}class B extends A {
public function method() { /* ... */ }}class C extends B {
public function method() {A::method();/* ... */
}}
Can also call grandparent method
class A {public function method() {
echo 'A::method with $this=' . get_class($this) . "\n";}
}class B /* does not extend A */ {
public function method() {A::method();
}}
(new B)->method();
class A {public function method() {
echo 'A::method with $this=' . get_class($this) . "\n";}
}class B /* does not extend A */ {
public function method() {A::method();
}}
(new B)->method();// PHP 5: A::method with $this=B (+ deprecation)
class A {public function method() {
echo 'A::method with $this=' . get_class($this) . "\n";}
}class B /* does not extend A */ {
public function method() {A::method();
}}
(new B)->method();// PHP 5: A::method with $this=B (+ deprecation)// PHP 7.0: Undefined variable: this// PHP 7.1: Error: Using $this when not in object context
class Test {public function __call($name, $args) {
echo "__call($name)\n";}public static function __callStatic($name, $args) {
echo "__callStatic($name)\n";}public function doCall() {
Test::foobar();}
}
Test::foobar();(new Test)->doCall();
class Test {public function __call($name, $args) {
echo "__call($name)\n";}public static function __callStatic($name, $args) {
echo "__callStatic($name)\n";}public function doCall() {
Test::foobar();}
}
Test::foobar(); // __callStatic(foobar)(new Test)->doCall();
class Test {public function __call($name, $args) {
echo "__call($name)\n";}public static function __callStatic($name, $args) {
echo "__callStatic($name)\n";}public function doCall() {
Test::foobar(); // __call(foobar)}
}
Test::foobar(); // __callStatic(foobar)(new Test)->doCall();
class Test {public function __construct() {
$this->fn = function() {/* $this can be used here */
};}
}
class Test {public function __construct() {
$this->fn = static function() {/* $this CANNOT be used here */
};}
}
class Test {public function __construct() {
$this->fn = static function() {/* $this CANNOT be used here */
};}
} Without static:• Closure references $this• $this->fn references Closure