构造函数以及析构函数在PHP中须要注意的地方

基本上全部的编程语言在类中都会有构造函数和析构函数的概念。构造函数是在函数实例建立时能够用来作一些初始化的工做,而析构函数则能够在实例销毁前作一些清理工做。相对来讲,构造函数咱们使用得很是多,而析构函数则通常会用在释放资源上,好比数据库连接、文件读写的句柄等。php

构造函数与析构函数的使用

咱们先来看看正常的构造与析构函数的使用:git

class A
{
    public $name;
    public function __construct($name)
    {
        $this->name = $name;
        echo "A:构造函数被调用,{$this->name}", PHP_EOL;
    }

    public function __destruct()
    {
        echo "A:析构函数被调用,{$this->name}", PHP_EOL;
    }
}

$a = new A('$a');
echo '-----', PHP_EOL;

class B extends A
{
    public function __construct($name)
    {
        $this->name = $name;
        parent::__construct($name);
        echo "B:构造函数被调用,{$this->name}", PHP_EOL;
    }

    public function __destruct()
    {
        parent::__destruct();
        echo "B:析构函数被调用,{$this->name}", PHP_EOL;
    }
}

class C extends A
{
    public function __construct($name)
    {
        $this->name = $name;
        echo "C:构造函数被调用,{$this->name}", PHP_EOL;
    }

    public function __destruct()
    {
        echo "C:析构函数被调用,{$this->name}", PHP_EOL;
    }
}

class D extends A
{

}
// unset($a); // $a的析构提早
// $a = null; // $a的析构提早
$b = new B('$b');

$c = new C('$c');

$d = new D('$d');

echo '-----', PHP_EOL;exit;

// A:构造函数被调用,$a
// -----
// A:构造函数被调用,$b
// B:构造函数被调用,$b
// C:构造函数被调用,$c
// A:构造函数被调用,$d
// -----
// A:析构函数被调用,$d
// C:析构函数被调用,$c
// A:析构函数被调用,$b
// B:析构函数被调用,$b
// A:析构函数被调用,$a
复制代码

上面的代码是否是有一些内容和咱们的预期不太同样?没事,咱们一个一个来看:github

  • 子类若是重写了父类的构造或析构函数,若是不显式地使用parent::__constuct()调用父类的构造函数,那么父类的构造函数不会执行,如C类
  • 子类若是没有重写构造或析构函数,则默认调用父类的
  • 析构函数若是没显式地将变量置为NULL或者使用unset()的话,会在脚本执行完成后进行调用,调用顺序在测试代码中是相似于栈的形式先进后出(C->B->A,C先被析构),但在服务器环境中则不必定,也就是说顺序不必定固定

析构函数的引用问题

当对象中包含自身相互的引用时,想要经过设置为NULL或者unset()来调用析构函数可能会出现问题。数据库

class E
{
    public $name;
    public $obj;
    public function __destruct()
    {
        echo "E:析构函数被调用," . $this->name, PHP_EOL;
        echo '-----', PHP_EOL;
    }
}

$e1 = new E();
$e1->name = 'e1';
$e2 = new E();
$e2->name = 'e2';

$e1->obj = $e2;
$e2->obj = $e1;
复制代码
相似于这样的代码,$e1和$e2都是E类的对象,他们又各自持有对方的引用。其实简单点来讲的话,本身持有本身的引用都会出现相似的问题。
$e1 = new E();
$e1->name = 'e1';
$e2 = new E();
$e2->name = 'e2';

$e1->obj = $e2;
$e2->obj = $e1;
$e1 = null;
$e2 = null;
// gc_collect_cycles();

$e3 = new E();
$e3->name = 'e3';
$e4 = new E();
$e4->name = 'e4';

$e3->obj = $e4;
$e4->obj = $e3;
$e3 = null;
$e4 = null;

echo 'E destory', PHP_EOL;
复制代码

若是咱们不打开gc_collect_cycles()那一行的注释,析构函数执行的顺序是这样的:编程

// 不使用gc回收的结果
// E destory
// E:析构函数被调用,e1
// -----
// E:析构函数被调用,e2
// -----
// E:析构函数被调用,e3
// -----
// E:析构函数被调用,e4
// -----
复制代码

若是咱们打开了gc_collect_cycles()的注释,析构函数的执行顺序是:设计模式

// 使用gc回收后结果
// E:析构函数被调用,e1
// -----
// E:析构函数被调用,e2
// -----
// E destory
// E:析构函数被调用,e3
// -----
// E:析构函数被调用,e4
// -----
复制代码

能够看出,必需要让php使用gc回收一次,肯定对象的引用都被释放了以后,类的析构函数才会被执行。引用若是没有释放,析构函数是不会执行的。bash

构造函数的低版本兼容问题

在PHP5之前,PHP的构造函数是与类名同名的一个方法。也就是说若是我有一个F类,那么function F(){}方法就是它的构造函数。为了向低版本兼容,PHP依然保留了这个特性,在PHP7之后若是有与类名同名的方法,就会报过期警告,但不会影响程序执行。服务器

class F
{
    public function f() 
    {
        // Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; F has a deprecated constructor 
        echo "F:这也是构造函数,与类同名,不区分大小写", PHP_EOL;
    }
    // function F(){
    //     // Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; F has a deprecated constructor 
    //     echo "F:这也是构造函数,与类同名", PHP_EOL;
    // }
    // function __construct(){
    //     echo "F:这是构造函数,__construct()", PHP_EOL;
    // }
}
$f = new F();
复制代码

若是__construc()和类同名方法同时存在的话,会优先走__construct()。另外须要注意的是,函数名不区分大小写,因此F()和f()方法是同样的都会成为构造函数。同理,由于不区分大小写,因此f()和F()是不能同时存在的。固然,咱们都不建议使用类同名的函数来作为构造函数,毕竟已是过期的特性了,说不定哪天就被取消了。微信

构造函数重载

PHP是不运行方法的重载的,只支持重写,就是子类重写父类方法,但不能定义多个同名方法而参数不一样。在Java等语言中,重载方法很是方便,特别是在类实例化时,能够方便地实现多态能力。编程语言

$r1 = new R(); // 默认构造函数
$r2 = new R('arg1'); // 默认构造函数 一个参数的构造函数重载,arg1
$r3 = new R('arg1', 'arg2'); // 默认构造函数 两个参数的构造函数重载,arg1,arg2
复制代码

就像上述代码同样,若是你尝试定义多个__construct(),PHP会很直接地告诉你运行不了。那么有没有别的方法实现上述代码的功能呢?固然有,不然咱也不会写了。

class R
{
    private $a;
    private $b;
    public function __construct()
    {
        echo '默认构造函数', PHP_EOL;
        $argNums = func_num_args();
        $args = func_get_args();
        if ($argNums == 1) {
            $this->constructA(...$args);
        } elseif ($argNums == 2) {
            $this->constructB(...$args);
        }
    }
    public function constructA($a)
    {
        echo '一个参数的构造函数重载,' . $a, PHP_EOL;
        $this->a = $a;
    }
    public function constructB($a, $b)
    {
        echo '两个参数的构造函数重载,' . $a . ',' . $b, PHP_EOL;
        $this->a = $a;
        $this->b = $b;
    }
}
$r1 = new R(); // 默认构造函数
$r2 = new R('arg1'); // 默认构造函数 一个参数的构造函数重载,arg1
$r3 = new R('arg1', 'arg2'); // 默认构造函数 两个参数的构造函数重载,arg1,arg2
复制代码

相对来讲比Java之类的语言要麻烦一些,可是也确实是实现了相同的功能哦。

构造函数和析构函数的访问限制

构造函数和析构函数默认都是public的,和类中的其余方法默认值同样。固然它们也能够设置成private和protected。若是将构造函数设置成非公共的,那么你将没法实例化这个类。这一点在单例模式被普遍应用,下面咱们直接经过一个单例模式的代码看来。

class Singleton
{
    private static $instance;
    public static function getInstance()
    {
        return self::$instance == null ? self::$instance = new Singleton() : self::$instance;
    }

    private function __construct()
    {

    }
}

$s1 = Singleton::getInstance();
$s2 = Singleton::getInstance();
echo $s1 === $s2 ? 's1 === s2' : 's1 !== s2', PHP_EOL;

// $s3 = new Singleton(); // Fatal error: Uncaught Error: Call to private Singleton::__construct() from invalid context
复制代码

当$s3想要实例化时,直接就报错了。关于单例模式为何要让外部没法实例化的问题,咱们能够看看以前的设计模式系统文章中的单例模式

总结

没想到咱们每天用到的构造函数还能玩出这么多花样来吧,平常在开发中比较须要注意的就是子类继承时对构造函数重写时父类构造函数的调用问题以及引用时的析构问题。

测试代码:github.com/zhangyue050…

参考文档: www.php.net/manual/zh/l… www.php.net/manual/zh/l… www.php.net/manual/zh/l… www.php.net/manual/zh/l…



===============

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【DarkMatterZyCoder/149844827】免费得PHP、项目管理学习资料
相关文章
相关标签/搜索