Está en la página 1de 35

Mtodos mgicos

Los nombres de mtodo __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() y __clone() son mgicos en las clases PHP. No se puede tener mtodos con estos nombres en cualquiera de las clases a menos que desee la funcionalidad mgica asociada a estos. Precaucin PHP se reserva todos los nombres de los mtodos que comienzan con __ como mgicos. Se recomienda que no utilice los nombres de mtodos con __ en PHP a menos que desee alguna funcionalidad mgica documentada.

__sleep() y __wakeup()
serialize() comprueba si la clase tiene un mtodo con el nombre mgico __sleep(). Si es as, el mtodo se ejecuta antes de cualquier serializacin. Se puede limpiar el objeto y se supone que devuelve un array con los nombres de todas las variables de el objeto que se va a serializar. Si el mtodo no devuelve nada, entonces NULL es serializado y un error E_NOTICE es emitido. Nota: No es posible para __sleep() devolver nombres de propiedades privadas en las clases padres. Hacer esto resultara un nivel de error E_NOTICE. En su lugar, puede utilizar la interfaz Serializable. El uso para el que est destinado __sleep consiste en confirmar datos pendientes o realizar tareas similares de limpieza. Adems, el mtodo es til si tiene objetos muy grandes que no necesitan guardarse por completo. Por el contrario, unserialize() comprueba la presencia de un mtodo con el nombre mgico __wakeup(). Si est presente, este mtodo puede reconstruir cualquier recurso que el objeto pueda tener. El uso para el que est destinado __wakeup() es restablecer las conexiones de base de datos que se puedan haber perdido durante la serializacin y realizar otras tareas de reinicializacin. Ejemplo #1 Sleep y wakeup <?php class Connection { protected $link; private $server, $username, $password, $db; public function __construct($server, $username, $password, $db) { $this->server = $server; $this->username = $username; $this->password = $password; $this->db = $db; $this->connect(); }

private function connect() { $this->link = mysql_connect($this->server, $this->username, $this>password); mysql_select_db($this->db, $this->link); } public function __sleep() { return array('server', 'username', 'password', 'db'); } public function __wakeup() { $this->connect(); } } ?>

__toString()
El mtodo __toString() permite a una clase decidir cmo comportarse cuando se le trata como una string. Por ejemplo, lo que echo $obj; mostrara. Este mtodo debe devolver un string, si no se emitir un nivel de error fatal E_RECOVERABLE_ERROR. Ejemplo #2 Ejemplo simple <?php // Declarar una clase simple class TestClass { public $foo; public function __construct($foo) { $this->foo = $foo; } public function __toString() { return $this->foo; } } $class = new TestClass('Hola Mundo'); echo $class; ?> El resultado del ejemplo sera: Hola Mundo Antes de PHP 5.2.0 el mtodo __toString() se llama slo cuando se combina directamente con echo() o print(). Desde PHP 5.2.0, se le llama en cualquier contexto de string (e.j. en printf() con el modificador %s) pero no en el contexto de otros tipos (e.j. con el modificador %d). Desde PHP 5.2.0, la conversin de los objetos sin el mtodo __toString() a string podra causar E_RECOVERABLE_ERROR.

__invoke()
El mtodo __invoke() es llamado cuando un script intenta llamar a un objeto como si fuera una funcin. Nota: Esta caracterstica est disponible desde PHP 5.3.0. Ejemplo #3 Uso de __invoke() <?php class CallableClass { public function __invoke($x) { var_dump($x); } } $obj = new CallableClass; $obj(5); var_dump(is_callable($obj)); ?> El resultado del ejemplo sera: int(5) bool(true)

__set_state()
Este mtodo static es llamado para las clases exportadas por var_export(), desde PHP 5.1.0. El nico parmetro de este mtodo es un array que contiene las propiedades exportadas en la forma array('property' => value, ...). Ejemplo #4 Uso de __set_state() (desde PHP 5.1.0) <?php class A { public $var1; public $var2; public static function __set_state($an_array) // As of PHP 5.1.0 { $obj = new A; $obj->var1 = $an_array['var1']; $obj->var2 = $an_array['var2']; return $obj; } } $a = new A; $a->var1 = 5; $a->var2 = 'foo';

eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array( // 'var1' => 5, // 'var2' => 'foo', // )); var_dump($b); ?> El resultado del ejemplo sera: object(A)#2 (2) { ["var1"]=> int(5) ["var2"]=> string(3) "foo" }

Palabra clave Final

Patrones

[edit] Last updated: Fri, 16 Sep 2011


User Contributed Notes Mtodos mgicos Wesley 25-May-2011 07:10 Warning __toString can be triggerd more then one time

<?php if(strstr(substr($obj,0,1024), 'somestuff') echo $obj; return 'missing somestuff at the start, create container!'; substr() will trigger a __toString aswell as echo $obj; ?> wich cause a performance issue since it will gather all data twice. what i used as a hotfix: <?php __toString(){ if(null === $this->sToString) $this->sToString = $this->_show(); return $this->sToString; } ?> Sammaye 18-Aug-2010 03:13 Thought I would just explain something about __get and __set here. Imgine you have a class like so: <?php class user_bo{ protected $attributes = Array(); public function __get($key){ return array_key_exists($key, $this->attributes) ? $this-

>attributes[$key] : null; } public function __set($key, $value){ $this->attributes[$key] = $value; } /** Constructor **/ public function __construct(){} } ?> now imagine you add a function like so to this class: <?php public function update(){ global $database; $fields = $database->connection->getTable("users"); $set = ""; foreach($fields as $field => $info){ if(isset($this->$field)){ if($this->$field == "now()"){ $set .= $field."=".mysql_real_escape_string($this>$field).", "; }elseif($this->$field == null){ $set .= $field."=null, "; }else{ $set .= $field."='".mysql_real_escape_string($this>$field)."', "; } } } $output_set = substr($set, 0, -2); $query = "UPDATE users SET $output_set WHERE sequal_user_id = '$this>sequal_user_id'"; mysql_query($query) or sqlerrorhandler("(".mysql_errno().") ".mysql_error(), $query, $_SERVER['PHP_SELF'], __LINE__); return true; } ?> The if(isset($this->$field)) will return false everytime. Instead of isset in loops like this you should use ($this->$field). isset returns false even when the field is populated and in theory it should never return false since in my __get: <?php return array_key_exists($key, $this->attributes) ? $this->attributes[$key] : null; ?> I return null if my variable don't exist.

Anonymous 25-Jul-2010 02:41 Concerning __set() with protected/private/overloaded properties, the behavior might not be so intuitive without knowing some underlying rules. Consider this test object for the following examples... <?php class A { protected $test_int = 2; protected $test_array = array('key' => 'test'); protected $test_obj; function __construct() { $this->test_obj = new stdClass(); } function __get($prop) { return $this->$prop; } function __set($prop, $val) { $this->$prop = $val; } } $a = new A(); ?> Combined Operators (.=, +=, *=, etc): you must also define a companion __get() method to grant write -and- read access to the property. Remember, "$x += $y" is shorthand for "$x = $x + $y". In other words, "__set($x, (__get($x) + $y))". Properties that are Arrays: attempting to set array values like "$a>test_array[] = 'asdf';" from outside this object will result in an "Indirect modification of overloaded property" notice and the operation completely ignored. You can't use '[]' for array value assignment in this context (with the exception only if you made __get() return by reference, in which case, it would work fine and bypass the __set() method altogether). You can work around this doing something like unioning the array instead: <?php $a->test_array[] = 'asdf'; // notice given and ignored unless __get() was declared to return by reference $a->test_array += array(1 => 'asdf'); // to add a key/value $a->test_array = array("key" => 'asdf') + $a->test_array; // to overwrite a key/value. ?> Properties that are Objects: as long as you have that __get() method, you can freely access and alter that sub object's own properties, bypassing __set() entirely. Remember, objects are assigned and passed by reference naturally. <?php $a->test_obj->prop = 1; // fine if $a did not have a set method declared.

?> All above tested in 5.3.2. tom 18-Jul-2010 10:20 Note a common pitfall when using __wakeup. If you unserialize a datastructure, you may not rely on the parent object to have been fully unserialized by the time __wakeup is called. Example <?php class A { public $b; public $name; } class B extends A { public $parent; public function __wakeup() { var_dump($parent->name); } } $a = new A(); $a->name = "foo"; $a->b = new B(); $a->b->parent = $a; $s = serialize($a); $a = unserialize($s); ?> Expected output: "foo". Actual output: NULL. Reason: $b is unserialized before $name. By the time B::__wakeup is called, $a->name does not yet have a value. So be aware that the order in which your class variables are defined is important! You need to manually order them by dependencies - or write a __sleep function and order them by depencies there. (Currently I can't tell which option I hate more) Anonymous 01-Jul-2010 04:29 C++-style operator overloading finally makes an appearance with the introduction to __invoke(). Unfortunately, with just '()'. In that sense, it is no more useful than having a default class method (probably quite useful actually) and not having to type out an entire method name. Complimenting wbcarts at juno dot com's point class below, the following allows calculating distance between one or more graph points... <?php class point { public $x; public $y; function __construct($x=0, $y=0) { $this->x = (int) $x; $this->y = (int) $y; }

function __invoke() { $args = func_get_args(); $total_distance = 0; $current_loc = $this; foreach ($args as $arg) { if (is_object($arg) and (get_class($arg) === get_class($this))) { $total_distance += sqrt(pow($arg->x - $current_loc->x, 2) + pow((int) $arg->y - $current_loc->y, 2)); $current_loc = $arg; } else { trigger_error("Arguments must be objects of this class."); return; } } return $total_distance; } } $p1 = new point(1,1); $p2 = new point(23,-6); $p3 = new point(15,20); echo $p1($p2,$p3,$p1); // round trip 73.89 ?> Functionally, __invoke() can also be used to mimic the use of variable functions. Sadly, attempting any calling of __invoke() on a static level will produce a fatal error. tfn dot yldrm at hotmail dot com 15-Jun-2010 10:26 Properties can be used for getter and setter as C# methods get_PropertyName and set_PropertyName for full event-based programming. <?php /** * @author Tufan Baris YILDIRIM * @name getter and setter magics. */ Class GetSet { public $Name,$Surname; // they will be deleted those only for autocompletion. private $_Name,$_Surname; public function __construct() { foreach(get_class_vars('GetSet') as $varName=>$varValue) { if(substr($varName,0,1)!="_") unset($this->$varName); } } public function __get($varName) { if(method_exists($this,$MethodName='get_'.$varName)) return $this->$MethodName();

else trigger_error($varName.' is not avaliable .',E_USER_ERROR); } public function __set($varName,$value) { if(method_exists($this,$MethodName='set_'.$varName)) return $this->$MethodName($value); else trigger_error($varName.' is not avaliable .',E_USER_ERROR); } private function set_Surname($value) { // On surname changed Events. echo "Surname Changed as ".$value."\r\n"; $this->_Surname=$value; } private function get_Surname() { // On Get Events. echo "Surname Getted\r\n"; return $this->_Surname; } private function set_Name($value) { // On Name changed Events. echo "Name Changed as ".$value."\r\n"; $this->_Name=$value; } private function get_Name() { // On Get Name Events. echo "Name getted\r\n"; return $this->_Name; } } ?> Using : <?php $get=new GetSet(); $get->Name="Tufan Baris"; $get->Surname="YILDIRIM"; echo $get->Name.' '.$get->Surname; ?> Output Name Changed as Tufan Baris Surname Changed asYILDIRIM Name getted Surname Getted Tufan Baris YILDIRIM xmontero at dsitelecom dot com 05-Dec-2009 04:01

This "tip" is for those who receive the error "Object of class X could not be converted to string" when __toString() is not possible, and it is not a choice to process or not - for example when received via debug_backtrace(). HOW IT HAPPENED: I use to have a logging function called LogEnter() and another called LogExit() that I use to call at every function when intensively debugging. For example all my functions look like: <?php function AnyFunction( $a, $b, $c ) { LogEnter(); // DoSomeStuffHere. LogExit(); } ?> Then inside LogEnter(), I use debug_backtrace() to discover the parameters that were used when calling the function (in this example the values of $a, $b, $c). The LogEnter() deals without knowing the number of parameters to treat, and formats something like ( 2, 3, 4 ) if those were the values. That info is printed among other info to a log-file. Doing this, keeps me with a single-very-simple way to trace all te calls. The thing is that if the the following are met at the same time, I have a problem: The The The For function accepts (and is called with) a class as a parameter. function is being debuggd by calling LogEnter(); class does not have __toString() coded. some reason I am not allowed to edit the class nor inherit from it.

Then the debug function performs an "Object of class X could not be converted to string" error when trying to "dump" the parameter list (containing the object) into the file. LOG FUNCTION: This looks for the "parameters array" and then calls BuildArgs to format them. LogWrite finds the function name, file, line number, and so on, but does not matter here. <?php public function LogEnter() { $Debug = debug_backtrace(); $DebugLine = $Debug[ 1 ]; // [0] would be this very same function. // [1] is the caller of this function. $Args = isset( $DebugLine[ 'args' ] ) ? $DebugLine[ 'args' ] : '[x]';

$Msg = 'Entering with ' . BuildArgs( $Args ); LogWrite( $Msg ); } // CAUTION, THIS NEXT ONE FAILS private function BuildArgs( $Args ) { $Result = '( ' . implode( ', ', $Args ) . ' )'; return $Result; } ?> The problem is that if you call a function where a parameter being an object that has not the __toString() defined, then automatically "imploding" the argument list into a string, fails. SOLUTION: The solution is that the __toString is "checkable for its existance". The "tips" are to manually traverse the result of debug_backtrace()['args'] and then, for each element, check: - if it is or not an object with is_object( $Value ) - in case it is, check if __toString() is defined with method_exists( $Value, '__toString' ) - in case it is not an object or the method exist, perform a normal assignation. - in case it is an object without that method, print something else. I choose to output the class name with get_class( $Value ) and put it between square brackets. The final function becomes like this: <?php private function BuildArgs( $Args ) { // This function gets an 'args' array from debug_backtrace() and formats it in the printable format (a, b, c) including the parenthesis. $FormattedParams = ''; $Glue = ''; foreach( $Args as $Value ) { if( is_object( $Value ) ) { if( method_exists( $Value, "__toString" ) ) { $PrintableValue = $Value; } else { $PrintableValue = '[' . get_class( $Value ) . ']'; } } else { $PrintableValue = $Value;

} $FormattedParams .= $Glue . $PrintableValue; $Glue = ', '; } $Result = sprintf( '( %s )', $FormattedParams ); return $Result; } ?> vad dot viktor at gmail dot com 15-Oct-2009 10:06 If you are using __autoload to dynamically load your classes, then __invoke won't work. Calling a class like SomeClass() will give and error in this case as it is evaluated first as a function, not as a class and so __autoload won't be triggered. moechofe 25-Jul-2009 09:47 __invoke() cannot be used to create fluente interface like in the "D language" <?php class CallableClass { var $next; function __invoke($x) { var_dump($x); return $this; } } $obj = new CallableClass; $obj->next = new CallableClass; var_dump( $obj(5) ); // OK! var_dump( $obj(5)(6) ); // Parse error var_dump( $obj->next(7) ); // Fatal error: Call to undefined method CallableClass::next() var_dump( {$obj->next}(7) ); // Parse error ?> zach at bygeekz dot com 25-May-2009 07:45 Try this one on. <?php $ret = new Test(true); var_dump((bool)(string)$ret); var_dump($ret); $ret=null; $ret = new Test(); var_dump((bool)(string)$ret); var_dump($ret); class Test { protected $state=null; function __construct($state=null) { $this->state = $state; } function __toString() { if ($this->state) { return "1"; } else { return "0"; } } }

?> You could for instance do.. if(!(bool)(string)$ret) { do_something!; } Alternatively, just make state public, and check it. if(!$ret->state) {}. There is no automatic to verify a class. It is to force it out to Test(); then test the way I have found aside from some internal state check will always return an object. The only way around that string either where I did, or $ret = (string) new bool of your output..

if (!$ret) { echo "noooo!"; } But now you have no calling methods, so I hope you passed some data in to get a usable string out. Of course, if your class isn't named test, you can add a method.. public function test() { return $this->state; } Logically that will work regardless of the _toString(), but I had hoped to post this to help others see that there are a multitude of ways to check the validity of a class once it is loaded. In __construct you can add any number of checks and set your state appropriately. jahanaddison6 at hotmail dot com 03-May-2009 10:16 If you're expecting this code to work think again: <?php class Example { public function __construct() { return false; } public function Test() { return true; } } ?> The above code will not work as expected, The Test method will still return true. rudie-de-hotblocks at osu1 dot php dot net 09-Apr-2009 01:35 Note also that the constructor is executed also, and before __set_state(), making this magic function less magic, imho, (except for the ability to assign private members). muratyaman at gmail dot com 07-Jan-2009 03:54 Regarding __toString: <?php class my_tag_A{

public public public public

$id=''; $href=''; $target=''; $class='';

public $label=''; function __construct($href, $label){ $this->href = $href; $this->label = $label; } public function __toString(){ return '<a '.$this->nz_arr(array('id', 'href', 'target', 'class')). ' >' . $this->label . '</a>'; } function nz_arr($attrib_arr){ $s = ''; foreach($attrib_arr as $attrib){ $s .= $this->nz($attrib); } return $s; } /** * Print the tag attribute if it is not blank, such as id="$this->id" * @param string $attrib * @return string */ function nz($attrib){ $s = ''; if($this->$attrib != '') $s = $attrib .' = "' . $this->$attrib . '"'; return $s; } //This causes RECURSION because of parsing between double quotes. This is a very UNEXPECTED behaviour! function nz_($attrib){ $s = ''; if($this->$attrib != '') $s = "$attrib = \"$this->$attrib\""; return $s; } }//end class

//usage $a = new my_tag_A('abc.php', 'ABC'); $a->target = '_blank'; echo $a; //prints: // <a href="abc.php" target="_blank" >ABC</a> ?> jsnell at e-normous dot com 03-Dec-2008 10:01 Be very careful to define __set_state() in classes which inherit from a parent using it, as the static __set_state() call will be called for any children. If you are not careful, you will end up with an object of the wrong type. Here is an example:

<?php class A { public $var1; public static function __set_state($an_array) { $obj = new A; $obj->var1 = $an_array['var1']; return $obj; } } class B extends A { } $b = new B; $b->var1 = 5; eval('$new_b = ' . var_export($b, true) . ';'); var_dump($new_b); /* object(A)#2 (1) { ["var1"]=> int(5) } */ ?> patricknegri at gmail dot com 26-Oct-2008 11:57 Imports Pattern - Extend Classes in Real Time: <?php class BaseClass { var $__imported; var $__imported_functions; function __construct() { $__imported = Array(); $__imported_functions = Array(); } function Imports($object) { $new_imports = new $object(); $imports_name = get_class($new_imports); array_push( $__imported, Array($imports_name,$new_imports) ); $imports_function = get_class_methods($new_imports); foreach ($imports_function as $i=>$function_name) { $this->__imported_functions[$function_name] = &$new_imports; } } function __call($m, $a) { if (array_key_exists($m,$this->__imported_functions)) {

return call_user_func_array(Array($this>__imported_functions[$m],$m),$a); } throw new ErrorException ('Call to Undefined Method/Class Function', 0, E_ERROR); } } class ExternalFunc { function TestB() { echo "External Imported!"; } } class B extends BaseClass { function __construct() { $this->Imports("ExternalFunc"); } function Msg() { echo "Hello world<br />"; } } $b = new B(); $b->Msg(); // or call $b->Imports("ExternalFunc"); $b->TestB(); //$b->TestB(1,3,4); ?> jon at webignition dot net 03-Oct-2008 02:26 The __toString() method is extremely useful for converting class attribute names and values into common string representations of data (of which there are many choices). I mention this as previous references to __toString() refer only to debugging uses. I have previously used the __toString() method in the following ways: - representing a data-holding object as: - XML - raw POST data - a GET query string - header name:value pairs - representing a custom mail object as an actual email (headers then body, all correctly represented) When creating a class, consider what possible standard string representations are available and, of those, which would be the most relevant with respect to the purpose of the class. Being able to represent data-holding objects in standardised string forms makes it much easier for your internal representations of data to be shared in an interoperable way with other applications.

wbcarts at juno dot com 02-Oct-2008 11:12 To be helpful, the __toString() method should return the class name and the state of all its properties inside square brackets. <?php class Point { protected $x, $y; public function __construct($xVal = 0, $yVal = 0) { $this->x = $xVal; $this->y = $yVal; } public function __toString() { // the function we're interested in... return "Point[x=$this->x, y=$this->y]"; } } $point1 = new Point(10, 10); $point2 = new Point(50, 50); echo $point1 . '<br>'; echo $point2 . '<br><br>'; ?> Point[x=10, y=10] Point[x=50, y=50] Classes that include objects, should call that objects __toString() method. <?php class Line { protected $start, $end; public function __construct(Point $p1, Point $p2){ $this->start = $p1; $this->end = $p2; } public function __toString() { // the function we're interested in... return 'Line[start=' . $this->start->__toString() . // call __toString() ', end=' . $this->end->__toString() . ']'; // call __toString() } } echo (new Line($point1, $point2)); ?> Line[start=Point[x=10, y=10], end=Point[x=50, y=50]] rc @ nospam @ vorklift dot sea oh em 07-Aug-2008 02:03 A note: __wakeup occurs before saving the unserialization of an session object. Therefore, $_SESSION['var']::__wakeup() setting $_SESSION['var'] = new Class() will fail and $_SESSION['var'] will remain unchanged. This means that if you have a pseudo-temporary object that contains a class to auto revert to, you have to revert that session object in the initialization of the website rather than via a __wakeup() script. Anonymous 31-May-2008 11:24

Serializing objects is problematic with references. This is solved redefining the __sleep() magic method. This is also problematic when parent class has private variables since the parent object is not accessible nor its private variables from within the child object. I found a solution that seems working for classes that implements this __sleep() method, and for its subclasses. Without more work in subclasses. The inheritance system does the trick. Recursively __sleep() call parent' __sleep() and return the whole array of variables of the object instance to be serialized. <?php class foo { } class a { private $var1; function __construct(foo &$obj = NULL) { $this->var1 = &$obj; } /** Return its variables array, if its parent exists and the __sleep method is accessible, call it and push the result into the array and return the whole thing. */ public function __sleep() { $a = array_keys(get_object_vars(&$this)); if (method_exists(parent, '__sleep')) { $p = parent::__sleep(); array_push($a, $p); }; return $a; } } class b extends a { function __construct(foo &$obj = NULL) { parent::__construct($obj); } } session_start(); $myfoo = &new foo(); $myb = &new b($myfoo); $myb = unserialize(serialize(&$myb)); ?> This should work, I haven't tested deeper. yanleech at gmail dot com 10-May-2008 01:24 Maybe we can using unserialize() & __wakeup() instead "new" when creating a new instance of class. Consider following codes: class foo { static public $WAKEUP_STR = 'O:3:"foo":0:{}'; public function foo(){}

public function bar(){} } $foo = unserialize(foo::$WAKEUP_STR); michal dot kocarek at seznam dot cz 18-Apr-2008 09:34 Remember that setters and getters (__set, __get) will work in your class as long as you NOT SET the property with given name. If you still want to have the public property definition in the class source code (phpDocumentor, editor code completition, or any other reason) when using these magic methods, simply unset() your public properties inside the constructor. __set/__get function will be called and code reader will see at first sight, which public properties are available. Example: <?php class user { /** * @var int Gets and sets the user ID */ public $UserID; private $_userID; public function __construct() { // All the magic is in single line: // We unset public property, so our setters and getters // are used and phpDoc and editors with code completition are happy unset($this->UserID); } public function __set($key, $value) { // assign value for key UserID to _userID property } public function __get($key) { // return value of _userID for UserID property } } ?> dhuseby domain getback tld com 01-Mar-2008 02:22 The above hint for using array_keys((array)$obj) got me investigating how to get __sleep to really work with object hierarchies. With PHP 5.2.3, If you want to serialize an object that is part of an object hierarchy and you want to selectively serialize members (public, private, and protected) by manually specifying the array of members, there are a few simple rules for naming members that you must follow: 1. public members should be named using just their member name, like so: <?php class Foo { public $bar; public function __sleep() { return array("bar");

} } ?> 2. protected members should be named using "\0" . "*" . "\0" . member name, like so: <?php class Foo { protected $bar; public function __sleep() { return array("\0*\0bar"); } } ?> 3. private members should be named using "\0" . class name . "\0" . member name, like so: <?php class Foo { private $bar; public function __sleep() { return array("\0Foo\0bar"); } } ?> So with this information let us serialize a class hierarchy correctly: <?php class Base { private $foo = "foo_value"; protected $bar = "bar_value"; public function __sleep() { return array("\0Base\0foo", "\0*\0bar"); } } class Derived extends Base { public $baz = "baz_value"; private $boo = "boo_value"; public function __sleep() { // we have to merge our members with our parent's return array_merge(array("baz", "\0Derived\0boo"), parent::__sleep()); } } class Leaf extends private $qux = protected $zaz public $blah = Derived { "qux_value"; = "zaz_value"; "blah_value";

public function __sleep() { // again, merge our members with our parent's return array_merge(array("\0Leaf\0qux", "\0*\0zaz", "blah"), parent::__sleep()); } } // test it $test = new Leaf(); $s = serialize($test); $test2 = unserialize($s); echo $s; print_r($test); print_r($test2); ?> Now if you comment out all of the __sleep() functions and output the serialized string, you will see that the output doesn't change. The most important part of course is that with the proper __sleep() functions, we can unserialize the string and get a properly set up object. I hope this solves the mystery for everybody. __sleep() does work, if you use it correctly :-) andrew dot minerd at sellingsource dot com 08-Nov-2007 03:55 Until __sleep is "fixed" (here's hoping), a function that will return ALL members of a given object -- public, protected, AND private: <?php public function getPropertyNames(array $filter = NULL) { $rc = new ReflectionObject($this); $names = array(); while ($rc instanceof ReflectionClass) { foreach ($rc->getProperties() as $prop) { if (!$filter || !in_array($prop->getName(), $filter)) $names[] = $prop->getName(); } $rc = $rc->getParentClass(); } return $names; } ?> amir_abiri at ipcmedia dot com 24-Jul-2007 11:58 Another small thing that is important to note about __sleep() and privte member variables: <?php class A { private $a; public function __construct() {

$this->a = 1; } } class B extends A { protected $b; public function __construct() { parent::__construct(); $this->b = 2; } function __sleep() { return array('a', 'b'); } } serialize(new B); ?> result: Notice: serialize(): "a" returned as member variable from __sleep() but does not exist in ... To summerize: in a given class hierarchy in which parent classes contain private member variables, those variables are serialized when __sleep() is not defined. However, once __sleep() is defined, there is no way to make those private member variables serialized as well. From that point on, serialization is performed from the visibility scope of the subclass. It is particularly important to note this little quirk when designing base classes that their derivables may be serialized, or when subclassing an external library class. alejandro dot gama at gmail dot com 09-May-2007 02:47 Referering my previus note: there was an error in the code. But i find a better way: <? session_start(); class Classes{ private $name; private $statics; function __construct($name){ $this->name=$name; $this->statics=array(); } function setStatic($k,$v){ if(!is_resource($v)) $this->statics[$k]=$v; }

function __wakeup(){

foreach($this->statics as $k=>$v) eval($this->name."::\$".$k."=\$this->statics['".$k."'];"); } } function storeStaticAttributes(){ $classes=get_declared_classes(); foreach($classes as $name){ $reflect=new ReflectionClass($name); if($reflect->isUserDefined()){ $statics=$reflect->getStaticProperties(); if(empty($_SESSION["_classes"])) $_SESSION["_classes"]=array(); if(empty($_SESSION["_classes"][$name])) $_SESSION["_classes"][$name]=new Classes($name); foreach($statics as $k=>$v) $_SESSION["_classes"][$name]->setStatic($k,$v); } } } register_shutdown_function('storeStaticAttributes'); ?> Travis Swicegood 08-May-2007 02:43 There is no need to use eval() to mimic mixins (i.e., multiple inheritance) within PHP 5. You only need to: <?php class MyClass { private $_obj = null; public function __construct($obj) { $this->_obj = $obj; } public function __call($method, $args) { if (!method_exists($this->_obj, $method)) { throw new Exception("unknown method [$method]"); } return call_user_func_array( array($this->_obj, $method), $args ); } } ?> You could just as easily add an addMixin() method that would allow you to add multiple objects to an array, and then iterate over that array until you found the right method. As noted, these are referred to as a Mixins in other languages.

adar at darkpoetry dot de 05-May-2007 01:09 Maybe not really new and all in all definitely not the best solution,but if you cant extend a class (if your class alreay extends an abstract or other things like that) you can 'fake' a extend. <?php class MyClass extends SomeAbstractUnknownClass { private $classObject; public function __construct ( classObject $classToExtend ) { $this->classObject = $classToExtend; } public function __call($func, $var) { if ( !count($var) ) { return $this->classObject->$func($var); } else { $str = ''; $values = array_values($var); for ( $i=0; $i<count($values); $i++ ) { $str .= "'".$values[$i]."' ,"; } $str = substr($str, 0, -2); return eval('return $this->classObject->'.$func.'('.$str.');'); } } } ?> So if you'll do a $myClass->unknownMethod() and it is found neither in MyClass nor in SomeAbstractUnknownClass, MyClass will try to call this method in $classObject. I use this for 'extending' a UserObject-Class which already extends an other one. Better solutions are always welcome ;) Drico Filho 23-Jan-2007 03:33 Since PHP 5.2.0, you'll always get an error like this: "Object of class foo could not be converted to string" When one tries to use an object as string, for instance: class Test{} echo new Test(); Thus, one way to avoid this problem is to programme the magic method __toString. However, in the older versions, it would output a string saying that it was an object together a unique obj id. Therefore, the __toString() method must comply with this behaviour. My suggestion: class Test{ function __toString(){

if(!isset($this->__uniqid)) $this->__uniqid = md5(uniqid(rand(), true)); return(get_class($this)."@".$this->__uniqid); } } echo new Test(); would output something like this: Test@6006ba04f5569544c10a588b04849cf7 jstubbs at work-at dot co dot jp 02-Sep-2006 07:32 $myclass->foo['bar'] = 'baz'; When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to: $tmp_array = &$myclass->foo; $tmp_array['bar'] = 'baz'; unset($tmp_array); Therefore, the above won't do anything if your __get implementation resembles this: function __get($name) { return array_key_exists($name, $this->values) ? $this->values[$name] : null; } You will actually need to set the value in __get and return that, as in the following code: function __get($name) { if (!array_key_exists($name, $this->values)) $this->values[$name] = null; return $this->values[$name]; } taylorbarstow at google's mail service 15-May-2006 08:54 I've just come accross something interesting relating to storing PHP5 objects in a session. If you don't provide an __autoload(), then you MUST load the class definition before calling session_start(). I guess that when you call session_start(), any objects in the session are unserialized then and there and placed into $_SESSION. If you don't provide the class definition before calling session_start(), your object will get the class __PHP_Incomplete_Class, and you won't be able to use it for anything. Examples: <?php session_start(); require_once 'MyClass.php'; $obj = new MyClass; $_SESSION['obj'] = $obj; ?>

Works fine.

Then on a subsequent page load:

<?php session_start(); require_once 'MyClass.php'; $_SESSION['obj']->callSomeMethod(); ?> Fatal error: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "MyClass" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition. But if you do this instead, it works fine: <?php require_once 'MyClass.php'; session_start(); $_SESSION['obj']->callSomeMethod(); ?> Hopefully in some future release of PHP, __PHP_Incomplete_Class will be smart enough to check for a class definition at time of use (method call or property operation), and, if the class exists, magically "complete" itself and turn into the desired object. rayRO 02-Apr-2006 04:55 If you use the Magical Method '__set()', be shure that the call of <?php $myobject->test['myarray'] = 'data'; ?> will not appear! For that u have to do it the fine way if you want to use __set Method ;) <?php $myobject->test = array('myarray' => 'data'); ?> If a Variable is already set, the __set Magic Method already wont appear! My first solution was to use a Caller Class. With that, i ever knew which Module i currently use! But who needs it... :] There are quiet better solutions for this... Here's the Code: <?php class Caller { public $caller; public $module; function __call($funcname, $args = array()) { $this->setModuleInformation(); if (is_object($this->caller) && function_exists('call_user_func_array')) $return = call_user_func_array(array(&$this->caller, $funcname), $args); else trigger_error("Call to Function with call_user_func_array

failed", E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function __construct($callerClassName = false, $callerModuleName = 'Webboard') { if ($callerClassName == false) trigger_error('No Classname', E_USER_ERROR); $this->module = $callerModuleName; if (class_exists($callerClassName)) $this->caller = new $callerClassName(); else trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR); if (is_object($this->caller)) { $this->setModuleInformation(); if (method_exists($this->caller, '__init')) $this->caller->__init(); $this->unsetModuleInformation(); } else trigger_error('Caller is no object!', E_USER_ERROR); } function __destruct() { $this->setModuleInformation(); if (method_exists($this->caller, '__deinit')) $this->caller->__deinit(); $this->unsetModuleInformation(); } function __isset($isset) { $this->setModuleInformation(); if (is_object($this->caller)) $return = isset($this->caller->{$isset}); else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function __unset($unset) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$unset})) unset($this->caller->{$unset}); } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); } function __set($set, $val) {

$this->setModuleInformation(); if (is_object($this->caller)) $this->caller->{$set} = $val; else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); } function __get($get) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$get})) $return = $this->caller->{$get}; else $return = false; } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; } function setModuleInformation() { $this->caller->module = $this->module; } function unsetModuleInformation() { $this->caller->module = NULL; } } // Well this can be a Config Class? class Config { public $module; public $test; function __construct() { print('Constructor will have no Module Information... Use __init() instead!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); $this->test = '123'; } function __init() { print('Using of __init()!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); } function testFunction($test = false) { if ($test != false) $this->test = $test;

} } echo('<pre>'); $wow = new Caller('Config', 'Guestbook'); print_r($wow->test); print('<br />'); print('<br />'); $wow->test = '456'; print_r($wow->test); print('<br />'); print('<br />'); $wow->testFunction('789'); print_r($wow->test); print('<br />'); print('<br />'); print_r($wow->module); echo('</pre>'); ?> Outputs something Like: Constructor will have no Module Information... Use __init() instead! --> <-Using of __init()! --> Guestbook <-123 456 789 Guestbook ksamvel at gmail dot com 10-Feb-2006 05:29 To copy base part of derived class appropriate method in base should be defined. E.g.: class A { public function setAVar( $oAVar) { $this->oAVar = $oAVar; } public function getAVar() { return $this->oAVar; } public function copyA( &$roDest) { if( $roDest instanceof A) $this->oAVar = $roDest->oAVar; } private $oAVar; } class B extends A { public function setBVar( $oBVar) { $this->oBVar = $oBVar; } public function getBVar() { return $this->oBVar; } private $oBVar; } $oA = new A();

$oB = new B(); $oA->setAVar( 4); $oB->setAVar( 5); $oB->setBVar( 6); echo "oA::oAVar " . $oA->getAVar() . "<br>"; echo "oB::oAVar " . $oB->getAVar() . "<br>"; echo "oB::oBVar " . $oB->getBVar() . "<br>"; echo "<br>"; $oB->copyA( $oA); echo "oA::oAVar " . $oA->getAVar() . "<br>"; echo "oB::oAVar " . $oB->getAVar() . "<br>"; echo "oB::oBVar " . $oB->getBVar() . "<br>"; Output: oA::oAVar 4 oB::oAVar 5 oB::oBVar 6 oA::oAVar 4 oB::oAVar 4 oB::oBVar 6 b dot schoppmeier at bas-consult dot de 26-Jan-2006 11:18 The sequence of events regarding __sleep and __destruct is unusual __ as __destruct is called before __sleep. The following code snippet: <?php $sequence = 0; class foo { public $stuff; public function __construct($param) { global $sequence; echo "Seq: ", $sequence++, " - constructor\n"; $this->stuff = $param; } public function __destruct() { global $sequence; echo "Seq: ", $sequence++, " - destructor\n"; } public function __sleep() { global $sequence; echo "Seq: ", $sequence++, " - __sleep\n"; return array("stuff"); } public function __wakeup() { global $sequence; echo "Seq: ", $sequence++, " - __wakeup\n"; } } session_start(); $_SESSION["obj"] = new foo("A foo"); ?> yields the output:

Seq: 0 - constructor Seq: 1 - destructor Seq: 2 - __sleep Only when you end your script with a call to session_write_close() as in: <?php $sequence = 0; class foo { public $stuff; public function __construct($param) { global $sequence; echo "Seq: ", $sequence++, " - constructor\n"; $this->stuff = $param; } public function __destruct() { global $sequence; echo "Seq: ", $sequence++, " - destructor\n"; } public function __sleep() { global $sequence; echo "Seq: ", $sequence++, " - __sleep\n"; return array("stuff"); } public function __wakeup() { global $sequence; echo "Seq: ", $sequence++, " - __wakeup\n"; } } session_start(); $_SESSION["obj"] = new foo("A foo"); session_write_close(); ?> the sequence is as common sense would expect it to be as the following output shows: Seq: 0 - constructor Seq: 1 - __sleep Seq: 2 - destructor docey 09-Dec-2005 04:44 about __sleep and _wakeup, consider using a method like this: class core { var $sub_core; //ref of subcore var $_sleep_subcore; // place where serialize version of sub_core will be stored function core(){ $this->sub_core = new sub_core(); return true; } function __wakeup() { // on wakeup of core, core unserializes sub_core // wich it had stored when it was serialized itself

$this->sub_core = unserialize($this->_sleep_subcore); return true; } function __sleep() { // sub_core will be serialized when core is serialized. // the serialized subcore will be stored as a string inside core. $this->_sleep_subcore = serialize($this->sub_core); $return_arr[] = "_sleep_subcore"; return $return_arr; } } class sub_core { var $info; function sub_core() { $this->info["somedata"] = "somedata overhere" } function __wakeup() { return true; } function __sleep() { $return_arr[] = "info" return $return_arr; } } this way subcore is being serialized by core when core is being serialized. subcore handles its own data and core stores it as a serialize string inside itself. on wakeup core unserializes subcore. this may have a performance cost, but if you have many objects connected this way this is the best way of serializing them. you only need to serialize the the main object wich will serialize all those below which will serialize all those below them again. in effect causing a sort of chainreaction in wich each object takes care of its own info. offcoarse you always need to store the eventualy serialized string in a safe place. somebody got experience with this way of __wakeup and __sleep. works in PHP4&5 martin dot goldinger at netserver dot ch 15-Aug-2005 11:47 When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance. <? class BaseObject

{ function __sleep() { $vars = (array)$this; foreach ($vars as $key => $val) { if (is_null($val)) { unset($vars[$key]); } } return array_keys($vars); } }; ?> jeffxlevy at gmail dot com 14-Aug-2005 01:26 Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection). mastabog at hotmail dot com 13-Aug-2005 12:06 In reply to krisj1010 at gmail.com below: __sleep() handles protected/private properties very well. You should never rely on get_class_vars() to retrieve property names since this function only returns the public properties. Use the Reflection API instead for that purpose. Better yet, if you know which ones you want to save it is always faster to specify the return array manually. Domenic Denicola 23-Jun-2005 03:52 This small sentence tripped me up for a half an hour: "It is worth noting that the __toString method will only be called when it is directly combined with echo() or print()." So code like this will _not_ work, even though you might think it would: <? //$x is some variable with a __toString method defined. $y = "x's value is: " . $x; $y = "x's value is: " . (string)$x; ?> In _em_ both situations, $y will contain "x's value is: Object id #42" (or whatever object ID). So, the only recourse I guess is this: <? $y = "x's value is: " . $x->__toString(); ?> elias 11-Apr-2005 04:48 The default toString output is very useful for visual debuggin because it shows the object id.

There is no function to resolve the id directly(?), but you can do this: <?php function __toString() { sscanf((string)$this, "Object id #%d", $id); return "Object(Template) id #$id"; } ?> HTH, elias ddavenport at newagedigital dot com 27-Jan-2005 07:09 One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'. Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous. Consider the following... <?php class SomeStupidStorageClass { public function getContents($pos, $len) { ...stuff... } } class CryptedStorageClass extends SomeStupidStorageClass { private $decrypted_block; public function getContents($pos, $len) { ...decrypt... } } ?> If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored. Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to. Considering encapsulation again, no class should have to know how the parent handles its own private data. And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience. If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable. Like so.... <?php class BetterClass { private $content; public function __sleep() { return array('basedata1', 'basedata2');

} public function getContents() { ...stuff... } } class BetterDerivedClass extends BetterClass { private $decrypted_block; public function __sleep() { return parent::__sleep(); } public function getContents() { ...decrypt... } } ?> The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be. krisj1010 at gmail.com 09-Jan-2005 08:09 If you are attempting to write an abstract/base class which automates the __sleep process in PHP5 you will run into some trouble if the subclasses which are being serialized have private/protected variables you need to be serialized. The reason is, even though get_class($this) within the base class will return the subclass -- get_class_vars(get_class($this)) will *not* return the subclass' protected/private variables. Which makes sense -- using OO principles. However, when automating __sleep it becomes necissary to have access to the private/protected subclass variables because their names have to be returned by __sleep. So here is the work around: <?php public function __sleep() { ... code ... $sleepVars = array_keys((array)$this); return $sleepVars; } ?> Even though array_keys includes more information about the variable names than just the variable names -- it still seems to work appropriately.

También podría gustarte