- 0x01 前言
- 0x02 PHP魔术方法
- 0x03 案例
0x01 前言
PHP 将所有以 __
(两个下划线)开头的类方法保留为魔术方法。
所以在定义类方法时,建议不要以 __
为前缀。
0x02 PHP魔术方法
魔术方法 | 基础讲解 |
---|---|
__construct() | 类的构造函数-实例化一个类之前先执行该方法 |
__destruct() | 类的析构函数-在销毁一个类之前执行该方法 |
__call() | 在对象中调用一个不可访问方法时调用 |
__callStatic() | 在静态上下文中调用一个不可访问方法时调用 |
__get() | 读取不可访问属性的值时调用 |
__set() | 在给不可访问属性赋值时调用 |
__isset() | 当对不可访问属性调用 isset() 或 empty() 时调用 |
__unset() | 当对不可访问属性调用 unset() 时,__unset() 时调用 |
__sleep() | 执行serialize()时,会先调用该魔术方法 |
__wakeup() | 执行unserialize()时,会先调用该魔术方法 |
__toString() | 当一个对象被当作字符串对待的时候,会触发这个魔术方法 |
__invoke() | 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用 |
__set_state() | 自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用 |
__clone() | 当复制完成时,如果定义了 clone() 方法,则新创建的对象(复制生成的对象)中的 clone() 方法会被调用 |
__debugInfo() | 执行var_dump()方法的时候会调用__debugInfo()方法。如果未在对象上定义该方法,则将显示所有公共,受保护和私有属性。 PHP 5.6.0中添加了此功能。 |
__autoload() | 它会在试图使用尚未被定义的类时自动调用。 通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。 |
0x03 案例
下面让我们使用实例,来学习魔术方法是如何使用的
PHP版本: 5.6.27-nts
Web服务器软件: Apache
0x03.1 __construct()
0x03.1.1 说明
PHP 5 允行开发者在一个类中定义一个方法作为构造函数。
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
如果类没有声明该方法,那么类中就会默认存在一个没有参数且内容为空的构造方法。
注意: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。
要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()
。
如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
0x03.1.2 类中的声明格式
public function __constrct(参数列表)
{
// 抒写实际代码
}
0x03.1.3 实例
# 测试代码
<?php
class Person {
public $name;
public $sex;
public $age;
/**
* 显示声明一个带参数的构造方法
*/
public function __construct($name='小明', $sex='男', $age=22)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
/**
* 申明一个普通的类方法
*/
public function say()
{
echo "我叫: " . $this->name . ",性别:" . $this->sex . ",年龄:" . $this->age;
}
}
?>
// 创建对象$person_1 且不带任参数
$person_1 = new Person();
$person_1->say();
// 输出结果
// 我叫: 小明,性别:男,年龄:22
//
// 创建对象$person_2 且带参数 $name='张三'
$person_2 = new Person('张三');
$person_2->say();
// 输出结果
// 我叫: 张三,性别:男,年龄:22
// 创建对象$person_3 且带参数 $name='小红' $sex = '女' $age = 18
$person_3 = new Person('小红', '女', '18');
$person_3->say();
// 输出结果
// 我叫: 小红,性别:女,年龄:18
在类刚实例化的时候先调用了 __construct
方法进行初始化操作,然后在去调用 say()
方法 ,输出内容
0x03.2 __destruct()
0x03.2.1 说明
PHP 5 引入了析构函数的概念
析构函数方法允许在销毁一个类之前执行的一些操作或完成一些功能
例如说:现在插件了一个文件类,我们在使用完文件以后就可以通过该方法简单合理的关闭文件
0x03.2.2 类中的声明格式
public function __destruct()
{
// 抒写实际代码
}
注意:析构函数不能带有任何参数
0x03.2.3 实例
# 测试代码
<?php
class Person {
public $name;
public $sex;
public $age;
/**
* 显示声明一个带参数的构造方法
*/
public function __construct($name='小明', $sex='男', $age=22)
{
$this->name = $name;
}
/**
* 申明一个普通的类方法
*/
public function say()
{
echo "我叫: " . $this->name . ",性别:" . $this->sex . ",年龄:" . $this->age;
}
/**
* 声明一个析构方法
*/
public function __destruct()
{
echo '我叫:' . $this->name . '我被删除了';
}
}
?>
// 创建对象 $person_1 并且使用 unset() 函数销毁创建的对象
$person_1 =new Person('张三');
$person_1->say();
unset($person_1);
// 输出结果
// 先输出:我叫: 张三,性别:男,年龄:22
// 然后在输出:我叫:张三我被删除了
// 执行顺序
// 先执行 __construct() 方法进行赋值
// 然后调用 say() 类方法 输出:我叫: 张三,性别:男,年龄:22
// 接着使用 unset() 函数 销毁创建的对象 触发了方法 __destruct() 然后输出:我叫:张三我被删除了
0x03.3 __call()
0x03.3.1 说明
在对象中调用一个不可访问方法时,该方法会被调用
该方法有固定的两个参数
$name
参数是要调用的方法名称。$arguments
参数是一个枚举数组,包含着要传递给方法 $name 的参数。
为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免。
该方法在调用一个不存在的方法时会自动调用,使程序会仍然继续执行下去。
0x03.3.2 类中的声明格式
public function __call(string $name, array $arguments)
{
// 抒写实际代码
}
0x03.3.3 实例
# 测试代码
<?php
class MethodTest
{
/**
* 申明一个普通的类方法
*/
public function hello()
{
echo '你好呀,我是一个存在的方法: hello';
}
/**
* 声明此方法用来处理调用对象中不存在的方法
*/
public function __call($name, $arguments)
{
echo '你所调用的函数方法: ' . $name . '不存在' . '<br/>';
echo '传递的参数为: ';
print_r($arguments);
}
}
?>
// 实例化一个对象,然后调用一个根本不存在的方法
$obj = new MethodTest;
$obj->runTest('传递参数1', '传递参数2', '传递参数3');
// 输出结果
// 你所调用的函数方法: runTest不存在
// 传递的参数为: Array ( [0] => 传递参数1 [1] => 传递参数2 [2] => 传递参数3 )
// 实例化一个对象,然后调用一个根本不存在的方法,接着在调用一个存在的方法
$obj = new MethodTest;
$obj->runTest('传递参数1', '传递参数2', '传递参数3');
$obj->hello();
// 输出结果
// 你所调用的函数方法: runTest不存在
// 传递的参数为: Array ( [0] => 传递参数1 [1] => 传递参数2 [2] => 传递参数3 )
// 你好呀,我是一个存在的方法: hello
0x03.4 __callStatic()
0x03.4.1 说明
在对象中调用一个不可访问的静态方法时,该方法会被调用
该方法有固定的两个参数
$name
参数是要调用的方法名称。$arguments
参数是一个枚举数组,包含着要传递给方法 $name 的参数。
为了避免当调用的静态方法不存在时产生错误,而意外的导致程序中止,可以使用 __callStatic() 方法来避免。
该方法在调用一个不存在的静态方法时会自动调用,使程序会仍然继续执行下去。
0x03.4.2 类中的声明格式
public function __callStatic(string $name, array $arguments)
{
// 抒写实际代码
}
0x03.4.3 实例
# 测试代码
<?php
class StaticMethodTest
{
/**
* 申明一个普通的类方法
*/
public static function hello()
{
echo '你好呀,我是一个存在的静态方法: hello';
}
/**
* 声明此方法用来处理调用对象中不存在的方法
*/
public static function __callStatic($name, $arguments)
{
echo '你所调用的静态函数方法: ' . $name . '不存在' . '<br/>';
echo '传递的参数为: ';
print_r($arguments);
}
}
?>
// 实例化一个对象,然后调用一个根本不存在的静态方法
$obj = new StaticMethodTest;
$obj::runTest('传递参数1', '传递参数2', '传递参数3');
// 输出结果
你所调用的静态函数方法: runTest不存在
传递的参数为: Array ( [0] => 传递参数1 [1] => 传递参数2 [2] => 传递参数3 )
// 实例化一个对象,然后调用一个根本不存在的静态方法,接着在调用一个存在的静态方法
$obj = new StaticMethodTest;
$obj::runTest('传递参数1', '传递参数2', '传递参数3');
$obj::hello();
// 输出结果
你所调用的静态函数方法: runTest不存在
传递的参数为: Array ( [0] => 传递参数1 [1] => 传递参数2 [2] => 传递参数3 )
你好呀,我是一个存在的静态方法: hello
0x03.5 __get()
0x03.5.1 说明
读取不可访问属性的值/不存在的参数时,该方法会被调用
php 面向对象编程中,类的成员属性被设定为 private 后,如果我们在外面调用它则会出现“Fatal error: Cannot access private property”的错误
想要类属性设置 private 依然可以访问,我们可以使用魔术方法__get()
来做到
该方法有固定的一个参数
$name
该参数是要调用的参数名称
0x03.5.2 类中的声明格式
public function __get($name)
{
// 抒写实际代码
}
0x03.5.3 实例
<?php
// 测试代码
class Computer
{
private $brand;
private $vga_card;
function __construct($brand = '神州', $vga_card = 'N卡GTX 1060')
{
$this->brand = $brand;
$this->vga_card = $vga_card;
}
/**
* 声明此方法用来处理 读取不可访问属性的值时,该方法会被调用
* @param $name
*
* @return string
*/
public function __get($name)
{
if (!isset($this->$name)) {
return '属性: ' . $name . '不存在';
}
return $this->$name;
}
}
?>
// 实例化一个对象,然后调用两个私有属性
$computer = new Computer();
echo $computer->brand . "<br>";
echo $computer->vga_card . "<br>";
// 输出结果
// 神州
// N卡GTX 1060
// 实例化一个对象,然后调用一个根本不存在的属性
$computer = new Computer();
echo $computer->xxxx;
// 输出结果
// 属性: xxxx不存在
// 注意,这里会输出 “属性: xxxx不存在” 是因为我在 __get() 方法里面做了判断
// 就是这个判断 if (!isset($this->$name))
0x03.6 __set()
0x03.6.1 说明
在给不可访问属性赋值时,__set() 会被调用
php 面向对象编程中,类的成员属性被设定为 private 后,如果我们在外面尝试修改它的值则会出现“Cannot access private property”的错误
想要类属性设置 private 依然可以修改,我们可以使用魔术方法 __set()
来做到
该方法有固定的二个参数
$name
该参数是要设置的参数名称$val
该参数是要设置的内容
0x03.6.2 类中的声明格式
public function __set($name,$val){
// 抒写实际代码
}
0x03.6.3 实例
<?php
// 测试代码
class main
{
private $name = '张三';
/**
* 声明此方法用来处理 在给不可访问属性赋值时,该方法会被调用
*
* @param $name
* @param $val
*
* @return string
*/
public function __set($name,$val){
$this->$name = $val;
}
/**
* 声明此方法用来处理 读取不可访问属性的值时,该方法会被调用
* @param $name
*
* @return string
*/
public function __get($name)
{
if (!isset($this->$name)) {
return '属性: ' . $name . '不存在';
}
return $this->$name;
}
}
?>
$main = new main();
$main->name = "王五"; // 调用了 __set()魔术方法 修改了私有属性 private $name 的值
echo $main->name; // 调用 __get()魔术方法 读取 private $name 的值
// 输出结果
// 王五
0x03.7 __isset()
0x03.7.1 说明
当对不可访问属性调用 isset() 或 empty() 时 ,__isset() 方法会被调用
0x03.7.2 类中的声明格式
public function __isset($name)
{
// 抒写实际代码
}
0x03.7.3 实例
<?php
class main
{
private $name = '张三';
public function __isset($name)
{
if (isset($this->$name)) {
echo $name . ' 该私有变量存在<br/>';
return true;
} else {
echo $name . ' 该私有变量不存在<br/>';
return false;
}
}
}
?>
// 使用 isset 判断一下 类私有变量 name 是否存在
$main = new main();
var_dump(isset($main->name));
// 输出结果
// name 该私有变量存在
// bool(true)
// 使用 isset 判断一下 类私有变量 aaa 是否存在
$main = new main();
var_dump(isset($main->aaa));
// 输出结果
// aaa 该私有变量不存在
// bool(false)
0x03.8 __unset()
0x03.8.1 说明
当对不可访问属性调用 unset() 时,__unset() 会被调用
0x03.8.2 类中的声明格式
public function __unset($name)
{
// 抒写实际代码
}
0x03.8.3 实例
<?php
class main
{
private $name = '张三';
public function __get($name)
{
if (!isset($this->$name)) {
return '属性: ' . $name . "不存在<br/>";
}
return $this->$name;
}
public function __unset($name)
{
echo "该参数 $name 被删除了<br/>";
unset($this->$name);
}
}
?>
$main = new main();
echo $main->name;
// 输出结果
// 张三
$main = new main();
unset($main->name);
echo $main->name;
// 输出结果
// 该参数 name 被删除了
// 属性: name不存在
0x03.9 __sleep()
0x03.9.1 说明
执行serialize()时,先会调用这个函数
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
0x03.9.2 类中的声明格式
public function __sleep()
{
// 抒写实际代码
}
0x03.9.3 实例
<?php
class Person
{
public $name;
public $sex;
public $age;
public function __construct($name='小明', $sex='未知', $age=25)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
/**
* @return array
*/
public function __sleep() {
echo '当我被外部使用 serialize() 调用时,我会执行 __sleep()方法<br>';
# 把名字 base64编码 一下确定它进来了该方法
$this->name = base64_encode($this->name);
// 这里必须返回一个数值,里边的元素表示返回的属性名称
// array('name') 表示的就是 $this->name
// array('sex') 表示的就是 $this->sex
// 例如:
// 我这里传了 name 与 sex 那么 serialize() 完毕以后,返回的值就只会带上参数
// $this->name
// $this->sex
//
// 如果序列化的时候
// $this->name 是 小军
// $this->sex 是 男
//
// 那么下次反序列化的时候
// $this->name 值是 小军
// $this->sex 值是 男
// $this->age 值是 NULL 因为没有return的时候我没有加上 $this->age
return array('name', 'sex');
}
}
?>
$person = new Person('小军', '男'); // 初始赋值
echo serialize($person);
// 它会执行 __sleep 方法
// 并且 echo 一句话
// 并且 类里面的 $this->name base64编码赋值保存起来
// 执行结果
// 当我被外部使用 serialize() 调用时,我会先执行 __sleep()方法
// O:6:"Person":2:{s:4:"name";s:8:"5bCP5Yab";s:3:"sex";s:3:"男";}
0x03.10 __wakeup()
0x03.10.1 说明
执行unserialize()时,先会调用这个函数
unserialize() 会先检查是否存在一个 __wakeup() 方法。
如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源
0x03.10.2 类中的声明格式
public function __wakeup()
{
// 抒写实际代码
}
0x03.10.3 实例
<?php
class Person
{
public $name;
public $sex;
public $age;
public function __construct($name='小明', $sex='未知', $age=25)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
/**
* @return array
*/
public function __sleep() {
# 把名字 base64编码 一下确定它进来了该方法
$this->name = base64_encode($this->name);
return array('name', 'sex');
}
public function __wakeup()
{
$this->name = '郑' . base64_decode($this->name);
}
}
?>
$person = new Person('小军', '男'); // 初始赋值
$p = serialize($person);
$p1 = unserialize($p);
var_dump($p1->name);
var_dump($p1->sex);
var_dump($p1->age);
// 执行结果
// 执行结果中名字返回 "郑小军" 是因为,执行了 __wakeup() 方法
// 在 __wakeup() 方法里面,我把名字base64解码并在前面添加了一个 “郑” 字
// string(9) "郑小军"
// string(3) "男"
// NULL
0x03.11 __toString()
0x03.11.1 说明
该方法用于一个类被当成字符串时应怎样回应。
例如 echo $obj; 应该显示些什么。
此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
0x03.11.2 类中的声明格式
public function __toString() {
// 抒写实际代码
}
0x03.11.3 实例
<?php
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() {
return '世界 ' . $this->foo;
}
}
?>
$class = new TestClass('你好');
echo $class;
// 执行结果
// 使用 echo 触发了 __toString() 方法
// 记得返回必须是字符串!!!
// echo 的时候多了“世界”两个字是因为我在 _toString() 方法,return的
// 世界 你好
0x03.12 __invoke()
0x03.12.1 说明
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
0x03.12.2 类中的声明格式
public function __invoke(参数列表)
{
// 抒写实际代码
}
0x03.12.3 实例
<?php
class CallableClass
{
private $name = '测试';
function __invoke($x) {
if (!isset($this->$x)) {
return $x . '这个属性在类中不存在';
}
return $this->$x;
}
}
// 把类当函数调用的时候 类的__invoke() 方法会执行
$obj = new CallableClass;
echo $obj('name');
// 输出结果
// 测试
$obj = new CallableClass;
echo $obj('xxx');
// 执行结果
// xxx这个属性在类中不存在
0x03.13 __set_state()
0x03.13.1 说明
自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态方法会被调用。
本方法的唯一参数是一个数组,其中包含按 array(‘property’ => value, …) 格式排列的类属性。
0x03.13.2 类中的声明格式
public static function __set_state($array)
{
// 抒写实际代码
}
0x03.13.3 实例
<?php
class A
{
public $var1;
public $var2;
public static function __set_state($array)
{
$obj = new A;
$obj->var1 = $array['var1'];
$obj->var2 = $array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
echo var_export($a, true);
// 执行结果
// A::__set_state(array(
// 'var1' => 5,
// 'var2' => 'foo',
// ))
0x03.14 __clone()
0x03.14.1 说明
当对象复制完成时调用,利用clone 关键字来触发
0x03.14.2 类中的声明格式
function __clone() {
// 抒写实际代码
}
0x03.14.3 实例
<?php
class SubObject
{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// 强制复制一份this->object, 否则仍然指向同一个对象
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;
print("原始对象:\n");
print_r($obj);
print("克隆的对象:\n");
print_r($obj2);
// 执行结果
// 原始对象:
// MyCloneable Object
// (
// [object1] => SubObject Object
// (
// [instance] => 1
// )
//
// [object2] => SubObject Object
// (
// [instance] => 2
// )
// )
// 克隆的对象:
// MyCloneable Object
// (
// [object1] => SubObject Object
// (
// [instance] => 3
// )
//
// [object2] => SubObject Object
// (
// [instance] => 2
// )
// )
0x03.15 __debugInfo()
0x03.15.1 说明
该方法在var_dump()类对象的时候被调用,如果没有定义该方法,则var_dump会打印出所有的类属性
注意:它的返回必须是数组!!!
注意:__debugInfo()方法只能用于PHP 5.6.0及更高版本。
0x03.15.2 类中的声明格式
public function __debugInfo() {
// 抒写实际代码
}
0x03.15.3 实例
<?php
class C {
private $test_value;
public function __construct($val) {
$this->test_value = $val;
}
/**
* @return array
*/
public function __debugInfo() {
return [$this->test_value];
}
}
var_dump(new C('测试内容'));
// 输出结果
// object(C)#1 (1) {
// [0]=>
// string(12) "测试内容"
// }
0x03.16 __autoload()
0x03.16.1 说明
__autoload() 方法用于自动加载类
在实际项目中,不可能把所有的类都写在一个 PHP 文件中,当在一个 PHP 文件中需要调用另一个文件中声明的类时,就需要通过 include 把这个文件引入。
不过有的时候,在文件众多的项目中,要一一将所需类的文件都 include 进来,一个很大的烦恼是不得不在每个类文件开头写一个长长的包含文件的列表。我们能不能在用到什么类的时候,再把这个类所在的 php 文件导入呢?
为此,PHP 提供了 __autoload() 方法,它会在试图使用尚未被定义的类时自动调用。通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
__autoload() 方法接收的一个参数,就是欲加载的类的类名,所以这时候需要类名与文件名对应,如 Person.php ,对应的类名就是 Pserson 。
0x03.16.2实例
// 文件名称:Person.php
<?php
class Person {
private $name;
private $age;
function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
function say() {
echo "我的名字叫:".$this->name."<br />";
echo "我的年龄是:".$this->age;
}
}
?>
// 文件名称:test.php
<?php
function __autoload($class_name)
{
require_once $class_name.'.php';
}
//当前页面 Person 类不存在则自动调用 __autoload() 方法,传入参数 Person
$p1 = new Person("小明","25");
$p1->say();
?>
运行 test.php 输出:
我的名字叫:小明
我的年龄是:25