PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法, 包括: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(),__serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), __debugInfo() 。
__autoload
方法是用于类的自动加载, 不属于类的魔术方法。
所有的魔术方法, 都是public的
从PHP7.4.0开始, 用__serialize, __unserialize 取代了 __sleep(), __wakeup(), 如果存在前者则不会加载后者.
- __construct() 构造函数
- __destruct() 析构函数
- __call() 在对象中调用一个不可访问方法时调用
- __callStatic() 在静态上下文中调用一个不可访问的方法时调用
- __get() 读取不可访问属性的值时调用
- __set() 在给不可访问属性赋值时调用
- __isset() 当对不可访问属性调用 isset() 或 empty() 时调用
- __unset() 当对不可访问属性调用 unset() 时调用
- __sleep() / __serialize 执行serialize() 时会先检查是否存在该方法并先调用
- __wakeup() / __unserialize() 执行unserialize()时会先检查是否存在该方法并先调用
- __toString() 用于一个类被当成字符串时的回应
- __invoke() 当尝试以调用函数的方式调用一个对象时调用
- __set_state() 当调用 var_export() 导出类时,此静态方法会被调用
- __clone() 当用clone关键字复制完成时调用
- __debugInfo() 当使用var_dump打印对象的属性时调用。 如果未定义则会打印所有的public, protected 和 private 属性。
构造/析构函数 __construct, __desctruct
如果扩展类中没有定义, 则会自动继承;
如果重写了, 就会覆盖父类的定义, 如果此时需要保留父类的方法, 则需要使用 parent::__construct 和 parent::__destruct 调用.
Class Person {
public $name;
function __construct($name='aben') {
$this->name = $name;
}
}
class Student extends Person{
public $gender;
function __construct($name='aben', $gender='male') {
$this->gender=$gender;
parent::__construct($name); //如果这里不调用, name的值就无法初始化
}
}
注意: 析构函数没有参数
方法和属性的重载
php的类使用__call 和 __callStatic进行方法的重载, 使用 __set, __get, __isset, __unset 进行类的重载
__call 和 __callStatic 重载方法
在对象中调用一个不可访问方法时会调用__call方法, 在静态上下文中调用一个不可访问的方法时调用__callStatic方法.
类中的不可访问的方法, 包括未定义的方法以及private和protected的方法.
Class Person {
public function __call($name, $arguments){
// 注意: $name 的值区分大小写
echo "Calling object method '$name' " . implode(', ', $arguments). PHP_EOL;
}
public static function __callStatic($name, $arguments){
// 注意: $name 的值区分大小写
echo "Calling static method '$name' " . implode(', ', $arguments). PHP_EOL;
}
}
$Person = new Person();
$a = $Person->setName('aben', []); // 输出: Calling object method 'setName' aben
$Person::setAge('2012-12-8'); // 输出: Calling static method 'setAge' 2012-12-8
注意: __callStatic也是静态方法
__set, __get, __isset, __unset 重载属性
public __set( string $name, mixed $value) : void
public __get( string $name) : mixed
public __isset( string $name) : bool
public __unset( string $name) : void
在给不可访问属性赋值时,__set($name,$value) 会被调用。
读取不可访问属性的值时,__get($name) 会被调用。
当对不可访问属性调用 isset() 或 empty() 时,__isset($name) 会被调用。
当对不可访问属性调用 unset() 时,__unset($name) 会被调用。
在(反)序列化过程中的__sleep和__wakeup, __serialize()和__unserialize()
public function __sleep( void) : array //返回需要序列化的变量名称
public function __wakeup( void) : void
public __serialize ( void ) : array //返回需要序列化的变量名称和值的键值对
public __unserialize ( array $data ) : void
__sleep() 可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。适合用于一些很大的对象但不需要全部保存的场景。
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
从7.4.0版本开始,用__serialize和__unserialize取代__sleep和__wakeup,但是因为__sleep于__serialize返回的数据格式不同,导致了在__wakeup和__unserialize时也略微不同。
<?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();
}
//从PHP7.4.0版本开始支持__serialize和__unserialize
public function __serialize(): array {
//返回的是变量名称和值得键值对
return [
'dsn' => $this->dsn,
'user' => $this->username,
'pass' => $this->password,
];
}
public function __unserialize(array $data): void {
$this->dsn = $data['dsn'];
$this->username = $data['user'];
$this->password = $data['pass'];
$this->connect();
}
}
?>
__toString 用于一个类被当成字符串时应怎样回应
public function __toString ( void ) : string
如果一个类没有定义__toString方法, 则当其被当做字符串使用时, 会发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
注意: 不能在 __toString() 方法中抛出异常!
Class Person {
public function __toString() {
return 'This is class Person';
}
}
$Person = new Person();
echo $Person;
__invoke 当尝试以调用函数的方式调用一个对象时调用
public function__invoke ([ $para,... ] ) : mixed
Class Person {
public function __invoke($age) {
return 'Your age is:'.$age;
}
}
$Person = new Person();
echo $Person(18).PHP_EOL; //返回: Your age is:18
var_dump(is_callable($Person)); //返回: bool(true)
上面的代码中如果没有定义__invoke, 则 is_callable为false.
__set_state() 当调用 var_export() 导出类时,此静态方法会被调用
public static function __set_state ( array $properties ) : object
class A{
public $var1;
public $var2;
public static function __set_state($an_array) {
$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);
__debugInfo 当使用var_dump打印对象的属性时调用
Class Person {
public $age;
public $gender;
public function __construct($age, $gender) {
$this->age = $age;
$this->gender = $gender;
}
}
$Person = new Person(18, 'male');
var_dump($Person);
执行结果是:
object(Person)#1 (2) {
["age"]=>
int(18)
["gender"]=>
string(4) "male"
}
加上__debugInfo后就可以控制属性的输出:
Class Person {
public $age;
public $gender;
public function __construct($age, $gender) {
$this->age = $age;
$this->gender = $gender;
}
public function __debugInfo() {
//这里控制只输出age
return [
'age'=>$this->age
];
}
}
$Person = new Person(18, 'male');
var_dump($Person);
执行结果是:
object(Person)#1 (1) {
["age"]=>
int(18)
}
__clone 当用clone关键字复制完成时调用
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。
$copy_of_object = clone $object;
当对象被复制后,PHP 会对对象的所有属性执行一个浅复制(shallow copy)。 所有的引用属性仍然会是一个指向原来的变量的引用 。
class Account{
public $balance;//账户余额
public function __construct($balance) {
$this->balance = $balance;
}
}
class Person{
public $id;
public $name;//姓名
public $age;//年龄
public $used_names;//曾用名
public $account;//账户. 注意: 这里是一个对象!
public function __construct(int $id, string $name, int $age, array $used_names, Account $account) {
$this->id=$id;
$this->name=$name;
$this->age=$age;
$this->used_names = $used_names;
$this->account = $account;
}
public function setAge(int $age){
$this->age=$age;
}
public function getAge(){
return $this->age;
}
public function setUsedNames(array $used_names){
$this->used_names = $used_names;
}
public function __clone() {
$this->id=0;
}
}
上面定义了2个类: Person和Account,并且在Person类的clone操作时把属性id设置为0。
$Person1 = new Person(1,'aben',18, ['张三','李四'], new Account(100));
$Person2 = clone $Person1;
$Person2->id=2;
$Person2->age = 20;
$Person2->name = 'sky';
$Person2->used_names = ['王五','老六'];
$Person2->account = new Account(200);
echo serialize($Person1).PHP_EOL;
echo serialize($Person2);
执行结果是:
O:6:"Person":5:{s:2:"id";i:1;s:4:"name";s:4:"aben";s:3:"age";i:18;s:10:"used_names";a:2:{i:0;s:6:"张三";i:1;s:6:"李四";}s:7:"account";O:7:"Account":1:s:7:"balance";i:100;}}
O:6:"Person":5:{s:2:"id";i:2;s:4:"name";s:3:"sky";s:3:"age";i:20;s:10:"used_names";a:2:{i:0;s:6:"王五";i:1;s:6:"老六";}s:7:"account";O:7:"Account":1:{s:7:"balance";i:200;}}
看下结果中的Account.balance是没有问题的。 但是如果把上面的定义account的代码调整一下:
$Person2->account->balance = 200;
也就是:
$Person1 = new Person(1,'aben',18, ['张三','李四'], new Account(100));
$Person2 = clone $Person1;
$Person2->id=2;
$Person2->age = 20;
$Person2->name = 'sky';
$Person2->used_names = ['王五','老六'];
//$Person2->account = new Account(200);
$Person2->account->balance = 200; //注意这里是直接对对象属性赋值
echo serialize($Person1).PHP_EOL;
echo serialize($Person2);
执行的结果是:
O:6:"Person":5:{s:2:"id";i:1;s:4:"name";s:4:"aben";s:3:"age";i:18;s:10:"used_names";a:2:{i:0;s:6:"张三";i:1;s:6:"李四";}s:7:"account";O:7:"Account":1:s:7:"balance";i:200;}}
O:6:"Person":5:{s:2:"id";i:2;s:4:"name";s:3:"sky";s:3:"age";i:20;s:10:"used_names";a:2:{i:0;s:6:"王五";i:1;s:6:"老六";}s:7:"account";O:7:"Account":1:{s:7:"balance";i:200;}}
很显然,Person1.Account.balance的值也被改变了,所以需要对需要clone的对象的引用属性追加一次复制:
class Person{
....
public function __clone() {
$this->id=0;
$this->account = clone $this->account; //强制复制一份, 否则仍然指向同一个对象
}
....
}
如果使用复制方法 $Person2 = $Person1, 则实际上仍然中存在一个对象$Person1, 对$Person2的所有操作其实都是在对$Person1进行操作. 而clone默认只能复制普通属性.
更多信息请参考官方手册: https://www.php.net/manual/zh/language.oop5.magic.php