Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
PHP Language Trivia
Nikita Popov
(nikic)
Object properties
Object properties
Name mangling
class Test {
public $pub = 1;
protected $prot = 2;
private $priv = 3;
}
$obj = new Test;
$arr = (array) $obj;
var_dump($ar...
class Test {
public $pub = 1;
protected $prot = 2;
private $priv = 3;
}
$obj = new Test;
$arr = (array) $obj;
var_dump($ar...
class Test {
public $pub = 1;
protected $prot = 2;
private $priv = 3;
}
$obj = new Test;
$arr = (array) $obj;
var_dump($ar...
class Test {
public $pub = 1;
protected $prot = 2;
private $priv = 3;
}
$obj = new Test;
$arr = (array) $obj;
var_dump($ar...
Why name mangling?
class A {
private $prop = 'A';
public function getPropA() { return $this->prop; }
}
class B extends A {
protected $prop = ...
class A {
private $prop = 'A';
public function getPropA() { return $this->prop; }
}
class B extends A {
protected $prop = ...
class A {
private $prop = 'A';
public function getPropA() { return $this->prop; }
}
class B extends A {
protected $prop = ...
class A {
private $prop = 'A';
public function getPropA() { return $this->prop; }
}
class B extends A {
protected $prop = ...
class A {
private $prop = 'A';
public function getPropA() { return $this->prop; }
}
class B extends A {
protected $prop = ...
Object can have multiple properties
with same name
Name mangling ensures
unique property names
No reason to expose this internal detail …
No reason to expose this internal detail …
… but libraries rely on it now
to access private properties
Object properties
Integer property names
$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 = ...
$array = [];
$array[123] = "foo";
$array["123"] = "bar";
var_dump($array);
array(1) {
[123]=>
string(3) "bar"
}
$object = ...
$array = [];
$array[123] = "foo";
$array["123"] = "bar";
var_dump($array);
array(1) {
[123]=>
string(3) "bar"
}
$object = ...
$array = [123 => "foo"];
$object = (object) $array;
var_dump($object->{123});
// Notice: Undefined property: stdClass::$12...
$array = [123 => "foo"];
$object = (object) $array;
var_dump($object->{123});
// Notice: Undefined property: stdClass::$12...
$array = [123 => "foo"];
$object = (object) $array;
var_dump($object->{123});
// Notice: Undefined property: stdClass::$12...
$object = new stdClass;
$object->{123} = "foo";
$array = (array) $object;
var_dump($array[123]);
// Notice: Undefined offs...
$object = new stdClass;
$object->{123} = "foo";
$array = (array) $object;
var_dump($array[123]);
// Notice: Undefined offs...
Fixed in PHP 7.2!
Now integer keys are renormalized
on array->object and object->array casts
$array = [123 => "foo"];
$object = (object) $array;
var_dump($object->{123});
string(3) "foo"
$object = new stdClass;
$obj...
Object properties
Memory usage
$array = [
"key1" => 1,
"key2" => 2,
// ...
];
class Value {
public $key1;
public $key2;
}
$object = new Value;
$object->k...
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
Memoryusage(bytes)
Number of properties/keys...
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
Arraysize/objectsize
Number of properties/keys
Ratio Ratio (re...
Optimized for different usecases
Objects: Good for fixed set of keys
Arrays: Good for dynamic set of keys
Class entry
Property 0
Property 1
…
Object
Class entry
Property 0
Property 1
…
Object
Contains [property name => property offset] map
Class entry
Properties array
Property 0
Property 1
…
Object
Contains [property name => property offset] map
[property name...
Class entry
Properties array
Property 0
Property 1
…
Object
Contains [property name => property offset] map
[property name...
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...
class Value {
public $x;
}
$obj = new Value;
// $obj size: 56 bytes
foreach ($obj as $k => $v) { }
// $obj size: 432 bytes...
// 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;
}
Dynami...
Object properties
Magic get & set
Direct property access baseline
getProperty() method 2.2x slower
__get() magic 6.0x slower
Direct property access baseline
getProperty() method 2.2x slower
__get() magic 6.0x slower
Userland  internal  userland ...
class Test {
public function __get($name) {
return $this->$name;
}
}
class Test {
public function __get($name) {
return $this->$name;
}
} Does not recurse into __get()
Will access property di...
class Test {
public function __get($name) {
return $this->$name;
}
} Does not recurse into __get()
Will access property di...
class Test {
public function __get($name) {
return $this->$name;
}
} Does not recurse into __get()
Will access property di...
__get("foo")
__get("bar")
__set("bar", 42)
Recursion guards:
[
"foo" => GET,
"bar" => GET|SET,
]
__get("foo")
__get("bar")
[
"foo" => GET,
"bar" => GET,
]
Recursion guards:
__get("foo")
[
"foo" => GET,
"bar" => 0,
]
Recursion guards:
[
"foo" => 0,
"bar" => 0,
]
Recursion guards:
[
"foo" => 0,
"bar" => 0,
]
Recursion guards:
Never cleaned up
[
"foo" => 0,
"bar" => 0,
]
Recursion guards:
Never cleaned up
PHP 7.1: Recursion guard array not used if
magic accessors ...
Object properties
Unset properties
class Test {
public $prop;
}
$obj = new Test;
unset($obj->prop);
var_dump($obj->prop);
// Notice: Undefined property: Test...
class Test {
public $prop;
}
$obj = new Test;
unset($obj->prop);
var_dump($obj->prop);
// Notice: Undefined property: Test...
class Test {
public $prop;
public function __construct() {
unset($this->prop);
}
public function __get($name) {
echo "__ge...
class Test {
public $prop;
public function __construct() {
unset($this->prop);
}
public function __get($name) {
echo "__ge...
Scoped calls
Foo::bar()
Static method call … or is it?
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();
/* ... */
}...
class A {
public function method() { /* ... */ }
}
class B extends A {
public function method() { /* ... */ }
}
class C ex...
class A {
public function method() {
echo 'A::method with $this=' . get_class($this) . "n";
}
}
class B /* does not extend...
class A {
public function method() {
echo 'A::method with $this=' . get_class($this) . "n";
}
}
class B /* does not extend...
class A {
public function method() {
echo 'A::method with $this=' . get_class($this) . "n";
}
}
class B /* does not extend...
class Test {
public function __call($name, $args) {
echo "__call($name)n";
}
public static function __callStatic($name, $a...
class Test {
public function __call($name, $args) {
echo "__call($name)n";
}
public static function __callStatic($name, $a...
class Test {
public function __call($name, $args) {
echo "__call($name)n";
}
public static function __callStatic($name, $a...
Static Closures
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 */
};
}
} Withou...
class Test {
public function __construct() {
$this->fn = static function() {
/* $this CANNOT be used here */
};
}
} Withou...
Upcoming SlideShare
Loading in …5
×

PHP Language Trivia

13,428 views

Published on

Some tidbits about weird behavior and/or performance characteristics in PHP, in particular relating to object properties.

Published in: Technology
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

PHP Language Trivia

  1. 1. PHP Language Trivia Nikita Popov (nikic)
  2. 2. Object properties
  3. 3. Object properties Name mangling
  4. 4. class Test { public $pub = 1; protected $prot = 2; private $priv = 3; } $obj = new Test; $arr = (array) $obj; var_dump($arr);
  5. 5. 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) }
  6. 6. 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) }
  7. 7. 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) ["0Test0priv"]=> int(3) }
  8. 8. Why name mangling?
  9. 9. 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"
  10. 10. 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
  11. 11. 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
  12. 12. 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
  13. 13. 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) { ["0A0prop"]=> string(1) "A" ["prop"]=> string(1) "C" } Refer to same property Private property is independent
  14. 14. Object can have multiple properties with same name Name mangling ensures unique property names
  15. 15. No reason to expose this internal detail …
  16. 16. No reason to expose this internal detail … … but libraries rely on it now to access private properties
  17. 17. Object properties Integer property names
  18. 18. $array = []; $array[123] = "foo"; $array["123"] = "bar"; var_dump($array); array(1) { [123]=> string(3) "bar" }
  19. 19. $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" }
  20. 20. $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
  21. 21. $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?
  22. 22. $array = [123 => "foo"]; $object = (object) $array; var_dump($object->{123}); // Notice: Undefined property: stdClass::$123 var_dump($object->{"123"}); // Notice: Undefined property: stdClass::$123
  23. 23. $array = [123 => "foo"]; $object = (object) $array; var_dump($object->{123}); // Notice: Undefined property: stdClass::$123 var_dump($object->{"123"}); // Notice: Undefined property: stdClass::$123 var_dump($object); object(stdClass)#1 (1) { [123]=> string(3) "foo" }
  24. 24. $array = [123 => "foo"]; $object = (object) $array; var_dump($object->{123}); // Notice: Undefined property: stdClass::$123 var_dump($object->{"123"}); // Notice: Undefined property: stdClass::$123 var_dump($object); object(stdClass)#1 (1) { [123]=> string(3) "foo" } Unnormalized integer property name
  25. 25. $object = new stdClass; $object->{123} = "foo"; $array = (array) $object; var_dump($array[123]); // Notice: Undefined offset: 123 var_dump($array["123"]); // Notice: Undefined offset: 123
  26. 26. $object = new stdClass; $object->{123} = "foo"; $array = (array) $object; var_dump($array[123]); // Notice: Undefined offset: 123 var_dump($array["123"]); // Notice: Undefined offset: 123 var_dump($array); array(1) { ["123"]=> string(3) "foo" } Unnormalized integral string key
  27. 27. Fixed in PHP 7.2! Now integer keys are renormalized on array->object and object->array casts
  28. 28. $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"
  29. 29. Object properties Memory usage
  30. 30. $array = [ "key1" => 1, "key2" => 2, // ... ]; class Value { public $key1; public $key2; } $object = new Value; $object->key1 = 1; $object->key2 = 2; vs.
  31. 31. 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 Memoryusage(bytes) Number of properties/keys Array Array (real) Object Object (real)
  32. 32. 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 Arraysize/objectsize Number of properties/keys Ratio Ratio (real)
  33. 33. Optimized for different usecases Objects: Good for fixed set of keys Arrays: Good for dynamic set of keys
  34. 34. Class entry Property 0 Property 1 … Object
  35. 35. Class entry Property 0 Property 1 … Object Contains [property name => property offset] map
  36. 36. 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
  37. 37. 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
  38. 38. class Value { public $x; } $obj = new Value; // $obj size: 56 bytes foreach ($obj as $k => $v) { } // $obj size: 432 bytes
  39. 39. 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
  40. 40. 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
  41. 41. // PhpParser node iteration $names = $node->getSubNodeNames(); foreach ($names as $name) { $value = $node->$name; }
  42. 42. // PhpParser node iteration $names = $node->getSubNodeNames(); foreach ($names as $name) { $value = $node->$name; } Dynamic lookup is slow, but this avoids large memory usage increase
  43. 43. Object properties Magic get & set
  44. 44. Direct property access baseline getProperty() method 2.2x slower __get() magic 6.0x slower
  45. 45. Direct property access baseline getProperty() method 2.2x slower __get() magic 6.0x slower Userland  internal  userland is slow
  46. 46. class Test { public function __get($name) { return $this->$name; } }
  47. 47. class Test { public function __get($name) { return $this->$name; } } Does not recurse into __get() Will access property directly
  48. 48. 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
  49. 49. 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)
  50. 50. __get("foo") __get("bar") __set("bar", 42) Recursion guards: [ "foo" => GET, "bar" => GET|SET, ]
  51. 51. __get("foo") __get("bar") [ "foo" => GET, "bar" => GET, ] Recursion guards:
  52. 52. __get("foo") [ "foo" => GET, "bar" => 0, ] Recursion guards:
  53. 53. [ "foo" => 0, "bar" => 0, ] Recursion guards:
  54. 54. [ "foo" => 0, "bar" => 0, ] Recursion guards: Never cleaned up
  55. 55. [ "foo" => 0, "bar" => 0, ] Recursion guards: Never cleaned up PHP 7.1: Recursion guard array not used if magic accessors used only for one property at a time
  56. 56. Object properties Unset properties
  57. 57. class Test { public $prop; } $obj = new Test; unset($obj->prop); var_dump($obj->prop); // Notice: Undefined property: Test::$prop
  58. 58. 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
  59. 59. 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);
  60. 60. 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()
  61. 61. Scoped calls
  62. 62. Foo::bar() Static method call … or is it?
  63. 63. class A { public function method() { /* ... */ } } class B extends A { public function method() { parent::method(); /* ... */ } }
  64. 64. class A { public function method() { /* ... */ } } class B extends A { public function method() { A::method(); /* ... */ } }
  65. 65. class A { public function method() { /* ... */ } } class B extends A { public function method() { A::method(); /* ... */ } } Scoped instance call: Call A::method() with current $this
  66. 66. 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
  67. 67. 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();
  68. 68. 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)
  69. 69. 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
  70. 70. 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();
  71. 71. 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();
  72. 72. 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();
  73. 73. Static Closures
  74. 74. class Test { public function __construct() { $this->fn = function() { /* $this can be used here */ }; } }
  75. 75. class Test { public function __construct() { $this->fn = static function() { /* $this CANNOT be used here */ }; } }
  76. 76. class Test { public function __construct() { $this->fn = static function() { /* $this CANNOT be used here */ }; } } Without static: • Closure references $this • $this->fn references Closure
  77. 77. class Test { public function __construct() { $this->fn = static function() { /* $this CANNOT be used here */ }; } } Without static: • Closure references $this • $this->fn references Closure  Cycle causes delayed GC

×