php language trivia

77
PHP Language Trivia Nikita Popov (nikic)

Upload: nikita-popov

Post on 22-Jan-2018

11.318 views

Category:

Technology


2 download

TRANSCRIPT

PHP Language TriviaNikita Popov

(nikic)

Object properties

Object propertiesName mangling

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)

}

Why name mangling?

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

Object can have multiple propertieswith same name

Name mangling ensuresunique property names

No reason to expose this internal detail …

No reason to expose this internal detail …

… but libraries rely on it nowto access private properties

Object propertiesInteger 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 = 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

Fixed in PHP 7.2!

Now integer keys are renormalizedon array->object and object->array casts

$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"

Object propertiesMemory usage

$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

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 => 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

Object propertiesMagic 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 is slow

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 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)

__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 ifmagic accessors used only for one property at a time

Object propertiesUnset properties

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()

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();/* ... */

}}

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();

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 */

};}

} Without static:• Closure references $this• $this->fn references Closure

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