Constructori și Destructori

Constructor

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

PHP 5 permite programatorilor să definească constructori pentru clase. Clasele care au definit un constructor vor apela această metodă la fiecare obiect nou creat, pentru ca acesta (obiectul) să fie utilizabil pentru inițializare înante de a fi folosit.

Notă: Constructorul-părinte nu este apelat implicit dacă clasa-fiică definește un constructor. Pentru a apela un constructor-părinte, este necesar de a apela parent::__construct() din cadrul constructorului-fiu. Dacă clasa-fiică nu definește un constructor, atunci acesta poate fi moștenit de la clasa-părinte la fel ca o metodă normală a clasei (dacă aceasta nu a fost declarată ca privată).

Example #1 utilizarea noilor constructori unificați

<?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 {
    
// inherits BaseClass's constructor
}

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

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

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

Spre deosebire de alte metode, PHP nu va genera un mesaj de eroare de nivel E_STRICT atunci când __construct() este suprascrisă cu alți parametri decât cei ai metodei __construct() din clasa-părinte.

Constructorii sunt metode apelate la inițializarea obiectelor. Aceștia pot fi definiți cu un număr arbitrar de argumente, care pot fi opționali sau nu, pot ave tip, și pot avea valoare implicită. Argumentele pentru constructori se pun între paranteze după numele clasei.

Example #2 Constructori în clasele cu spații de nume

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

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

// Apel cu ambii parametrii
$p1 = new Point(45);
// Apel cu argumentul necesar. $y va prelua valoare implictă 0.
$p2 = new Point(4);
// Cu argumente numite (începând cu PHP 8.0):
$p3 = new Point(y5x4);
?>

Dacă o clasă nu are constructor, sau constructorul nu are nici un argument necesar, parantezele pot fi omise.

Constructorii de stil vechi

Înainte de PHP 8.0.0, clasele din namespaceul global interpretau o metodă cu același nume ca și clasa ca un constructor de stil vechi. Acea syntaxă este învechită și nerecomandată, iar utilizarea ei va genera or eroare de tip E_DEPRECATED dar metoda constructor va fi apelată. Dacă atât __construct() cât și o motodă cu acelasi nume cu al clasei sunt definite, numai __construct() va fi apelat.

În clasele din namespace-uri, sau orice altă clasă începând cu PHP 8.0.0, o metodă cu același nume cu al clasei nu are însemnătate specială.

Folosiți întotdeauna __construct() în cod nou.

Promovarea prin constructor

Începând cu PHP 8.0.0, parametrii constructorului pot fi promovati să corespundă cu o proprietate a objectului. Este foarte des întâlnit ca parametrii constructorului să fie asignat unei proprietăți în constructor și apoi să nu se mai lucreze cu acea proprietate. Promovarea prin constructor oferă o scurtătură a acestul caz. Exemplul de mai sus poate fi rescris în forma următoare.

Example #3 Folosirea promovării prin constructor

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

Atunci când argumentele constructorului include modificatori de vizibilitate, PHP îl va interpreta atât ca o proprietate a obiectului cât și drept argument al constructorului, va asigna valoarea argumentului la proprietatea respectivă. Corpul constructorului poate fi gol sau poate contine alte declarații. Declarațiile vor fi executate după ce valorile au fost asignate la proprietățile corespunzătoare.

Nu este necesar să fie promovate toate argumentele. Este posibil să se amestece argumente/proprietăți promovate și nepromovate, în orice ordine. Argumentele promovate nu au nici un impact asupra codului care apelează constructorul.

Notă:

Proprietățile obiectelor nu pot fi de tipul callable pentru că ar introduce ambiguități. Argumentele promovate, de asemenea, nu pot fi de tipul callable. Orice alte declarații de tip sunt permise.

Notă:

Atributele plasate ca argumente la un constructor promovat vor fi replicate și ca proprietăți dar și ca argumente.

Metode de creare statice

PHP suportă un singur constructor într-o clasă. În unele cazuri, însă, este nevoie să permitem crearea unui obiect în diferite feluri cu intrări diferite. Metoda recomandată este folosind metode statice care împacheterază constructorul.

Example #4 Folosirea metodelor de creare statice

<?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 {
        
// Put your own logic here.
        
$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);

Constructorul poate fi făcut private sau protected pentru a preveni apelarea din exterior. Dacă se alege această cale, numai o metodă statică va putea instanția classa. Pentru că sunt definite în aceeași clasă au access la metodele private, chiar dacă obiectele instanțiate sunt diferite. Un constructor private este opțional și poate fi o alegere bună în funcție de cerințe...

Cele trei metode statice publice demonstrează feluri diferite de instanțiere a obiectelor.

  • fromBasicData() permite exact parametrii necesari, și apoi crează obiectul apelând constructorul și returnând rezultatul.
  • fromJson() acceptă un text în format JSON și îl pre-procesează pentru într-un format necesar constructorului. Apoi returnează obiectul nou.
  • fromXml() acceptă un text XML, în pro-procesează, apoi crează un obiect gol. Constructorul este apelat, dar pentru că toți parapetrii sunt opționali nu sunt pasați. Apoi asignează valori corecte proprietăților obiectului înainte de a întoarce rezultatul.

În toate cele trei cazuri, cuvântul cheie staticeste tradus în numele clasei în care este scris acest cod. În acest caz, Product.

Destructori

__destruct ( ) : void

PHP 5 introduce un concept de distrugere a unui obiect similar cu cel regăsit în alte limbaje de programare orientate pe obiecte (C++). Metoda de distrugere va fi apelată imediat ce nu mai sunt careva referințe către un anumit obiect sau în orice ordine în timpul secvenței de deconectare.

Example #5 Exemplu destructor

<?php

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

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

$obj = new MyDestructableClass();

La fel ca la constructori, destructorii-părinti nu vor fi apelați implicit. Pentru a apela destructorul-părinte trebuie să apelați explicit parent::__destruct() în destructorul descendent. De asemenea, ca și în cazul constructorilor, o clasă-fiică poate să moștenească destructorul părintelui dacă ea nu-și implementează destructorul propriu.

Destructorul este apelat chiar dacă scriptul este terminat cu apelul la funcția exit(). Apelând exit() într-un destructor va preveni apelarea tuturor rutinelor de terminare din scriptul respectiv.

Notă:

Destructorii apelați la momentul terminării execuției scriptului au antetele HTTP trimise deja. Directorul de lucru în timpul fazei de deconectare a scriptului poate să difere în cazul anumitor SAPI-uri (de ex. Apache).

Notă:

Încercarea de a arunca o excepție dintr-un destructor (apelat la momentul terminării execuției scriptului) va cauza o eroare fatală.

add a note add a note

User Contributed Notes 12 notes

up
107
david dot scourfield at llynfi dot co dot uk
12 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
3
iwwp at outlook dot com
4 years 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
22
domger at freenet dot de
6 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
15 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
7
prieler at abm dot at
16 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
9
Per Persson
11 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
4
Yousef Ismaeil cliprz[At]gmail[Dot]com
10 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
instatiendaweb at gmail dot com
3 years ago
/**
* Haciendo una prueba con dos clases y dos destructores
* La prueba consta de acceder a la variable global del primer objeto en el segundo
* objeto el destructor 2
* Primera clase ==> $GLOBALS['obj']
* SEgunda clase ==> $GLOBALS['obj2']
* Se ejecuta construct y todo el codigo....
* Primer destruct borra el objeto y lo hace null
* Tratamos de acceder a $GLOBALS['obj'] en el segundo destruct pero
* ya no esta es un objeto null
* Warning: Undefined array key "obj" in...
*/

class MyDestructableClass{
public $parametro;

     function __construct($parametro) {
echo("<div class=\"div\">"), "Construyendo ",__CLASS__ , ("</div>");
         escribir::verifacionnota($this ,'Antes de guardar la variable  ');
         $this->parametro = $parametro;
         escribir::verifacionnota($this ,'Despues de guardar la variable  ');
     }

  

     function __destruct() {
        escribir::linea(5); //Separador
        echo("<div class=\"div\">"), "Destruyendo " ,  __CLASS__ , ("</div>");
        escribir::verifacionnota($this ,'Antes de borrar la variable  ');
        unset($this->parametro);
        escribir::verifacionnota($this ,'Despues de borrar la variable  ');

       // unset($GLOBALS[$this]);
     }
}

$obj = new MyDestructableClass('parametroone');
escribir::verifacionnota($obj ,' Verificar la clase MyDestructableClass, no es necesario
borrar la clase porque se ejecuta al final del script  ');
escribir::titulosep('Provando ejemplo aqui se puede acceder a la variable global');
escribir::verificacion($GLOBALS['obj']);

class destructora{
    function __destruct(){
        escribir::titulosep('Sin embargo esta variable muere aqui');
        escribir::verificacion($GLOBALS['obj']);
    }
}

$obj2 = new destructora();
up
1
bolshun at mail dot ru
15 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
1
Jonathon Hibbard
14 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
0
Reza Mahjourian
17 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
-2
david at synatree dot com
16 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