Constructor
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.
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
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.