构造函数和析构函数

构造函数:
PHP5可以在类中使用__construct()定义一个构造函数,具有构造函数的类,会在每次对象创建的时候调用该函数,因此常用来在对象创建的时候进行一些初始化工作。

  • 构造函数可以接受参数,能够在创建对象时赋值给对象属性
  • 构造函数可以调用类方法或其他函数
  • 构造函数可以调用其他类的构造函数
  • PHP中的子类的构造函数不会主动调用父类的构造函数,要显示的使用parent::__construct()调用:
  • 也可以使用类名直接调用构造函数
private $name;
public function __construct($name){
    $this->setName($name);
    parent::__construct($name);//调用父类构造函数
    Animal::__construct($name); // 调用Animal构造函数    
}

析构函数
析构函数指的是当某个对象的所有引用被删除,或者对象被显式的销毁时会执行的函数。

  • 析构函数是在销毁对象时自动调用,不能显示的调用
  • 析构函数不能带参数
  • 要执行父类的析构函数,必须在子类的析构函数中显示调用parent::__destruct()

以下几种情况可能会调用析构函数:页面加载完毕、unset()、变量引用指向别的对象或值时

<?php
class test{
    function __destruct(){
        echo "当对象销毁时会调用!!!";
    }
}
$a = $b = $c = new test();
unset($b);
?>

私有属性的设置获取

实际应用中,经常会把类的属性设置为私有(private),那么需要对属性进行访问时,就会变得麻烦。虽然可以将对属性的访问写成一个方法来实现,但 PHP 提供了一些特殊方法来方便此类操作。

_set() 方法用于设置私有属性值;在类里面使用了 __set() 方法后,当使用 $p1->name = "张三"; 这样的方式去设置对象私有属性的值时,就会自动调用 __set() 方法来设置私有属性的值。

function __set($property_name, $value){ 
    $this->$property_name = $value; 
}

_get() 方法用于获取私有属性值

function __get($property_name, $value){ 
    return isset($this->$property_name) ? $this->$property_name : null;
}

_isset() 方法用于检测私有属性是否被设置;如果对象里面成员是公有的,可以直接使用 isset() 函数。如果是私有的成员属性,那就需要在类里面加上一个 __isset() 方法:

private function __isset($property_name){
    return isset($this->$property_name);
}

_unset() 方法用于删除私有属性;同 isset() 函数一样,unset() 函数只能删除对象的公有成员属性,当要删除对象内部的私有成员属性时,需要使用__unset() 方法:

private function __unset($property_name){
    unset($this->$property_name);
}

__toString()方法

__toString() 是快速获取对象的字符串信息的魔术方法,它在直接输出对象引用时自动调用。
__toString() 的作用:
当我们调试程序时,需要知道是否得出正确的数据,比如打印一个对象时,看看这个对象都有哪些属性,其值是什么。如果定义了toString方法,在打印对象体时,就会自动调用该方法,格式化输出这个对象所包含的数据。

<?php
class Person{
    private $name = "";
    function __construct($name = ""){            // 定义构造函数
        $this->name = $name;
    }
    public function say(){                       // 定义公共方法
        echo "hello,".$this->name."!";
    }
    function __toString(){                       // 定义获取字符串的魔术方法
        return "__toString:".$this->name.'!';
 }
}
$blog = new Person('blog');
$blog->say();                                    // 输出:hello,blog!
echo $blog;                                      // 输出:__toString:blog!
?>

如果没有定义__toString() 方法,则执行echo语句时,会报错。
Catchable fatal error: Object of class Person could not be converted to string in /usercode/file.php on line 16

__clone()方法

clone关键字
用于克隆一个完全一样的对象,克隆之后两个对象互不干扰。

适用场景:在编码过程中,有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。

// 使用clone关键字之前的复制效果实例
//
// 定义一个类,类中定义一个公共成员变量
class TestClass{
    public $value;
}

$obj = new TestClass();  // 实例化TestClass类的对象
$obj->value = 'qq';      // 给成员变量赋值
var_dump($obj);          // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
$obj2 = $obj;            // 将$obj赋值给$obj2变量,等同于复制一个$obj
$obj2->value = 'zz';     // 更改$value的值 
var_dump($obj);          // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }
var_dump($obj2);         // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }

从以上的例子可以看出来当实例化对象赋值给其他变量时,改变实例化对象和改变赋值对象,另一方都会随之改变。这是因为普象的赋值,仅仅是把新对象指向对象存储的地址(即 $obj给了$obj2它的地址,就和引用一样,$obj2指向了$obj,所以导致两者所执行的操作将会收到影响)。

// 使用clone关键字之后的复制效果实例
//
// 定义一个类,类中定义一个公共成员变量
class TestClass
{
    public $value;
}

$obj = new TestClass();  // 实例化TestClass类的对象
$obj->value = 'qq';      // 给成员变量赋值
var_dump($obj);          // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
$obj2 = clone $obj;      // 使用clone关键字克隆$obj
$obj2->value = 'zz';     // 更改$value的值 
var_dump($obj);          // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
var_dump($obj2);         // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }

可以看出,使用clone关键字之后,更改$obj2的成员变量值并不会对$obj的成员变量产生影响,克隆之后的两个对象$obj和$obj2是互不影响,互相独立的。

__clone方法
用于重写原本的属性和方法;__clone() 方法只会在对象被克隆的时候自动调用。

__clone()方法对一个对象实例进行的浅复制,对象内的基本数值类型进行的是传值复制,而对象内的对象型成员变量,如果不重写__clone方法,显式的clone这个对象成员变量的话,这个成员变量就是传引用复制,而不是生成一个新的对象。

// 定义age类
class Age{
    public $balance;
    public function __construct($balance){
        $this->balance = $balance;
    }
}
//
// 定义第二个类,类中包括两个公共成员以及构造函数、克隆方法
class IndexService{
    public $age;     // 对象属性成员变量 
    public $name;    // 非对象属性成员变量

    public function __construct($name, Age $age){
        $this->name = $name;
        $this->age = $age;
    }
    // 使用clone的时候触发
    
    public function __clone()
    {
        $this->name = 'name:'.$this->name;     
        $this->age = clone $this->age; // 这个对象会被强制复制,这样就不会指向原来的对象。
    }
}

// 运行
$obj = new IndexService('peter',new Age(20));
$obj2 = clone $obj;
$obj2->age->balance = 10;
var_dump($obj);  
var_dump($obj2); // age对象中的balance值更改为10,name值也被更改。

02dcaea6a562a59a69e4a01b3e9a638f_511x254.png

注意: __clone()方法中的clone操作只能用于对象,如果用于非对象属性,将会报错:# 致命错误: __clone method called on non-object。
例子中的age就是一个对象。

以上例子中,$age对象被强制复制,不会指向原来的对象,所以修改其中的值之后,并不会对原来对象有影响;如果不加上$this->age = clone $this->age;这行代码,运行结果则变成这样:两个对象的age对象的balance值都改成了10。
2.png

__call()方法

__call() 方法用于监视错误的方法调用。该方法在调用的方法不存在时会自动调用,程序会继续执行下去。
该方法有两个参数,第一个参数会自动接收不存在的方法名,第二个参数则以数组的方式接收不存在方法的多个参数。

<?php
    class Test
    {
        function __call($function_name, $args)
        {
            echo "你所调用的函数:$function_name(参数:<br />";
            var_dump($args);
            echo ")不存在!";
        }
    }
    // 调用不存在的方法
    $p1=new Test();
    $p1->test(2,"test");
    //你所调用的函数:test(参数:array(2) {[0]=> int(2) [1]=>string(4) "test"})不存在!

类的自动加载

php实现类文件自动载入有两种办法:

  • 魔术方法:__autoload();
  • SPL标准库提供的spl_autoload_register();

__autoload()
__autoload()是php中的一个魔术方法,在代码中当调用不存在的类的时候会自动调用。

在开发过程中可以使用include或者require函数来引入需要调用的类文件,但是如果需要调用的类很多的时候,对应的include或者require也会很多,会造成代码冗余,而且每次执行都要加载这些文件,会白白占用内存,其次当删除了某个类文件时,也要对应的修改include或者require语句。
使用__autoload()魔术方法来代替include或者require语句,则可以避免大量的代码冗余,也可以提高执行效率。
在 Test.php文件中我们调用 类Test1 和 类Test2,由于我们没有显式的引用类文件,那么系统就会自动调用 __autoload() 方法。

//Test.php文件
function __autoload($class){
    if(file_exists($class.".php")){
        require_once($class.".php");
    }else{
        die("文件不存在!");
    }
}

Test1::test();
Test2::test();

__autoload()的缺点:

  1. 一个文件中只允许有一个__autoload(),当引入的项目中有其他__autoload()时,则会发生冲突。
  2. 当项目中的类根据不同的用处放在不同的文件夹中,此时要分别调用对应的类,会十分困难。

spl_autoload_register()
该函数可以注册任意数量的自动加载器,当使用未被定义的类和接口时自动去加载。

//Test.php文件
function my_autoload1($class){
    if(file_exists("classes/".$class.".php")){
        require_once("classes/".$class.".php");
    }else{
        die("文件不存在!");
    }
}
function my_autoload2($class){
    if(file_exists("core/".$class.".php")){
        require_once("core/".$class.".php");
    }else{
        die("文件不存在!");
    }
}

//将加载函数注册到PHP中
spl_autoload_register("my_autoload1");
spl_autoload_register("my_autoload2");

Test1::test();
Test2::test();

鸣谢:看云chunyu

最后修改:2022 年 06 月 19 日 04 : 07 PM
对您有帮助的话,请赏包辣条吧 ^~^