PHP魔术方法使用
2020-12-22 本文已影响0人
爱折腾的傻小子
支持魔术方法
class MyClass
{
public function __construct() {}
public function __destruct() {}
public function __call() {}
public function __callStatic() {}
public function __get() {}
public function __set() {}
public function __isset() {}
public function __unset() {}
public function __sleep() {}
public function __wakeup() {}
public function __toString() {}
public function __invoke() {}
public function __set_state() {}
public function __clone() {}
public function __debuginfo() {}
}
__construct 构造方法
- 当一个对象被实例化的时候会被首先调用
- 在PHP框架种依赖注入以及中间件一般就是这种方法完成的
- 类的构造方法是可以被子类继承和重写的
<?php
class A {
// 构造方法
public function __construct() {
echo "This is A construct\n";
}
}
class B extends A{
// 调用父类构造方法,再调用自己的构造方法
public function __construct() {
parent::__construct();
echo "This is B construct\n";
}
}
class C extends A{
// 重写构造方法,之调用自己的构造方法
public function __construct() {
echo "This is C construct";
}
}
new A(); // This is C construct
new B(); // This is A construct\n This is B construct\n
new C(); // This is C construct
__destruct 析构方法
- 在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等
- 析构方法不能带有任何参数
<?php
class Person{
public $name;
public $age;
public $sex;
// 构造方法
public function __construct($name = "", $sex = "男", $age = 22)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
// 成员方法
public function say()
{
echo "xxxxxxxxxxxxxxxxxxxx";
}
// 析构方法
public function __destruct()
{
echo "我正在被注销,需要关闭些什么吗?";
}
}
// 测试一
$person = new Person("小明");
$person->say(); // xxxxxxxxxxxxxxxxxxxx
unset($person); // 我正在被注销,需要关闭些什么吗?
echo 123; // 123
// 测试二
$person1 = new Person("小明");
$person1->say(); // xxxxxxxxxxxxxxxxxxxx
$person1 = null; // 我正在被注销,需要关闭些什么吗?
echo 123; // 123
__call 调用一个不可访问的方法时被调用。
- 不可访问的方法:方法不存在或方法是私有的
- 格式
// 参数 $function_name 会自动接收不存在的方法名
// 参数 $arguments 则以数组的方式接收不存在方法的多个参数
public function __call(string $function_name, array $arguments)
{
// 方法体
}
- 避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免
- 该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去
<?php
class Person
{
public function say()
{
echo "Hello, world!";
}
protected function isRed(){
echo "yes is Red\n";
}
/**
* 声明此方法用来处理调用对象中不存在的方法
*/
public function __call($funName, $arguments)
{
echo "你所调用的函数:" . $funName . "(参数:" ; // 输出调用不存在的方法名
print_r($arguments); // 输出调用不存在的方法时的参数列表
echo ")不存在!\n"; // 结束换行
}
}
$Person = new Person();
$Person->run("teacher");
/*
你所调用的函数:run(参数:Array
(
[0] => teacher
)
)不存在!
*/
$Person->eat("小明", "苹果");
/*
你所调用的函数:eat(参数:Array
(
[0] => 小明
[1] => 苹果
)
)不存在!
*/
$Person->say(); // Hello, world!
$Person->isRed(); // 改方法是存在但是是私有的
/*
你所调用的函数:isRed(参数:Array
(
)
)不存在!
*/
__callStatic 静态调用一个不可访问的方法时被调用
- 改方法的使用和__call基本一致,唯一区别就是调用形式不同
<?php
class Person
{
// 成员方法
public function say()
{
echo "xoxoxoxoxooxox";
}
// 私有静态方法
private static function s()
{
echo "1s1";
}
// __callStatic 魔术方法
public static function __callStatic($funName, $arg)
{
echo "你所调用的静态方法:" . $funName . "(参数:" ; // 输出调用不存在的方法名
print_r($arguments); // 输出调用不存在的方法时的参数列表
echo ")不存在!\n"; // 结束换行
}
}
$Person = new Person();
$Person::run("teacher"); // 你所调用的静态方法:run(参数:)不存在!
Person::run('weee'); // 你所调用的静态方法:run(参数:)不存在!
$Person::eat("小明", "苹果"); // 你所调用的静态方法:eat(参数:)不存在!
$Person::s(); // 你所调用的静态方法:s(参数:)不存在!
Person::s(); // 你所调用的静态方法:s(参数:)不存在!
$Person->say(); // xoxoxoxoxooxox
__get 获得一个类的成员变量时调用
- 类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误
- 在程序运行过程中,通过它可以在对象的外部获取私有成员属性的值
<?php
class Person
{
private $name;
private $age;
public function __construct($name="", $age=1)
{
$this->name = $name;
$this->age = $age;
}
// 在直接获取属性值时自动调用一次,以属性名作为参数传入并处理
// $propertyName 不存在的属性名称
public function __get($propertyName)
{
if ($propertyName == "age") {
if ($this->age > 30) {
return $this->age - 10;
} else {
return $this->$propertyName;
}
} else {
return $this->$propertyName;
}
}
}
$Person = new Person("小明", 60);
echo "姓名:" . $Person->name . ""; // 姓名:小明
echo "年龄:" . $Person->age . ""; // 年龄:50
__set 设置一个类的成员变量时调用
-
__set( $property, $value )
用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发 - 传递的参数是被设置的属性名和值
<?php
class Person
{
private $name;
private $age;
public function __construct($name="", $age=25)
{
$this->name = $name;
$this->age = $age;
}
// 模式方法 __set
// $property 被设置的属性
// $value 设置的值
public function __set($property, $value)
{
if ($property=="age")
{
if ($value > 150 || $value < 0) {
return;
}
}
$this->$property = $value;
}
// 成员方法
public function say()
{
echo "我叫".$this->name.",今年".$this->age."岁了";
}
}
$Person=new Person("小明", 25);
$Person->say(); // 我叫小明,今年25岁了
$Person->name = "小红";
$Person->age = 16;
$Person->age = 160;
$Person->say(); // 我叫小红,今年16岁了
__isset 当对不可访问属性调用isset()或empty()时调用
- isset()函数使用规则:
- 传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false
- 那么如果在一个对象外面使用isset()这个函数去测定对象里面的成员是否被设定可不可以用它呢?
- 如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性
- 如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见(可以使用__isset魔术方法)
- 当在类外部使用isset()函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的__isset()方法了帮我们完成这样的操作
- 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
<?php
class Person
{
public $sex;
private $name;
private $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// $content
public function __isset($content)
{
echo "当在类外部使用isset()函数测定私有成员{$content}时,自动调用\n";
echo isset($this->$content)."\n";
}
// 成员方法
public function say()
{
echo "我叫".$this->name.",今年".$this->age."岁了";
}
}
$person = new Person("小明", 25); // 初始赋值
echo isset($person->sex),"\n"; // 1
echo isset($person->name),"\n"; // 这里打印的是空串 魔术方法没有返回值的原因
/*
当在类外部使用isset()函数测定私有成员name时,自动调用
1
*/
echo isset($person->age),"\n";// 这里打印的是空串 魔术方法没有返回值的原因
/*
当在类外部使用isset()函数测定私有成员age时,自动调用
1
*/
__unset 当对不可访问属性调用unset()时被调用
- unset()这个函数的作用是删除指定的变量且传回true,参数为要删除的变量
- 那么如果在一个对象外部去删除对象内部的成员属性用unset()函数可以吗?
- 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性
- 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。
class Person
{
public $sex;
private $name;
private $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// $content
public function __unset($content)
{
echo "当在类外部使用unset()函数来删除私有成员时自动调用的\n";
echo isset($this->$content);
}
// 成员方法
public function say()
{
echo "我叫".$this->name.",今年".$this->age."岁了";
}
}
$person = new Person("小明", 25); // 初始赋值
unset($person->sex);
unset($person->name);
/*
当在类外部使用unset()函数来删除私有成员时自动调用的
1
*/
unset($person->age);
/*
当在类外部使用unset()函数来删除私有成员时自动调用的
1
*/
__sleep 执行serialize()时,先会调用这个函数
- serialize() 函数会检查类中是否存在一个魔术方法 __sleep()
- 如果存在,则该方法会优先被调用,然后才执行序列化操作
- 此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组
- 如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误
- __sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。
- __sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// $content
public function __sleep()
{
echo "当在类外部使用serialize()时会调用这里的__sleep()方法\n";
$this->name = base64_encode($this->name);
return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
}
// 成员方法
public function say()
{
echo "我叫".$this->name.",今年".$this->age."岁了";
}
}
$person = new Person('小明'); // 初始赋值
echo serialize($person);
/*
当在类外部使用serialize()时会调用这里的__sleep()方法
O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}
*/
__wakeup 执行unserialize()时,先会调用这个函数
- 如果存在,则会先调用
__wakeup
方法,预先准备对象需要的资源。 - __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// $content
public function __sleep()
{
echo "当在类外部使用serialize()时会调用这里的__sleep()方法\n";
$this->name = base64_encode($this->name);
return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
}
public function __wakeup() {
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法";
$this->name = 2;
$this->sex = '男';
// 这里不需要返回数组
}
// 成员方法
public function say()
{
echo "我叫".$this->name.",今年".$this->age."岁了";
}
}
$person = new Person('小明'); // 初始赋值
var_dump(serialize($person));
/*
当在类外部使用serialize()时会调用这里的__sleep()方法
string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}"
*/
var_dump(unserialize(serialize($person)));
/*
当在类外部使用serialize()时会调用这里的__sleep()方法
当在类外部使用unserialize()时会调用这里的__wakeup()方法
object(Person)#2 (3) {
["sex"]=>
string(3) "男"
["name"]=>
int(2)
["age"]=>
int(25)
}
*/
__toString 类被当成字符串时的回应方法
- __toString() 方法用于一个类被当成字符串时应怎样回应。例如
echo $obj;
应该显示些什么。 - 此方法必须返回一个字符串,否则将发出一条
E_RECOVERABLE_ERROR
级别的致命错误。 - 不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
public function __toString()
{
return '这里必须返回一个字符串';
}
}
$person = new Person('小明'); // 初始赋值
echo $person; // 这里必须返回一个字符串
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
}
$person = new Person('小明'); // 初始赋值
echo $person; // 这里必须返回一个字符串
/*
Catchable fatal error: Object of class Person could not be converted to string in /usercode/file.php on line 19
*/
__invoke 调用函数的方式调用一个对象时的回应方法
- 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
public function __invoke()
{
echo '这可是一个对象哦';
}
}
$person = new Person('小明'); // 初始赋值
$person(); // 这可是一个对象哦
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
}
$person = new Person('小明'); // 初始赋值
$person();
/*
Fatal error: Uncaught Error: Function name must be a string in /usercode/file.php:18
Stack trace:
#0 {main}
thrown in /usercode/file.php on line 18
*/
__set_state 调用var_export()导出类时,此静态方法会被调用。
- 当调用 var_export() 导出类时,此静态方法会被自动调用。
- 本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性。
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
}
$person = new Person('小明'); // 初始赋值
var_export($person);
/*
Person::__set_state(array(
'sex' => '男',
'name' => '小明',
'age' => 25,
))
*/
<?php
class Person
{
public $sex;
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
public static function __set_state($an_array)
{
$a = new Person();
$a->name = $an_array['name'];
return $a;
}
}
$person = new Person('小明'); // 初始赋值
$person->name = '小红';
var_export($person);
/*
Person::__set_state(array(
'sex' => '男',
'name' => '小红',
'age' => 25,
))
*/
__clone
- 使用关键字 clone 来克隆对象
- 如果想在克隆后改变原对象的内容,需要在类中添加一个特殊的 __clone() 方法来重写原本的属性和方法
- __clone() 方法只会在对象被克隆的时候自动调用
<?php
class Person
{
private $sex;
private $name;
private $age;
// 构造方法
public function __construct($name="", $age=25, $sex='男')
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// 成员方法
public function say()
{
echo "我的名字叫:".$this->name;
echo " 我的年龄是:".$this->age."<br />";
}
public function __clone()
{
$this->name = "我是假的".$this->name;
$this->age = 30;
}
}
$p1 = new Person("张三", 20);
$p1->say(); // 我的名字叫:张三 我的年龄是:20
$p2 = clone $p1;
$p2->say(); // 我的名字叫:我是假的张三 我的年龄是:30
- 单例类的加强:禁止克隆
- 在PHP中,为防止对单例类对象的克隆来打破单例类的上述实现形式,通常还为其提供一个空的私有 (private修饰的)__clone()方法。
<?php
class SingetonBasic
{
// 静态变量要私有化,防止类外修改,保存对象实例
private static $instance;
// 构造函数私有化,类外不能直接新建对象
private function __construct(){}
// 禁止使用关键字clone
private function __clone(){}
// 外部开放方法创建对象
public static function getInstance()
{
if (! self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
}
$a = SingetonBasic::getInstance();
$b = SingetonBasic::getInstance();
var_dump($a === $b); //结果为:boolean true a和b指向的是同一个对象
$c = clone $a; // 报错
/*
Fatal error: Uncaught Error: Call to private SingetonBasic::__clone() from context '' in /usercode/file.php:29 Stack trace: #0 {main} thrown in /usercode/file.php on line 29
*/
- 浅克隆:只是克隆对象中的非对象非资源数据,即对象中属性存储的是对象类型,则会出现克隆不完全
<?php
class B
{
public $val = 10;
}
class A
{
public $val = 20;
public $b;
public function __construct()
{
$this->b = new B();
}
}
$oa = new A();
$ob = clone $oa;
$oa->val = 30;
$oa->b->val = 40;
echo "<pre>";
var_dump($oa);
/*
object(A)#1 (2) {
["val"]=>
int(30)
["b"]=>
object(B)#2 (1) {
["val"]=>
int(40)
}
}
*/
var_dump($ob);
/*
object(A)#3 (2) {
["val"]=>
int(20)
["b"]=>
object(B)#2 (1) {
["val"]=>
int(40) // 这里本应该是10 结果出现40导致克隆不完全 $this->b 还用引用 A对象
}
}
*/
- 深克隆:一个对象的所有属性数据都彻底的复制,需要使用魔术方法__clone(),并在里面实现深度克隆
<?php
class B
{
public $val = 10;
}
class A
{
public $val = 20;
public $b;
public function __construct()
{
$this->b = new B();
}
public function __clone()
{
$this->b = clone $this->b;
}
}
$oa = new A();
$ob = clone $oa;
$oa->val = 30;
$oa->b->val = 40;
echo "<pre>";
var_dump($oa);
/*
object(A)#1 (2) {
["val"]=>
int(30)
["b"]=>
object(B)#2 (1) {
["val"]=>
int(40)
}
}
*/
var_dump($ob);
/*
object(A)#3 (2) {
["val"]=>
int(20)
["b"]=>
object(B)#4 (1) {
["val"]=>
int(10)
}
}
*/
__debuginfo 打印所需调试信息
- 该方法在PHP 5.6.0及其以上版本才可以用,如果你发现使用无效或者报错,请查看你的版本
class C
{
private $prop;
public function __construct($val)
{
$this->prop = $val;
}
public function __debugInfo()
{
return [
'propSquared' => $this->prop ** 2,
];
}
}
var_dump(new C(42));
/*
object(C)#1 (1) { ["propSquared"]=> int(1764) }
*/