美文网首页
PHP魔术方法使用

PHP魔术方法使用

作者: 爱折腾的傻小子 | 来源:发表于2020-12-22 10: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) }
*/

相关文章

  • php魔术方法

    PHP 中被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术...

  • PHP面试梳理

    PHP php 魔术方法 、魔术常量 php cli autoload , spl_autoload compos...

  • PHP魔术方法使用

    支持魔术方法 __construct 构造方法 当一个对象被实例化的时候会被首先调用 在PHP框架种依赖注入以及中...

  • PHP魔术方法

    PHP魔术方法

  • PHP魔术方法

    魔术方法(Magic methods) PHP中把以两个下划线__开头的方法称为魔术方法,这些方法在PHP中充当了...

  • 规则引擎升级版(直接能跑)

    利用了php的魔术方法

  • PHP中常用魔术函数及魔术常量总结

    PHP中会使用到很多魔术函数和魔术常量:以Yii2.0中调用model中方法来举例: __construct()实...

  • PHP 魔术方法使用总结

    1.__construct(),构造函数,构建对象时被调用。 2.__destruct(),析构函数,明确销毁对象...

  • PHP魔术方法的使用

    Object类 Object类中包含_set()和_get()方法后,就可以访问到未定义的属性 Object类中包...

  • php魔术方法总结使用

    (1)_clone克隆 实例化对象的赋值是传递的对象的地址索引,两个变量指向同一个对象,属于浅拷贝,而clone与...

网友评论

      本文标题:PHP魔术方法使用

      本文链接:https://www.haomeiwen.com/subject/pflrnktx.html