Magic Methods

The function names __construct, __destruct (see Constructors and Destructors), __call, __get, __set, __isset, __unset (see Overloading), __sleep, __wakeup, __toString, __clone and __autoload are magical in PHP classes. You cannot have functions with these names in any of your classes unless you want the magic functionality associated with them.

注意

PHP reserves all function names starting with __ as magical. It is recommended that you do not use function names with __ in PHP unless you want some documented magic functionality.

__sleep and __wakeup

serialize() checks if your class has a function with the magic name __sleep. If so, that function is executed prior to any serialization. It can clean up the object and is supposed to return an array with the names of all variables of that object that should be serialized.

The intended use of __sleep is to close any database connections that the object may have, commit pending data or perform similar cleanup tasks. Also, the function is useful if you have very large objects which do not need to be saved completely.

Conversely, unserialize() checks for the presence of a function with the magic name __wakeup. If present, this function can reconstruct any resources that the object may have.

The intended use of __wakeup is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.

__toString

The __toString method allows a class to decide how it will react when it is converted to a string.

例子 19-25. Simple example

<?php
// Declare a simple class
class TestClass
{
    
public $foo;

    
public function __construct($foo) {
        
$this->foo = $foo;
    }

    
public function __toString() {
        return
$this->foo;
    }
}

$class = new TestClass('Hello');
echo
$class;
?>

上例将输出:

Hello

It is worth noting that the __toString method will only be called when it is directly combined with echo() or print().

例子 19-26. Cases where __toString is called

<?php
// __toString called
echo $class;

// __toString called (still a normal parameter for echo)
echo 'text', $class;

// __toString not called (concatenation operator used first)
echo 'text' . $class;

// __toString not called (cast to string first)
echo (string) $class;

// __toString not called (cast to string first)
echo "text $class";
?>

add a note add a note User Contributed Notes
alastair dot cooke at transport dot nsw dot gov dot au
23-Oct-2006 12:00
<?php
echo phpversion();

class
moose { function __toString() { return 'a moose'; } }

$foo = new moose();
echo
" i have $foo \n" ;
echo
$foo;
?>

will give you:
5.1.6
i have Object id #278
a moose

ie __toString does not happen when you interpolate things into a string...
jstubbs at work-at dot co dot jp
02-Sep-2006 03: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
16-May-2006 04: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
03-Apr-2006 12: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
11-Feb-2006 01: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 07: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
10-Dec-2005 12: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 07: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 09: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 08: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 11: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
12-Apr-2005 12: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
Gedoon-S
08-Apr-2005 02:26
I just thought it was worth mentioning that __toString works also with <?= $class ?>. It calls __toString and echoes what was returned just like <?php echo $class; ?> does, but it's shorter.
ddavenport at newagedigital dot com
27-Jan-2005 03: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 04: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.