PHP魔法方法的一些总结

PHP魔法方法的一些总结

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法

魔术方法的作用

反序列化漏洞的成因:

反序列化过程中,unserialize()接收的**值 (字符串)**可控 ,通过更改这个值 (字符串),得到所需要的代码。

通过调用方法,触发代码执行。

魔术方法

在这里插入图片描述

__construct()

构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法,如给成员属性初始化值

 <?php
highlight_file(__FILE__);
class User {
    public $username;
    public function __construct($username) {
        $this->username = $username;
        echo "触发了构造函数1次" ;
    }
}
$test = new User("benben");   //在此处触发构造函数
?>

#结果
触发了构造函数1

__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法

  • 实例化对象结束后,代码运行完会销毁,触发析构函数

  • 在反序列化过程中会触发析构函数

    • 反序列化得到的是对象,用完后会销毁
<?php
highlight_file(__FILE__);
class User {
    public function __destruct()
    {
        echo "触发了析构函数1次"."<br />" ;
    }
}
$test = new User("benben"); //触发一次
$ser = serialize($test);
unserialize($ser); //触发一次

?> 

#结果
触发了析构函数1次
触发了析构函数1

__sleep()

序列化serialize() 函数会检查类中是否存在一个魔术方法 sleep()。如果存在,该方法会先被调用,然后才执行序列化操作

此功能可以用于清理对象(可以用来选择需要序列化的属性),并返回这些属性的数组。这些属性将被存储在序列化字符串中。

如果该方法返回一个空数组或者未返回一个数组(可能是写了其他代码,比如拿来做命令执行),那么所有属性都不会被序列化, NULL 被序列化(序列化的结果是N),并产生一个 ENOTICE级别的错误。

如果没有定义 ​__sleep()​ 方法,那么所有属性都会被序列化。

注意:__sleep()执行在前,序列化执行在后

 <?php
highlight_file(__FILE__);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username, $nickname, $password)    {
        $this->username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }
    public function __sleep() {
        return array('username', 'nickname');  //返回了包含username,nickname的数组,不在数组中的password就不会被序列化
    }
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>

#结果
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";} 

可以看到,序列化后的字符串包含了 username,nickname属性的值。这些值可以在反序列化时被使用。

还可以用来执行别的命令

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username, $nickname, $password)    {
        $this->username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }
    public function __sleep() {
        system($this->username);
    }
}
$cmd = "id";
$user = new User($cmd, 'b', 'c');
echo serialize($user);
?> 

#结果
uid=33(www-data) gid=33(www-data) groups=33(www-data) N;  #结果后面有个N就是NULL被实例化了,应为__sleep没有返回属性数组

如果返回的数组里面不包含存在的属性呢

 public $username;
 public $nickname;
 private $password;

 public function __sleep() {
        return array(1,2,3);
    }

#结果
O:4:"User":0:{}  #属性为空

__wakeup()

反序列化unserialize()时会检查是否存在一个__wakeup()方法。

如果存在,则会先调用_wakeup() 方法,预先准备对象需要的资源。

预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

注意:反序列化先执行,__wakeup()后执行

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup() {
        $this->password = $this->username;  #用来给password赋值
    }
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';  # 可以看到序列化字符串里没有password的值
var_dump(unserialize($user_ser));
?>

#结果
object(User)#1 (4) { 
	["username"]=> string(1) "a" 
	["nickname"]=> string(1) "b" 
	["password":"User":private]=> string(1) "a"  //password被成功赋上username的值   
	["order":"User":private]=> NULL 
}    

用来命令执行

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup() {  #在反序列化后先执行,读取username的值
        system($this->username);
    }c
}
$user_ser = '?benben=O:4:"User":1:{s:8:"username";s:2:"id";}';  #给username赋值
unserialize($user_ser);
?> 

__toString()

当一个对象被当作字符串输出的时候,会触发__toString魔法函数,如果没有设置__toString,php会抛出致命错误,提示无法将对象转换成字符串

以下几种情况会触发

  1. 当使用echo​或print​输出一个对象时,PHP会自动调用该对象的__toString()​方法,将方法返回的字符串输出到屏幕上。
  2. 当将一个对象转换成字符串时,PHP也会自动调用该对象的__toString()​方法,将方法返回的字符串作为对象的字符串表示形式。
  3. 当使用字符串拼接运算符.​连接一个对象和一个字符串时,PHP会自动将该对象转换成字符串,并调用其__toString()​方法,然后将字符串拼接起来。
  4. 当使用sprintf​函数或printf​函数格式化字符串时,如果格式化字符串中包含一个对象占位符%s​,PHP会自动将该对象转换成字符串,并调用其__toString()​方法,然后将字符串插入到格式化字符串中

调用对象可以使用print_r​或者var_dump

示例

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __toString()
         {
             return '格式不对,输出不了!';
          }
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;   //触发错误
?>

#结果
User Object ( [benben] => this is test!! )
格式不对,输出不了!

__invoke()

它允许一个对象像函数一样被调用。当一个对象被作为函数调用时,PHP会自动调用这个对象的__invoke()方法来处理这个调用。

以下几种方式被触发:

  1. 将一个对象作为函数调用:当一个对象被作为函数调用时(就是在对象变量名后加上()),PHP会自动调用这个对象的__invoke()方法来处理这个调用。

     <?php
    highlight_file(__FILE__);
    error_reporting(0);
    class User {
        var $benben = "this is test!!";
             public function __invoke()
             {
                 echo  '它不是个函数!';
              }
    }
    $test = new User() ;
    echo $test ->benben;
    echo "<br />";
    echo $test() ->benben;   //把对象加上(),当成函数调用
    ?>
    
    #结果
    this is test!!
    它不是个函数!
    
  2. 使用call_user_func()或call_user_func_array()函数:这两个函数可以将一个对象作为第一个参数传递,并调用该对象的__invoke()方法。

    例如:

    class MyObject {
        public function __invoke($arg1, $arg2) {
            return $arg1 + $arg2;
        }
    }
    
    $obj = new MyObject();
    $result = call_user_func($obj, 1, 2); // 相当于调用 $obj->__invoke(1, 2)
    echo $result; // 输出 3
    

例如:

class MyObject {
    public function __invoke($arg1, $arg2) {
        return $arg1 + $arg2;
    }
}

$obj = new MyObject();
$result = call_user_func($obj, 1, 2); // 相当于调用 $obj->__invoke(1, 2)
echo $result; // 输出 3
  1. 使用回调函数:当一个对象被用作回调函数时,PHP会自动调用这个对象的__invoke()方法来处理回调。

    例如:

    class MyObject {
        public function __invoke($arg1, $arg2) {
            return $arg1 + $arg2;
        }
    }
    
    $obj = new MyObject();
    $result = array_reduce([1, 2, 3], $obj); // 相当于调用 $obj->__invoke(1, 2), $obj->__invoke(3, 3)
    echo $result; // 输出 9
    

在这个例子中,$obj​对象被用作回调函数,并作为第二个参数传递给了array_reduce()函数。这个函数会自动调用$obj​对象的__invoke()方法来处理回调,将数组中的元素两两相加。最后,$result变量将保存这个结果,并输出为9。

__call()

用于在对象调用不存在的方法时自动调用。通俗来说,就是当我们调用一个对象中不存在的方法时,PHP会自动调用__call()​方法,并将调用的方法名和参数传递给它

注意:__call($name,$arg)​有两个形参,其中第二个形参为数组

示例

class MyClass {
  public function __call($name, $arguments) {
    echo "调用的方法名为:$name,参数为:";
    print_r($arguments);
  }
}

$obj = new MyClass();
$obj->Method("test", 123); //调用一个不存在的函数

#结果
调用的方法名为:Method,参数为:Array ( [0] => test [1] => 123 )

__callStatic

它允许在静态上下文中调用一个不存在的静态方法。通俗来说,就是当你在一个类中调用一个不存在的静态方法时,PHP会自动调用__callStatic方法来处理这个调用。

注意:__call用于动态调用实例方法,而__callStatic用于动态调用静态方法。其它与__call一致

示例

class Test {
    public static function __callStatic($name, $arguments) {
        echo "调用静态方法 '$name',参数为:";
        print_r($arguments);
    }
}

Test::foo('bar', 'baz');  //调用不存在的静态方法

#结果
调用静态方法 'foo',参数为:Array([0] => bar [1] => baz)

知识补充

->​和::​都是PHP中用于访问类成员的运算符,但它们的作用不同:

  1. ->​运算符用于访问类的实例成员,即对象的属性和方法。它需要在一个实例化的对象上使用。还可以访问数组元素(仅限关联数组)
  2. ::​运算符用于访问类的静态成员,即静态属性和静态方法。它需要在类名上使用。

__get()

用于在对象中访问一个不存在的属性时自动调用,会传递一个参数,即属性名。它的作用是在对象中获取一个不存在的属性时,可以自动执行一些代码,比如从数据库中读取数据并返回,或者返回一个默认值等。

注意:__get($arg)有一个形参

示例

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    public $var1;
    public function __get($arg1)
    {
        echo  $arg1;
    }
}
$test = new User() ;
$test ->var2;   //这里访问了一个不存在的属性触发了__get魔法方法,并将var2这个值传给了__get
?>

#结果
var2 

__set()

当我们在对象中设置一个不存在的属性时,__set()方法会被自动调用,并且会传递两个参数:属性名和属性值。

注意:只有属性名而没有属性值,那么__set()方法就无法被调用。__set()方法只有在你尝试设置一个不存在的属性时才会被调用,并且需要传递属性名和属性值两个参数。

示例

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    public $var1;
    public function __set($arg1 ,$arg2)
    {
        echo  $arg1.','.$arg2;
    }
}
$test = new User() ;
$test ->var2=1;  //这里给不存在的属性var2赋值触发__set方法
?>

var2,1 

__isset()

用于在对象中检查一个属性是否存在。当在对象中调用isset()函数或empty()函数来检查一个不存在的属性时,__isset()方法会被自动调用,并且会传递一个参数,即属性名。如果不定义__isset()方法,当尝试检查一个不存在的属性时,PHP会返回false。

示例

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    private $var;   //私有属性,无法被外面获取
    public function __isset($arg1 )
    {
        echo  $arg1;
    }
}
$test = new User() ;
isset($test->var);  //检查属性是否存在,var是私有属性无法被检查到,触发__isset方法,且将var传递给它
?>

#结果
var 

__unset()

__unset是一个魔术方法,用于在对象中删除指定的属性。**当使用unset()函数删除一个对象的属性时,实际上是调用了对象的__unset()方法,并将要删除的属性名作为参数传递给__unset()方法。**如果没有定义__unset()方法,那么默认情况下会抛出一个警告。通过定义__unset()方法,可以在对象中自定义删除属性的行为。

示例

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    private $var;
    public function __unset($arg1)
    {
        echo  $arg1;
    }
}
$test = new User() ;
unset($test->var);  #触发__unset,并将属性名传给它
?>

#结果
var 

这里可能会有疑问,私有属性怎么能被类外部访问并删除呢,实际上删除属性是通过类内部的__unset魔法方法来完成的

__clone()

用于在PHP中复制对象。当使用clone关键字复制一个对象时,实际上是调用了对象的__clone()方法。如果没有定义__clone()方法,那么默认情况下会创建一个浅拷贝(shallow copy)的对象,即**只复制对象的属性,而不复制属性所引用的对象:**如果原对象中某个属性引用了其他对象,那么新对象中对应的属性也会引用同一个对象。如果我们希望新对象的属性也引用新的对象,那么就需要在__clone()方法中进行处理。

 <?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    private $var;
    public function __clone( )
    {
        echo  "__clone test";
    }
}
$test = new User() ;
$newclass = clone($test)  //复制新对象
?>
__clone test


此为个人学习笔记,如果错误请大佬指出,希望也对你们有帮助

参考资料

橙子科技工作室的个人空间_哔哩哔哩_bilibili

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值