• Save
Understanding PHP objects
Upcoming SlideShare
Loading in...5
×
 

Understanding PHP objects

on

  • 6,881 views

 

Statistics

Views

Total Views
6,881
Views on SlideShare
6,794
Embed Views
87

Actions

Likes
6
Downloads
0
Comments
0

2 Embeds 87

https://twitter.com 86
http://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Understanding PHP objects Understanding PHP objects Presentation Transcript

  • Understanding objects PHP objects internal design
  • Hello everybody  Julien PAULI  Programming in PHP since ~10y  PHP Internals reviewer since ~3y  PHP 5.5 and 5.6 Release Manager  Working at SensioLabs in Paris  http://www.phpinternalsbook.com  @julienpauli - github.com/jpauli - jpauli@php.net
  • What we'll cover together  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
  • Zvals
  • 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"
  • Zvals
  • zval management  Classic management example : zval *myval; ALLOC_INIT_ZVAL(myval); // malloc() ZVAL_STRINGL(myval, "foobar", sizeof("foobar")-1, 1); /* use myval */ zval_ptr_dtor(&myval);  Reference counting management example : Z_ADDREF_P(myval); Z_DELREF_P(myval); Z_SET_ISREF_P(myval); Z_UNSET_ISREF_P(myval);
  • zvals refcount  Every PHP variable is a zval : <?php $o = new MyClass;  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; // refcount = 1 $o2 = $o; // refcount = 2
  • zvals refcount  The zval is automatically freed when refcount reaches zero, and never before <?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  When a zval is freed, its content is freed if not shared elsewhere  In our case : an object
  • Statement  Objects are variables  Variables are zvals  Objects are not freed until their zval's refcount reaches zero
  • PHP objects
  • PHP Objects ?  Objects are zvals of type IS_OBJECT  The value field used is "obj" in the zval  zend_object_value type
  • 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
  • Showing the object handle $o = new MyClass; $a = $o; $b = $o; var_dump($a, $b, $o); object(MyClass)#1 (0) { } object(MyClass)#1 (0) { } object(MyClass)#1 (0) { }
  • Objects ARE NOT references
  • Objects are NOT references  Simple proof function foo($var) { $var = 42; } $o = new MyClass; foo($o); var_dump($o); object(MyClass)#1 (0) { }
  • 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" }
  • zvals vs object handles  This writes to the zval container $object : $object = 'overwritten';  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 : $object->var = 'changed';  All other zvals using this same object handle are affected, whatever their scope
  • 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 sym tables $o = new MyClass; $a = $o; $a->name = 'foo'; $a = 'string'; $o $a zval store zval1 Object#1 obj_handle"bar" name => => #1 object store Object#1 name => "foo" Object#1 zval2 $b = clone $a; $c = $b; $b->name = 'bar'; $b $c name => "bar" 'string' zval3 obj_handle => #2 Object#2 name => "bar"
  • zval duplication with objects  Even when you force PHP to duplicate a zval, if it represents an object, this latter won't be copied : $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 */  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
  • zval duplication and objects  Even if you force PHP to dup. a zval container, the object stored in it won't be dup. sym tables $o = new MyClass; $a = &$o; $a->name = 'foo'; $b = $a; $o $a zval store zval1 Object#1 obj_handle"bar" name => => #1 zval2 $b obj_handle => #1 object store Object#1 name => "foo"
  • 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
  • Garbage collector (GC)
  • Circular references GC
  • 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
  • Some circular references $a = new StdClass; $b = new StdClass; $a->b = $b; $b->a = $a; unset($a,$b); zval1 zval2 refcount = 1 obj_handle => #1 refcount = 1 obj_handle => #2
  • Some circular references $a = new StdClass; $b = new StdClass; $a->b = $b; $b->a = $a; unset($a,$b); zval1 zval2 refcount = 1 obj_handle => #1 refcount = 1 obj_handle => #2
  • 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
  • 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);
  • Diving into objects
  • zend_object type  Objects in PHP are zend_object  Objects live in a global "store"  They are indexed using there 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"
  • zend_class_entry  Represents a PHP class or an interface  By far the biggest structure !  This structure's been shrinked to fit the slide
  • Object memory consumption  Object declared attributes are stored once in the class structure at compile time  At the first time you try to read any declared property, PHP will duplicate all them from the class structure to the object structure  This effectively make those attributes owned by the object : a "private" copy is made, tied to the object  Conclusion : An object weigth is directly bound to its attributes weigth  As class informations are shared between objects
  • 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  A class consumes much more memory than an object  But the same class is shared between all its children objects
  • 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
  • Object handlers
  • Object handlers  Every operation on objects is done by handlers
  • 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)  ...
  • Object default handlers  PHP uses default handlers
  • Object default handler example  Default handlers implement default behavior we all are used to : static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC) { // ... if (UNEXPECTED(zend_hash_quick_find(&zobj->ce->function_table, lc_method_name, method_len+1, hash_value, (void **)&fbc) == FAILURE)) { if (zobj->ce->__call) { return zend_get_user_call_function(zobj->ce, method_name, method_len); } else { return NULL; } }
  • Overriding object handlers  You should know about "special behaving PHP objects" don't you ?
  • Overriding object handlers  You should know about "special behaving PHP objects" don't you ?     SimpleXmlElement PDOStatement DateTime ...  They all redefine default handlers
  • 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
  • PHP OOP gotchas
  • 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"
  • Destructor secrets  __destruct() is called when an object is destroyed  Destroyed != freeed  Reminder : An object is destroyed when no more zvals point to it $a = new SomeClass; // refcount = 1 /* calls __destruct() */ unset($a); // refcount reaches 0
  • 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 */
  • Destructors at shutdown  3-step shutdown 1 2 3
  • 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 class Foo { public function __destruct() { var_dump("Destroyed Foo"); } } class Bar { public function __destruct() { var_dump("Destroyed Bar"); } } 1 2 3 $a = new Foo, $b = new Bar; $a = new Bar, $b = new Foo; $a = new Bar, $b = new Foo; $c = $b; "Destroyed Bar" "Destroyed Foo" "Destroyed Foo" "Destroyed Bar" "Destroyed Bar" "Destroyed Foo"
  • 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"
  • Shutdown and destructors #3  If a destructor exit()s or die()s, the other destructors are not exectued  But the objects are "marked as destructed" class Foo { public function __destruct() { var_dump("Destroyed Foo"); die(); } } class Bar { public function __destruct() { var_dump("Destroyed Bar"); } } $a = new Foo; $a2 = $a; $b = new Bar; $b2 = $b; "Destroyed Foo"
  • __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; "alive" $foo = new Foo; $bar = new Bar; $bar->foo = $foo; $a = $bar; "destructed"
  • "Destroying" an object  When you destroy an object, PHP will immediatly free it $a = new SomeClass; unset($a);  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
  • __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
  • 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 !
  • function stack serialized 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'  serializing an Exception serializes its stack trace  Which itself could be not serializable ...
  • Class Early Binding class C extends B {} class B extends A {} class A {} Fatal error: Class 'B' not found class C extends A {} class A {} /* all right */  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
  • Thank you for listening