2. Hello everybody
Julien PAULI
Programming in PHP since ~10y
PHP Internals reviewer
PHP 5.5 and 5.6 Release Manager or Co-RM
Working at SensioLabs in Paris
http://www.phpinternalsbook.com
@julienpauli - github.com/jpauli - jpauli@php.net
3. What we'll cover together
Covering PHP 5 only
Quick recall on zvals
Object structures internally
zend_object_value
zend_class_entry
zend_object_handlers
zend_object_store
PHP objects lifecycle
Creating and destroying an object
Memory management & garbage collection
5. Zvals
PHP variables can carry several types
PHP is not strongly typed
Type juggling
Internally, all variables are represented into a
container which can carry all supported PHP
types : "zval"
8. zvals refcount
Every PHP variable is a zval :
PHP does not duplicate memory when you
duplicate variables in the same scope
It plays with the refcount of the zval
refcount is how many variables point to the zval
<?php
$o = new MyClass;
<?php
$o = new MyClass; // refcount = 1
$o2 = $o; // refcount = 2
9. zvals refcount
The zval is automatically freed when refcount
reaches zero, and never before
When a zval is freed, its content is freed if not
shared elsewhere
In our case : an object
<?php
$o = new MyClass; // refcount = 1
$o2 = $o; // refcount = 2
unset($o); // refcount = 1, zval is not freed
unset($o2); // refcount = 0, zval is freed
10. Statement
Objects are variables
Variables are zvals
Objects are not freed until their zval's refcount
reaches zero
12. PHP Objects ?
Objects are zvals of type IS_OBJECT
The value field used is "obj" in the zval
zend_object_value type
13. PHP objects details
An object carries :
A handle
Some handlers
The handle is a unique integer designed to
fetch the "real" object from an internal store
16. Objects are NOT references
Simple proof
function foo($var) {
$var = 42;
}
$o = new MyClass;
foo($o);
var_dump($o);
object(MyClass)#1 (0) {
}
17. Objects borrow ref. behavior
Because each variable (zval) encapsulates the
same object handle
function foo($var) {
$var->name = 'foo';
}
$o = new MyClass;
$o->name = 'bar';
foo($o);
var_dump($o);
object(MyClass)#1 (0) {
public $name =>
string(3) "foo"
}
18. zvals vs object handles
This writes to the zval container $object :
This changes the zval value
Before it was an object, now it's a string
The object that was into it has never been changed here
This fetches the object using its handle, and
writes to that object :
All other zvals using this same object handle are
affected, whatever their scope
$object = 'overwritten';
$object->var = 'changed';
19. Creating a new object
The two only ways to create a new object in the
store are :
new keyword (unserialize() may use new as well)
clone keyword
$o = new MyClass;
$a = $o;
$a->name = 'foo';
$b = clone $a;
$c = $b;
$b->name = 'bar';
$a = 'string';
Object#1
name => "foo"
Object#1
name => "bar"
zval1
obj_handle => #1
Object#1
name => "bar"
zval2
'string'$b
$o
object storezval storesym tables
$a
$c zval3
obj_handle => #2
Object#2
name => "bar"
20. zval duplication with objects
Even when you force PHP to duplicate a zval, if it represents an
object, this latter won't be copied :
This is PHP5 behavior
The objects are not duplicated, weither you use PHP references or not
Zvals may get duplicated (if you abuse PHP references usage !)
Objects themselves also carry a refcount
$o = new MyClass;
$a = &$o; // take a reference
/* Force PHP to duplicate the zval */
$b = $a;
/* We all agree that here, modifying $a or $b or $o
will modify the *same* object */
21. zval duplication and objects
Even if you force PHP to dup. a zval container,
the object stored in it won't be dup.
$o = new MyClass;
$a = &$o;
$a->name = 'foo';
$b = $a;
Object#1
name => "foo"
Object#1
name => "bar"
zval1
obj_handle => #1
$b
$o
object storezval storesym tables
$a
zval2
obj_handle => #1
22. First step conclusion
Having lots of variables pointing to the same
object is not bad for memory usage
"clone", "new" and "unserialize" are the only
ways to create an object in the store
Thus to consume more memory
To free (destroy) an object from memory, one
must destroy all zvals in all scopes pointing to
it
Keeping track of this can be hard
use xdebug, master your code, remember
25. Statements :
PHP garbage collector is NOT object specific
It is zval based (any PHP type so)
PHP GC is a circular references GC
It's only goal is to free unfetchable circular
references from userland
PHP has always freed unused zvals of which
refcount reached zero
GC has nothing to do with this behavior
PHP Circular references GC appeared in 5.3
28. Some circ.ref. cleaned by GC
$a = new StdClass;
$b = new StdClass;
$a->b = $b;
$b->a = $a;
unset($a,$b);
echo gc_collect_cycles(); // 2
29. Objects circ.ref are common
It is very easy to create a
circ.ref leak with objects
This will have an impact if :
Your objects are "heavy"
You run long living process
Ex are some SF2 commands
... with doctrine 2
class A
{
private $b;
function __construct() {
$this->b = new B($this);
}
}
class B
{
private $a;
function __construct(A $a) {
$this->a = $a;
}
}
$a = new A;
unset($a);
31. zend_object type
Objects in PHP are zend_object
Objects live in a global "store"
They are indexed using their unique handle
As we've seen, PHP does all it can do not to
duplicate the object into the store
Only way to duplicate : "clone"
32. zend_class_entry
Represents a PHP class
or an interface
By far the biggest
structure !
This structure's been
shrinked to fit the slide
The structure size is
~500 bytes
33. Object memory consumption
Object declared attributes are stored once in the class
structure at compile time
When you create an object (new), PHP will duplicate
the zval attributes from the class to the object
The object now effectively owns its own copy of attributes
zvals pointers are copied, not zval values. COW still effective
Conclusion : An object weight is directly bound to its
attributes weight
As class informations are shared between objects
34. Object memory consumption
Every declared property is stored in the class structure
They are stored with info structures
Those also consume memory
The class also embeds
Its own static properties
its own constants
an array of interfaces it implements
an array of traits it uses
more info
class consumes much more memory than an object
But the same class is shared between all its children objects
35. Lifetimes
Objects start living when you create them and
stop living when they are destroyed
when the last zval pointing to the object is destroyed
Classes start living when PHP starts parsing a
T_CLASS (class {) token
Classes stop living when PHP shuts down (end
of request)
Every class info, e.g its static members, have a
lifetime of the class itself
unset(MyClass::$variable);
Fatal error: Attempt to unset static property
38. Object handlers
Every single tiny operation on objects is done
using a handler (a redefinable function)
For example
calling a method on an object
Fetching an object property
But also :
Casting an object (zend_object_cast_t)
Comparing an object with something (zend_object_compare_t)
...
42. Overriding object handlers
You should know about "special behaving PHP
objects" don't you ?
SimpleXmlElement
PDOStatement
DateTime
...
They all redefine default handlers
43. And from PHP land ?
You may overwrite some handlers from PHP
Land
ArrayAccess
Serializable
Countable
Designing a PHP extension, you may overwrite
any handler you want
That's great
Customize PHP object behavior
45. construct. params strangeness
Please, explain that behavior :
new StdClass($a=5);
var_dump($a);
PHP Notice : undefined variable $a
new DateTime($a='now');
var_dump($a);
string(3) "now"
46. Destructor secrets
__destruct() is called when an object is
destroyed
Destroyed == freeed (often)
Reminder : An object is destroyed when no
more zvals point to it
$a = new SomeClass; // refcount = 1
/* calls __destruct() */
unset($a); // refcount reaches 0
47. Destructor secrets
Let's now see what happens at shutdown
When you don't destruct your objects yourself
That's bad, you'll see
When you leave PHP's shutdown clean your objects
Yes, that's bad
$a = new SomeClass; // refcount = 1
/* Shutdown sequence */
49. Shutdown and destructors #1
PHP will loop backward the global symbol table
and destroy objects which zval's refcount = 1
Last object created = first cleared
1 2 3
class Foo { public function __destruct() { var_dump("Destroyed Foo"); } }
class Bar { public function __destruct() { var_dump("Destroyed Bar"); } }
$a = new Foo,
$b = new Bar;
$a = new Bar,
$b = new Foo;
"Destroyed Bar"
"Destroyed Foo"
"Destroyed Foo"
"Destroyed Bar"
$a = new Bar,
$b = new Foo;
$c = $b;
"Destroyed Bar"
"Destroyed Foo"
50. Shutdown and destructors #2
Then (for objects where refcount > 1) PHP will
loop forward the object store and destroy all
objects
In order of their creation so (forward)
$a = new Foo;
$a2 = $a;
$b = new Bar;
$b2 = $b;
"Destroyed Foo"
"Destroyed Bar"
51. Shutdown and destructors #3
If a destructor exit()s or die()s, the other
destructors are not executed
But the objects are "marked as destructed"
$a = new Foo;
$a2 = $a;
$b = new Bar;
$b2 = $b;
"Destroyed Foo"
class Foo { public function __destruct() { var_dump("Destroyed Foo"); die(); } }
class Bar { public function __destruct() { var_dump("Destroyed Bar"); } }
52. __destruct() weirdness
So, knowing that, we can meet weird behaviors
class Foo
{
public $state = 'alive';
function __destruct() {
$this->state = 'destructed';
}
}
class Bar
{
public $foo;
function __destruct() {
var_dump($this->foo->state);
}
}
$foo = new Foo;
$bar = new Bar;
$bar->foo = $foo;
$foo = new Foo;
$bar = new Bar;
$bar->foo = $foo;
$a = $bar;
"alive"
"destructed"
53. "Destroying" an object
When you destroy an object, PHP will
immediatly free it
When PHP destroys an object during shutdown
sequence, it will only call __destruct() and will
not free the object immediately
That's why you can reuse "destroyed" objects
See preceding slide
The objects will be freed when the engine will
shutdown
$a = new SomeClass;
unset($a);
54. __destruct() in shutdown
PHP's shutdown sequence is clear
http://lxr.php.net/xref/PHP_5_4/main/main.c#1728
1. call shutdown functions
2. call object destructors
3. end output buffering
4. shutdown all extensions
5. destroy superglobals
6. shutdown scanner/executor/compiler
Frees object storage
Every object handling done after phase #2 can
lead to weird behavior and/or crash PHP
55. A great conclusion of this
Don't rely on PHP's shutdown behavior
It has changed throughout PHP versions
It will change in the future
It can make PHP hang or crash in worst cases
Just destroy and free the resources yourself !
56. function stack serialized
serializing an Exception serializes its stack trace
Which itself could be not serializable ...
function foo(SimpleXMlElement $x, $a)
{
echo serialize(new Exception());
}
foo(new SimpleXmlElement('<a />'), 'a');
Fatal error: Uncaught exception 'Exception' with message
'Serialization of 'SimpleXMLElement' is not allowed'
57. Class Early Binding
Early binding = compiler declares solo classes
Inheritence is honnored at runtime
Conditionnal declarations are honnored at runtime
Declare your classes in the "right" order
Use runtime autoloader
class C extends B {}
class B extends A {}
class A {}
Fatal error: Class 'B' not found
class C extends A {}
class A {}
/* all right */