PHP中的魔术方法将会自动被调用,除了construct()、destruct()、__call() 所有的魔术方法 必须 声明为 public
序号 | 魔术方法 | 模块 | 作用 |
---|---|---|---|
1 | __construct() | 构造函数 | 创建对象时自动调用 |
2 | __destruct() | 析构函数 | 对象销毁或失去引用时自动调用 |
3 | __call() | 方法重载 | 在对象中调用一个不可访问方法或不存在的方法时自动调用。 |
4 | __callStatic() | 方法重载 | 在静态上下文中调用一个不可访问方法时自动调用。 |
5 | __set() | 属性重载 | 在给不可访问(protected 或 private)或不存在的属性赋值时自动调用 |
6 | __get() | 属性重载 | 在对象中调用一个不可访问方法或不存在的方法时 |
7 | __isset() | 属性重载 | 当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,__isset() 会被调用。 |
8 | __unset() | 属性重载 | 当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。 |
9 | __sleep() | 序列化 | 调用 serialize() 方法进行序列化 |
10 | __wakeup() | 序列化 | 调用 unserialize() 方法进行发序列化时 |
11 | __serialize() | 序列化 | PHP 7.4 后加入,用于替代 __sleep() |
12 | __unserialize() | 序列化 | PHP 7.4 后加入,用于替代 __wakeup() |
13 | __toString() | 类被当成字符串时输出 | |
14 | __invoke() | 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。 | |
15 | __set_state() | 当使用 var_export() | |
16 | __debuginfo() | 当使用 var_dump() | |
17 | __clone() | 使用 clone() 时调用 |
1. 构造函数 __construct()
1.1 子类调用父类构造器
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
注意: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
代码片段如下:SubClass 和 OtherSubClass 都继承自 BaseClass,由于SubClass有定义自己的构造器所以要调用父类的构造器就必须使用 parent::__construct()。而OtherSubClass 由于没有自己的构造器会在创建对象的时候,隐式调用
<?php
class BaseClass
{
function __construct()
{
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass
{
function __construct()
{
parent::__construct();
print "In SubClass constructor\n";
}
}
class OtherSubClass extends BaseClass
{
}
$obj1 = new BaseClass();
echo PHP_EOL;
// In BaseClass constructor
$obj2 = new SubClass();
echo PHP_EOL;
//In BaseClass constructor
//In SubClass constructor
$obj3 = new OtherSubClass();
// In BaseClass constructor
1.2 构造器参数
构造器中的参数可以给定默认值(PHP8 的版本的话可以不按照顺序传参, 但是必须固定属性名称的对应关系)
<?php
class Point
{
protected int $x;
protected int $y;
public function __construct(int $x, int $y = 0)
{
$this->x = $x;
$this->y = $y;
}
public function __toString(): string
{
return "x = $this->x, y = {$this->y}";
}
}
// 两个参数都传入
$p1 = new Point(4, 5);
echo 'p1 => ', $p1, PHP_EOL;
// 仅传入必填的参数。 $y 会默认取值 0。
$p2 = new Point(4);
echo 'p2 => ', $p2, PHP_EOL;;
// 使用命名参数(PHP 8.0 起):
$p3 = new Point(y: 5, x: 4);
echo 'p3 => ', $p3, PHP_EOL;
1.3 构造器属性提升
PHP 8起,构造器的参数也可以相应提升为类的属性。 构造器的参数赋值给类属性的行为很普遍,否则无法操作。 而构造器提升的功能则为这种场景提供了便利。 因此上面的例子可以用以下方式重写
<?php
class Point {
public function __construct(protected int $x, protected int $y = 0)
{}
}
当构造器参数带访问控制(visibility modifier)时,PHP 会同时把它当作对象属性和构造器参数, 并赋值到属性。 构造器可以是空的,或者包含其他语句。 参数值赋值到相应属性后执行正文中额外的代码语句。
并非所有参数都需要提升。可以混合提升或不提升参数作为属性,也不需要按顺序。 提升后的参数不影响构造器内代码调用。
注意: 对象属性的类型不能为 callable 以避免为引擎带来混淆。 因此提升的参数也不能是 callable。 其他任意 类型声明 是允许的。
1.4 Static 创造方法
暂时略过
2. 析构函数 __destruct()
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。
例子:
class MyDestructableClass
{
function __construct()
{
print "In constructor\n";
}
function __destruct()
{
print "Destroying " . __CLASS__ . "\n";
}
}
$obj = new MyDestructableClass();
// In constructor
// Destroying MyDestructableClass
3. 属性重载
PHP所提供的重载(overloading)是指动态地创建类属性和方法。是通过魔术方法来实现的。当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。
3.1 __set()
在给不可访问(protected 或 private)或不存在的属性赋值时,__set() 会被调用。
3.2 __get()
读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。
3.3 __isset()
当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,__isset() 会被调用。
3.4 __unset()
当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。
4. 方法的重载
4.1 __call()
在对象中调用一个不可访问方法或不存在的方法时,__call() 会被调用。
<?php
public __call(string $name, array $arguments): mixed
4.2 __callStatic()
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
<?php
public static __callStatic(string $name, array $arguments): mixed
4.3 例子
<?php
class MethodTest
{
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
5. 序列化发序列化
5.1 __sleep()
<?php
public __sleep(): array
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 null 被序列化,并产生一个 E_NOTICE 级别的错误。
__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。使用 __serialize() 接口替代。
<?php
class Shop
{
public function __construct(
public string $name,
protected float $weight,
private float $price
)
{
$this->price = $price;
}
public function __toString(): string
{
return "name: {$this->name}, weight: {$this->weight}, price: {$this->price}";
}
public function __sleep(): array
{
return ['name', 'weight', 'price'];
}
}
$kele = new Shop('可口可乐', 355.00, '3.00');
echo $kele . PHP_EOL; // name: 可口可乐, weight: 355, price: 3
echo serialize($kele) . PHP_EOL; // O:4:"Shop":3:{s:4:"name";s:12:"可口可乐";s:9:"*weight";d:355;s:11:"Shopprice";d:3;}
5.2 __wakeup()
<?php
public __wakeup(): void
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
<?php
class Shop
{
public function __construct(
public string $name,
protected float $weight,
private float $price
)
{
$this->price = $price;
}
public function __toString(): string
{
return "name: {$this->name}, weight: {$this->weight}, price: {$this->price}";
}
public function __sleep(): array
{
echo 'in __sleep()' . PHP_EOL;
return ['name', 'weight', 'price'];
}
public function __wakeup(): void
{
echo 'in __wakeup()' . PHP_EOL;
}
}
$kele = new Shop('可口可乐', 355.00, '3.00');
$str = serialize($kele); // in __sleep()
echo $str . PHP_EOL; // O:4:"Shop":3:{s:4:"name";s:12:"可口可乐";s:9:"*weight";d:355;s:11:"Shopprice";d:3;}
$kele2 = unserialize($str); // in __wakeup()
echo $kele2; // name: 可口可乐, weight: 355, price: 3
5.3 __serialize()
这是7.4 后的新特性
serialize() 函数会检查类中是否存在一个魔术方法 __serialize() 。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误。
如果类中同时定义了 __serialize() 和 __sleep() 两个魔术方法,则只有 __serialize() 方法会被调用。 __sleep() 方法会被忽略掉。如果对象实现了 Serializable 接口,接口的 serialize() 方法会被忽略,做为代替类中的 __serialize() 方法会被调用。
<?php
public __serialize(): array
例子:serialize() 和 unserialize() 在序列化和反序列化时的优先级是最高的。
<?php
use JetBrains\PhpStorm\ArrayShape;
class Shop2
{
public function __construct(
public string $name,
protected float $weight,
private float $price
)
{
$this->price = $price;
}
public function __toString(): string
{
return "name: {$this->name}, weight: {$this->weight}, price: {$this->price}";
}
public function __sleep(): array
{
echo 'in __sleep()' . PHP_EOL;
return ['name', 'weight', 'price'];
}
public function __wakeup(): void
{
echo 'in __wakeup()' . PHP_EOL;
}
#[ArrayShape(['name' => "string", 'weight' => "float", 'price' => "float"])]
public function __serialize(): array
{
echo 'in __serialize()' . PHP_EOL;
return [
'name' => $this->name,
'weight' => $this->weight,
'price' => $this->price
];
}
public function __unserialize(array $data): void
{
echo 'in __unserialize()' . PHP_EOL;
$this->name = $data['name'];
$this->weight = $data['weight'];
$this->price = $data['price'];
}
}
$kele = new Shop2('可口可乐', 355.00, '3.00');
$str = serialize($kele);
echo $str . PHP_EOL;
$kele2 = unserialize($str);
echo $kele2;
//in __serialize()
//O:5:"Shop2":3:{s:4:"name";s:12:"可口可乐";s:6:"weight";d:355;s:5:"price";d:3;}
//in __unserialize()
//name: 可口可乐, weight: 355, price: 3%
5.4 __unserialize()
参考上一个
<?php
public __unserialize(array $data): void
6. __toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
<?php
public __toString(): string