PHP 8.0.0 Released!

构造函数和析构函数

构造函数

__construct ( mixed ...$values = "" ) : void

PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

Note: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。

Example #1 继承中的构造函数

<?php
class BaseClass {
    function 
__construct() {
        print 
"In BaseClass constructor\n";
    }
}

class 
SubClass extends BaseClass {
    function 
__construct() {
        
parent::__construct();
        print 
"In SubClass constructor\n";
    }
}

class 
OtherSubClass extends BaseClass {
    
// 继承 BaseClass 的构造函数
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();
?>

与其它方法不同,当 __construct() 被与父类 __construct() 具有不同参数的方法覆盖时,PHP 不会产生一个 E_STRICT 错误信息。

自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。不使用命名空间中的类则不受影响。 构造函数是一个普通的方法,在对应对象实例化时自动被调用。 因此可以定义任何数量的参数,可以是必选、可以有类型、可以有默认值。 构造器的参数放在类名后的括号里调用。

Example #2 使用构造器参数

<?php
class Point {
    protected 
int $x;
    protected 
int $y;

    public function 
__construct(int $xint $y 0) {
        
$this->$x;
        
$this->$y;
    }
}

// 两个参数都传入
$p1 = new Point(45);
// 仅传入必填的参数。 $y 会默认取值 0。
$p2 = new Point(4);
// 使用命名参数(PHP 8.0 起):
$p3 = new Point(y5x4);
?>

如果一个类没有构造函数,以及构造函数的参数不是必填项时,括号就可以省略。

旧式风格的构造器

PHP 8.0.0 之前,全局命名空间内的类如果有一个同名的方法,则会解析为旧式风格的构造器。 虽然函数能被当作构造器,但该语法已被废弃,并会导致 E_DEPRECATED 错误。 如果 __construct() 和同名方法同时存在时, 会调用 __construct()

以下两种情况时,与类同名的方法不再有特殊意义:命名空间中的类、PHP 8.0.0 起的任何类。

新代码中要使用 __construct()

构造器属性提升

PHP 8.0.0 起,构造器的参数也可以相应提升为类的属性。 构造器的参数赋值给类属性的行为很普遍,否则无法操作。 而构造器提升的功能则为这种场景提供了便利。 因此上面的例子可以用以下方式重写:

Example #3 使用构造器属性提升

<?php
class Point {
    public function 
__construct(protected int $x, protected int $y 0) {
    }
}

当构造器参数带访问控制(visibility modifier)时,PHP 会同时把它当作对象属性和构造器参数, 并赋值到属性。 构造器可以是空的,或者包含其他语句。 参数值赋值到相应属性后执行正文中额外的代码语句。

并非所有参数都需要提升。可以混合提升或不提升参数作为属性,也不需要按顺序。 提升后的参数不影响构造器内代码调用。

Note:

对象属性的类型不能为 callable 以避免为引擎带来混淆。 因此提升的参数也不能是 callable。 其他任意 类型声明 是允许的。

Note:

放在构造器提升参数里的属性会同时复制为属性和参数。

Static 创造方法

在 PHP 中每个 class 只能有一个构造器。 然而有些情况下,需要用不同的输入实现不同的方式构造对象。 这种情况下推荐使用 static 方法包装构造。

Example #4 使用 static 创造方法

<?php
class Product {

    private ?
int $id;
    private ?
string $name;

    private function 
__construct(?int $id null, ?string $name null) {
        
$this->id $id;
        
$this->name $name;
    }

    public static function 
fromBasicData(int $idstring $name): static {
        
$new = new static($id$name);
        return 
$new;
    }

    public static function 
fromJson(string $json): static {
        
$data json_decode($json);
        return new static(
$data['id'], $data['name']);
    }

    public static function 
fromXml(string $xml): static {
        
// 此处放置自己的代码逻辑
        
$data convert_xml_to_array($xml);
        
$new = new static();
        
$new->id $data['id'];
        
$new->name $data['name'];
        return 
$new;
    }
}

$p1 Product::fromBasicData(5'Widget');
$p2 Product::fromJson($some_json_string);
$p3 Product::fromXml($some_xml_string);

可以设置构造器为 private 或 protected,防止自行额外调用。 这时只有 static 方法可以实例化一个类。 由于它们位于同一个定义的 class 因此可以访问私有方法,也不需要在同一个对象实例中。 当然构造器不一定要设置为 private,是否合理取决于实际情况。

三个 static 方法展示了对象以不同方式的实例化方式。

  • fromBasicData() takes the exact parameters that are needed, then creates the object by calling the constructor and returning the result.
  • fromJson() accepts a JSON string and does some pre-processing on it itself to convert it into the format desired by the constructor. It then returns the new object.
  • fromXml() accepts an XML string, preprocesses it, and then creates a bare object. The constructor is still called, but as all of the parameters are optional the method skips them. It then assigns values to the object properties directly before returning the result.

在以上三个例子中,static 关键词会被翻译成代码所在类的类名。 这个例子中是 Product

析构函数

__destruct ( ) : void

PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

Example #5 析构函数示例

<?php

class MyDestructableClass 
{
    function 
__construct() {
        print 
"In constructor\n";
    }

    function 
__destruct() {
        print 
"Destroying " __CLASS__ "\n";
    }
}

$obj = new MyDestructableClass();

和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。

析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。

Note:

析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出。脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同。

Note:

试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。

add a note add a note

User Contributed Notes 11 notes

up
105
david dot scourfield at llynfi dot co dot uk
9 years ago
Be aware of potential memory leaks caused by circular references within objects.  The PHP manual states "[t]he destructor method will be called as soon as all references to a particular object are removed" and this is precisely true: if two objects reference each other (or even if one object has a field that points to itself as in $this->foo = $this) then this reference will prevent the destructor being called even when there are no other references to the object at all.  The programmer can no longer access the objects, but they still stay in memory.

Consider the following example:

<?php

header
("Content-type: text/plain");

class
Foo {
   
   
/**
     * An indentifier
     * @var string
     */
   
private $name;
   
/**
     * A reference to another Foo object
     * @var Foo
     */
   
private $link;

    public function
__construct($name) {
       
$this->name = $name;
    }

    public function
setLink(Foo $link){
       
$this->link = $link;
    }

    public function
__destruct() {
        echo
'Destroying: ', $this->name, PHP_EOL;
    }
}

// create two Foo objects:
$foo = new Foo('Foo 1');
$bar = new Foo('Foo 2');

// make them point to each other
$foo->setLink($bar);
$bar->setLink($foo);

// destroy the global references to them
$foo = null;
$bar = null;

// we now have no way to access Foo 1 or Foo 2, so they OUGHT to be __destruct()ed
// but they are not, so we get a memory leak as they are still in memory.
//
// Uncomment the next line to see the difference when explicitly calling the GC:
// gc_collect_cycles();
//
// see also: http://www.php.net/manual/en/features.gc.php
//

// create two more Foo objects, but DO NOT set their internal Foo references
// so nothing except the vars $foo and $bar point to them:
$foo = new Foo('Foo 3');
$bar = new Foo('Foo 4');

// destroy the global references to them
$foo = null;
$bar = null;

// we now have no way to access Foo 3 or Foo 4 and as there are no more references
// to them anywhere, their __destruct() methods are automatically called here,
// BEFORE the next line is executed:

echo 'End of script', PHP_EOL;

?>

This will output:

Destroying: Foo 3
Destroying: Foo 4
End of script
Destroying: Foo 1
Destroying: Foo 2

But if we uncomment the gc_collect_cycles(); function call in the middle of the script, we get:

Destroying: Foo 2
Destroying: Foo 1
Destroying: Foo 3
Destroying: Foo 4
End of script

As may be desired.

NOTE: calling gc_collect_cycles() does have a speed overhead, so only use it if you feel you need to.
up
22
domger at freenet dot de
3 years ago
The __destruct magic method must be public.

public function __destruct()
{
    ;
}

The method will automatically be called externally to the instance.  Declaring __destruct as protected or private will result in a warning and the magic method will not be called.

Note: In PHP 5.3.10 i saw strange side effects while some Destructors were declared as protected.
up
15
spleen
12 years ago
It's always the easy things that get you -

Being new to OOP, it took me quite a while to figure out that there are TWO underscores in front of the word __construct.

It is __construct
Not _construct

Extremely obvious once you figure it out, but it can be sooo frustrating until you do.

I spent quite a bit of needless time debugging working code.

I even thought about it a few times, thinking it looked a little long in the examples, but at the time that just seemed silly(always thinking "oh somebody would have made that clear if it weren't just a regular underscore...")

All the manuals I looked at, all the tuturials I read, all the examples I browsed through  - not once did anybody mention this! 

(please don't tell me it's explained somewhere on this page and I just missed it,  you'll only add to my pain.)

I hope this helps somebody else!
up
6
prieler at abm dot at
13 years ago
i have written a quick example about the order of destructors and shutdown functions in php 5.2.1:

<?php
class destruction {
    var
$name;

    function
destruction($name) {
       
$this->name = $name;
       
register_shutdown_function(array(&$this, "shutdown"));
    }

    function
shutdown() {
        echo
'shutdown: '.$this->name."\n";
    }

    function
__destruct() {
        echo
'destruct: '.$this->name."\n";
    }
}

$a = new destruction('a: global 1');

function
test() {
   
$b = new destruction('b: func 1');
   
$c = new destruction('c: func 2');
}
test();

$d = new destruction('d: global 2');

?>

this will output:
shutdown: a: global 1
shutdown: b: func 1
shutdown: c: func 2
shutdown: d: global 2
destruct: b: func 1
destruct: c: func 2
destruct: d: global 2
destruct: a: global 1

conclusions:
destructors are always called on script end.
destructors are called in order of their "context": first functions, then global objects
objects in function context are deleted in order as they are set (older objects first).
objects in global context are deleted in reverse order (older objects last)

shutdown functions are called before the destructors.
shutdown functions are called in there "register" order. ;)

regards, J
up
4
bolshun at mail dot ru
12 years ago
Ensuring that instance of some class will be available in destructor of some other class is easy: just keep a reference to that instance in this other class.
up
9
Per Persson
8 years ago
As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.

For example:
<?php
class Logger
{
    protected
$rows = array();

    public function
__destruct()
    {
       
$this->save();
    }

    public function
log($row)
    {
       
$this->rows[] = $row;
    }

    public function
save()
    {
        echo
'<ul>';
        foreach (
$this->rows as $row)
        {
            echo
'<li>', $row, '</li>';
        }
        echo
'</ul>';
    }
}

$logger = new Logger;
$logger->log('Before');

$nonset->foo();

$logger->log('After');
?>

Without the $nonset->foo(); line, Before and After will both be printed, but with the line neither will be printed.

One can however register the destructor or another method as a shutdown function:
<?php
class Logger
{
    protected
$rows = array();

    public function
__construct()
    {
       
register_shutdown_function(array($this, '__destruct'));
    }
   
    public function
__destruct()
    {
       
$this->save();
    }
   
    public function
log($row)
    {
       
$this->rows[] = $row;
    }
   
    public function
save()
    {
        echo
'<ul>';
        foreach (
$this->rows as $row)
        {
            echo
'<li>', $row, '</li>';
        }
        echo
'</ul>';
    }
}

$logger = new Logger;
$logger->log('Before');

$nonset->foo();

$logger->log('After');
?>
Now Before will be printed, but not After, so you can see that a shutdown occurred after Before.
up
3
Yousef Ismaeil cliprz[At]gmail[Dot]com
7 years ago
<?php

/**
* a funny example Mobile class
*
* @author Yousef Ismaeil Cliprz[At]gmail[Dot]com
*/

class Mobile {

   
/**
     * Some device properties
     *
     * @var string
     * @access public
     */
   
public $deviceName,$deviceVersion,$deviceColor;
   
   
/**
     * Set some values for Mobile::properties
     *
     * @param string device name
     * @param string device version
     * @param string device color
     */
   
public function __construct ($name,$version,$color) {
       
$this->deviceName = $name;
       
$this->deviceVersion = $version;
       
$this->deviceColor = $color;
        echo
"The ".__CLASS__." class is stratup.<br /><br />";
    }
   
   
/**
     * Some Output
     *
     * @access public
     */
   
public function printOut () {
        echo
'I have a '.$this->deviceName
           
.' version '.$this->deviceVersion
           
.' my device color is : '.$this->deviceColor;
    }
   
   
/**
     * Umm only for example we will remove Mobile::$deviceName Hum not unset only to check how __destruct working
     *
     * @access public
     */
   
public function __destruct () {
       
$this->deviceName = 'Removed';
        echo
'<br /><br />Dumpping Mobile::deviceName to make sure its removed, Olay :';
       
var_dump($this->deviceName);
        echo
"<br />The ".__CLASS__." class is shutdown.";
    }

}

// Oh ya instance
$mob = new Mobile('iPhone','5','Black');

// print output
$mob->printOut();

?>

The Mobile class is stratup.

I have a iPhone version 5 my device color is : Black

Dumpping Mobile::deviceName to make sure its removed, Olay :
string 'Removed' (length=7)

The Mobile class is shutdown.
up
0
iwwp at outlook dot com
10 months ago
To better understand the __destrust method:

class A {
    protected $id;

    public function __construct($id)
    {
        $this->id = $id;
        echo "construct {$this->id}\n";
    }

    public function __destruct()
    {
        echo "destruct {$this->id}\n";
    }
}

$a = new A(1);
echo "-------------\n";
$aa = new A(2);
echo "=============\n";

The output content:

construct 1
-------------
construct 2
=============
destruct 2
destruct 1
up
1
Jonathon Hibbard
10 years ago
Please be aware of when using __destruct() in which you are unsetting variables...

Consider the following code:
<?php
class my_class {
  public
$error_reporting = false;

  function
__construct($error_reporting = false) {
   
$this->error_reporting = $error_reporting;
  }

  function
__destruct() {
    if(
$this->error_reporting === true) $this->show_report();
    unset(
$this->error_reporting);
  }
?>

The above will result in an error:
Notice: Undefined property: my_class::$error_reporting in my_class.php on line 10

It appears as though the variable will be unset BEFORE it actually can execute the if statement.  Removing the unset will fix this.  It's not needed anyways as PHP will release everything anyways, but just in case you run across this, you know why ;)
up
1
Reza Mahjourian
14 years ago
Peter has suggested using static methods to compensate for unavailability of multiple constructors in PHP.  This works fine for most purposes, but if you have a class hierarchy and want to delegate parts of initialization to the parent class, you can no longer use this scheme.  It is because unlike constructors, in a static method you need to do the instantiation yourself.  So if you call the parent static method, you will get an object of parent type which you can't continue to initialize with derived class fields.

Imagine you have an Employee class and a derived HourlyEmployee class and you want to be able to construct these objects out of some XML input too.

<?php
class Employee {
   public function
__construct($inName) {
      
$this->name = $inName;
   }

   public static function
constructFromDom($inDom)
   {
      
$name = $inDom->name;
       return new
Employee($name);
   }

   private
$name;
}

class
HourlyEmployee extends Employee {
   public function
__construct($inName, $inHourlyRate) {
      
parent::__construct($inName);
      
$this->hourlyRate = $inHourlyRate;
   }

   public static function
constructFromDom($inDom)
   {
      
// can't call parent::constructFromDom($inDom)
       // need to do all the work here again
      
$name = $inDom->name// increased coupling
      
$hourlyRate = $inDom->hourlyrate;
       return new
EmployeeHourly($name, $hourlyRate);
   }

   private
$hourlyRate;
}
?>

The only solution is to merge the two constructors in one by adding an optional $inDom parameter to every constructor.
up
0
david at synatree dot com
12 years ago
When a script is in the process of die()ing, you can't count on the order in which __destruct() will be called.

For a script I have been working on, I wanted to do transparent low-level encryption of any outgoing data.  To accomplish this, I used a global singleton class configured like this:

class EncryptedComms
{
    private $C;
    private $objs = array();
    private static $_me;
   
    public static function destroyAfter(&$obj)
    {
        self::getInstance()->objs[] =& $obj;
        /*
            Hopefully by forcing a reference to another object to exist
            inside this class, the referenced object will need to be destroyed
            before garbage collection can occur on this object.  This will force
            this object's destruct method to be fired AFTER the destructors of
            all the objects referenced here.
        */
    }
    public function __construct($key)
    {
            $this->C = new SimpleCrypt($key);
            ob_start(array($this,'getBuffer'));
    }
    public static function &getInstance($key=NULL)
    {
        if(!self::$_me && $key)
            self::$_me = new EncryptedComms($key);
        else
            return self::$_me;
    }
   
    public function __destruct()
    {
        ob_end_flush();
    }
   
    public function getBuffer($str)
    {
        return $this->C->encrypt($str);
    }

}

In this example, I tried to register other objects to always be destroyed just before this object.  Like this:

class A
{

public function __construct()
{
     EncryptedComms::destroyAfter($this);
}
}

One would think that the references to the objects contained in the singleton would be destroyed first, but this is not the case.  In fact, this won't work even if you reverse the paradigm and store a reference to EncryptedComms in every object you'd like to be destroyed before it.

In short, when a script die()s, there doesn't seem to be any way to predict the order in which the destructors will fire.
To Top