PHP 8.3.21 Released!

Constructores y destructores

Constructor

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

PHP permite a los desarrolladores declarar constructores para las clases. Las clases que poseen un método constructor llaman a este método cada vez que se crea una nueva instancia del objeto, lo cual es interesante para todas las inicializaciones que el objeto necesita antes de ser utilizado.

Nota: Los constructores de la clase padre no son llamados implícitamente si la clase hija define un constructor. Si se desea utilizar un constructor de la clase padre, será necesario llamar a parent::__construct() desde el constructor de la clase hija. Si la clase hija no define un constructor, entonces puede ser heredado de la clase padre, exactamente de la misma manera que una método lo sería (si no ha sido declarado como privado).

Ejemplo #1 Constructor en la herencia

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

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

class
OtherSubClass extends BaseClass {
// Constructor heredado de BaseClass
}

// En el constructor de BaseClass
$obj = new BaseClass();

// En el constructor de BaseClass
// En el constructor de SubClass
$obj = new SubClass();

// En el constructor de BaseClass
$obj = new OtherSubClass();
?>

A diferencia de otros métodos, __construct() está excluido de las reglas de compatibilidad de las firmas usuelles cuando se extiende.

Los constructores son métodos ordinarios que son llamados durante la instanciación de su objeto correspondiente. Por consiguiente, pueden definir un número arbitrario de argumentos, que pueden ser requeridos, tener un tipo, y pueden tener un valor por omisión. Los argumentos de un constructor son llamados colocando los argumentos entre paréntesis después del nombre de la clase.

Ejemplo #2 Utilizando los argumentos de un constructor

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

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

// Pasar ambos parámetros.
$p1 = new Point(4, 5);
// Pasar solo el parámetro requerido. $y tomará su valor por omisión de 0.
$p2 = new Point(4);
// Con parámetros nombrados (a partir de PHP 8.0):
$p3 = new Point(y: 5, x: 4);
?>

Si una clase no tiene constructor, o si el constructor no tiene argumentos requeridos, los paréntesis pueden ser omitidos.

Estilo antiguo de los constructores

Anterior a PHP 8.0.0, las clases en el espacio de nombres global interpretarán un método que tiene el mismo nombre que la clase como un constructor de estilo antiguo. Esta sintaxis está obsoleta y resultará en un error E_DEPRECATED, pero llamará a esta función como un constructor. Si __construct() y un método con el mismo nombre son definidos, __construct() será llamado.

Las clases en los espacios de nombres, o cualquier clase a partir de PHP 8.0.0, un método con el mismo nombre que la clase no tendrá ningún significado especial.

Siempre utilizar __construct() en el nuevo código.

Promoción del Constructor

A partir de PHP 8.0.0, los parámetros del constructor pueden ser promovidos para corresponder a una propiedad del objeto. Es muy común que los parámetros de un constructor sean asignados a una propiedad sin realizar operaciones sobre ellos. La promoción del constructor proporciona un atajo para este caso de uso. El ejemplo anterior puede ser reescrito de la siguiente manera.

Ejemplo #3 Utilizando la promoción de propiedad del constructor

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

Cuando un argumento de constructor incluye un modificador, PHP lo interpretará como una propiedad del objeto y un argumento del constructor, y asignará el valor del argumento a la propiedad. El cuerpo del constructor puede estar entonces vacío o puede contener otras declaraciones. Todas las declaraciones adicionales serán ejecutadas después de que el valor del argumento haya sido asignado a su propiedad correspondiente.

No todos los argumentos deben ser promovidos. Es posible mezclar y combinar argumentos promovidos y no promovidos, en cualquier orden. Los argumentos promovidos no tienen ningún impacto en el código que llama al constructor.

Nota:

Utilizar un modificador de visibilidad (public, protected o private) es la manera más probable de aplicar la promoción de propiedad, pero cualquier otro modificador único (como readonly) tendrá el mismo efecto.

Nota:

Las propiedades del objeto no pueden ser tipadas callable debido a las ambigüedades del motor que esto introduciría. Por lo tanto, los argumentos promovidos tampoco pueden ser tipados callable. Sin embargo, cualquier otra declaración de tipo está permitida.

Nota:

Como las propiedades promovidas son tanto desucradas a una propiedad como también como el parámetro de una función, todas las restricciones de nombramiento para las propiedades y los parámetros se aplican.

Nota:

Los atributos colocados en un argumento de constructor promovido serán reproducidos tanto en la propiedad como en el argumento. Los valores por omisión en un argumento de constructor promovido serán replicados solo en el argumento y no en la propiedad.

New en inicializadores

A partir de PHP 8.1.0, los objetos pueden ser utilizados como valor por omisión para los parámetros, variables estáticas, constantes globales y los argumentos de atributos. Los objetos pueden ser ahora pasados a define().

Nota:

El uso de un nombre de clase dinámico o no-string o una clase anónima no está permitido. El uso del desempaquetado de argumentos no está permitido. El uso de expresiones no soportadas como argumento no está permitido.

Ejemplo #4 Uso de new en inicializadores

<?php

// Todos permitidos:
static $x = new Foo;

const
C = new Foo;

function
test($param = new Foo) {}

#[
AnAttribute(new Foo)]
class
Test {
public function
__construct(
public
$prop = new Foo,
) {}
}

// Todos no permitidos (error en tiempo de compilación):
function test(
$a = new (CLASS_NAME_CONSTANT)(), // nombre de clase dinámico
$b = new class {}, // clase anónima
$c = new A(...[]), // desempaquetado de argumentos
$d = new B($abc), // expresión de constante no soportada
) {}
?>

Método de creación estático

PHP soporta únicamente un constructor único por clase. Sin embargo, en algunos casos puede ser deseable permitir que un objeto sea construido de manera diferente con diferentes entradas. La manera recomendada de hacer esto es utilizando métodos estáticos como una envoltura del constructor.

Ejemplo #5 Utilizando la creación vía un método estático

<?php
$some_json_string
= '{ "id": 1004, "name": "Elephpant" }';
$some_xml_string = "<animal><id>1005</id><name>Elephpant</name></animal>";

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 $id, string $name): static {
$new = new static($id, $name);
return
$new;
}

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

public static function
fromXml(string $xml): static {
$data = simplexml_load_string($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);

var_dump($p1, $p2, $p3);

El constructor puede ser hecho privado o protegido para evitar su llamada desde el exterior. En este caso, solo un método estático será capaz de instanciar la clase. Dado que están en la misma definición de clase, tienen acceso a los métodos privados, incluso en una instancia diferente del objeto. Un constructor privado es opcional y puede o no tener sentido dependiendo del caso de uso.

Los tres métodos estáticos públicos demuestran entonces diferentes maneras de instanciar el objeto.

  • fromBasicData() toma los parámetros exactos que son necesarios, luego crea el objeto llamando al constructor y devolviendo el resultado.
  • fromJson() acepta una cadena de caracteres JSON y realiza un preprocesamiento sobre ella misma para convertirse en el formato deseado por el constructor. Luego devuelve el nuevo objeto.
  • fromXml() acepta una cadena de caracteres XML, realiza un preprocesamiento, luego crea un objeto en bruto. El constructor es llamado, pero como todos los parámetros son opcionales, el método los ignora. Luego asigna los valores a las propiedades del objeto directamente antes de devolver el resultado.

En los tres casos, la palabra clave static es traducida en el nombre de la clase donde el código se encuentra. En este caso, Product.

Destructor

__destruct(): void

PHP posee un concepto de destructor similar al de otros lenguajes orientados a objetos, como el C++. El método destructor es llamado tan pronto como no haya más referencias a un objeto dado, o en cualquier orden durante la secuencia de parada.

Ejemplo #6 Ejemplo con un Destructor

<?php

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

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

$obj = new MyDestructableClass();

Al igual que el constructor, el destructor de la clase padre no será llamado implícitamente por el motor. Para ejecutar el destructor de la clase padre, es necesario llamar explícitamente a la función parent::__destruct en el cuerpo del destructor. Al igual que los constructores, una clase hija puede heredar el destructor de la clase padre si no implementa uno propio.

El destructor será llamado incluso si la ejecución del script es detenida utilizando la función exit(). Llamar a la función exit() en un destructor impedirá la ejecución de las rutinas de parada restantes.

Si un destructor crea nuevas referencias a su objeto, no será llamado una segunda vez cuando el contador de referencias vuelva a cero o durante la secuencia de parada.

A partir de PHP 8.4.0, cuando la recolección de ciclos ocurre durante la ejecución de una fibra, los destructores de los objetos programados para la recolección son ejecutados en una fibra distinta, llamada gc_destructor_fiber. Si esta fibra es suspendida, se creará una nueva fibra para ejecutar los destructores restantes. La anterior gc_destructor_fiber no será más referenciada por el recolector de basura y podrá ser recolectada si no es referenciada en otro lugar. Los objetos cuyo destructor es suspendido no serán recolectados hasta que el destructor haya terminado o la fibra misma sea recolectada.

Nota:

Los destructores llamados durante la parada del script están en una situación donde los encabezados HTTP ya han sido enviados. El directorio de trabajo en la fase de parada del script puede ser diferente con ciertas APIs (e.g. Apache).

Nota:

Intentar lanzar una excepción desde un destructor (llamado al final del script) resulta en un error fatal.

add a note

User Contributed Notes 13 notes

up
164
david dot scourfield at llynfi dot co dot uk
13 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
7
Hayley Watson
1 year ago
There are other advantages to using static factory methods to wrap object construction instead of bare constructor calls.

As well as allowing for different methods to use in different scenarios, with more relevant names both for the methods and the parameters and without the constructor having to handle different sets of arguments of different types:

* You can do all your input validation before attempting to construct the object.
* The object itself can bypass that input validation when constructing new instances of its own class, since you can ensure that it knows what it's doing.
* With input validation/preprocessing moved to the factory methods, the constructor itself can often be reduced to "set these properties to these arguments", meaning the constructor promotion syntax becomes more useful.
* Having been hidden away from users, the constructor's signature can be a bit uglier without becoming a pain for them. Heh.
* Static methods can be lifted and passed around as first class closures, to be called in the normal fashion wherever functions can be called, without the special "new" syntax.
* The factory method need not return a new instance of that exact class. It could return a pre-existing instance that would do the same job as the new one would (especially useful in the case of immutable "value type" objects by reducing duplication); or a simpler or more specific subclass to do the job with less overhead than a more generic instance of the original class. Returning a subclass means LSP still holds.
up
33
domger at freenet dot de
7 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
26
spleen
16 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
9
iwwp at outlook dot com
5 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
6
david at synatree dot com
17 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.
up
3
mmulej at gmail dot com
3 years ago
*<Double post> I can't edit my previous note to elaborate on modifiers. Please excuse me.*

If both parent and child classes have a method with the same name defined, and it is called in parent's constructor, using `parent::__construct()` will call the method in the child.

<?php

class A {
public function
__construct() {
$this->method();
}
public function
method() {
echo
'A' . PHP_EOL;
}
}
class
B extends A {
public function
__construct() {
parent::__construct();
}
}
class
C extends A {
public function
__construct() {
parent::__construct();
}
public function
method() {
echo
'C' . PHP_EOL;
}
}
$b = new B; // A
$c = new C; // C

?>

In this example both A::method and C::method are public.

You may change A::method to protected, and C::method to protected or public and it will still work the same.

If however you set A::method as private, it doesn't matter whether C::method is private, protected or public. Both $b and $c will echo 'A'.
up
9
Per Persson
13 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
7
prieler at abm dot at
17 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
3
bolshun at mail dot ru
17 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
2
Yousef Ismaeil cliprz[At]gmail[Dot]com
11 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
-1
Jonathon Hibbard
15 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
-3
Reza Mahjourian
18 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.
To Top