5. Las clases
Vocabulario: Una clase es un tipo. Una variable de este tipo se denomina objeto. Un objeto es una instancia (ejemplar) de una clase.
5.1. La estructura de los scripts

5.2. Cualquier variable puede convertirse en un objeto dotado de atributos
El script [classes-01.php] es el siguiente:
<?php
// un objeto genérico
// $obj1=new stdClass();
// cualquier variable puede tener atributos por definición
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// muestra el objeto
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// modifica el objeto
$obj1->attr2 += 100;
// muestra el objeto
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// copia el valor de objeto1 (dirección del objeto al que apunta) en objeto2
// entonces las dos variables son diferentes, pero apuntan al mismo objeto
$obj2 = $obj1;
// modifica obj2
$obj2->attr2 = 0;
// muestra los dos objetos
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// cambia el objeto al que apunta obj1
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// asigna la referencia (la dirección) de objeto2 a objeto3
// $obj2 y $obj3 son entonces una misma variable
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// modifica obj3
$obj3->attr2 = 10;
// muestra los dos objetos
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// cambia el objeto al que apunta obj2
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
//muestra los dos objetos $obj2 y $obj3
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// ¿Es un objeto un diccionario?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
print "obj3[$attribut]=$valeur\n";
}
// fin
exit;
Resultados:
Warning: Creating default object from empty value in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 6
objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
obj1 :
stdClass Object
(
)
obj2 :
stdClass Object
(
[attr1] => un
[attr2] => 0
)
obj2 :
stdClass Object
(
[attr1] => un
[attr2] => 0
)
obj3 :
stdClass Object
(
[attr1] => un
[attr2] => 0
)
objet2=[un,10]
objet3=[un,10]
obj2 :
stdClass Object
(
[attr3] => deux
[attr4] => 20
)
obj3 :
stdClass Object
(
[attr3] => deux
[attr4] => 20
)
Warning: count(): Parameter must be an array or an object that implements Countable in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 50
1
Deprecated: The each() function is deprecated. This message will be suppressed on further calls in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 51
obj3[attr3]=deux
obj3[attr4]=20
Comentarios
- línea 6: la notación $obj->attr se refiere al atributo attr de la variable $obj. Si no existe, se crea, convirtiendo así a la variable $obj en un objeto con atributos. Hemos visto que PHP crea entonces, por defecto, un objeto de tipo stdClass;
- línea 16: la expresión $obj2=$obj1, cuando $obj1 es un objeto, es una copia de objetos por referencia: $obj2 y $obj1 son referencias (direcciones) a un mismo objeto. El objeto en sí puede ser modificado por cualquiera de las dos referencias;
- líneas 23-27: tienen como objetivo mostrar que $obj1 y $obj2 son dos variables diferentes: no se encuentran en la misma dirección de memoria:
- $obj2=$obj1 copió el valor de $obj1 en la variable $obj2 (operación 1 anterior). El valor de $obj1 es la dirección de un objeto. Por lo tanto, $obj1 y $obj2 apuntan al mismo objeto. Cuando se manipula una variable $obj y esta apunta a un objeto, PHP manipula el objeto al que apunta la variable $obj. Según el esquema a continuación, se observa que se puede modificar el objeto al que apunta tanto a través de $obj1 como a través de $obj2. Esto es lo que muestran las líneas 4 y 5 de los resultados;

- línea 30: la expresión $obj3=&$obj2 hace que $obj2 y $obj3 estén en la misma dirección, [1 ci-dessous]. Se podría decir que ambas variables son alias de la misma ubicación en memoria. Ambas apuntan a un objeto, el Objeto A que se muestra a continuación: [2];
- la operación $obj2=new stdClass() hace que se cree un nuevo objeto, el Objeto B ([3 ci-dessous]), y la dirección de este nuevo objeto se asigna a la variable $obj2. Dado que $obj2 y $obj3 son dos alias de la misma ubicación en memoria, $obj3 también apunta al nuevo objeto Objeto B. Esto es lo que muestran las líneas 16-27 y 30-41 de los resultados;

- líneas 52-54: muestran que un objeto puede recorrerse como un diccionario. Las claves del diccionario son los nombres de los atributos y los valores del diccionario, los valores de esos mismos atributos;
- línea 51: la función count se puede aplicar a un objeto (con una advertencia), pero no devuelve, como cabría esperar, el número de atributos. Por lo tanto, un objeto presenta similitudes con un diccionario, pero no es uno;
5.3. Una clase Persona sin atributos declarados
El script [classes-02.php] es el siguiente:
<?php
class Personne {
// atributos de la clase
// no declarados: se pueden crear dinámicamente
// método
function identite() {
// a priori, utiliza atributos inexistentes
return "[$this->prenom,$this->nom,$this->age]";
}
}
// prueba
// los atributos son públicos y se pueden crear dinámicamente
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// llamada a un método
print "personne=" . $p->identite() . "\n";
// fin
exit;
Resultados:
Comentarios
- líneas 3-13: definen una clase Personne. Una clase es un molde a partir del cual se crean objetos. Agrupa atributos y funciones llamadas métodos. No es obligatorio declarar los atributos;
- líneas 8-11: el método identité muestra el valor de tres atributos no declarados en la clase. La palabra clave $this designa el objeto al que se aplica el método;
- línea 17: se crea un objeto $p de tipo Personne. La palabra clave new sirve para crear un nuevo objeto. La operación devuelve una referencia al objeto creado (es decir, una dirección). Se pueden utilizar diversas formas de escritura: new Persona(), new Persona, new persona. El nombre de la clase no distingue entre mayúsculas y minúsculas;
- líneas 18-20: se crean los tres atributos necesarios para el método identité en el objeto $p;
- línea 22: se aplica el método identité de la clase Personne al objeto $p. En el código (líneas 8-11) del método identité, $this hace referencia al mismo objeto que $p;
5.4. La clase Persona con atributos declarados
El script [classes-03.php] es el siguiente:
<?php
class Personne {
// atributos de la clase
var $prenom;
var $nom;
var $age;
// método
function identite() {
return "[$this->prenom,$this->nom,$this->age]";
}
}
// prueba
// los atributos son públicos
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// llamada a un método
print "personne=" . $p->identite() . "\n";
// fin
exit;
Resultados:
Comentarios
- líneas 6-8: los atributos de la clase se declaran explícitamente con la palabra clave var;
5.5. La clase Persona con un constructor
Los ejemplos anteriores mostraban clases exóticas como Personne, tal como se podían encontrar en PHP 4. No se recomienda seguir estos ejemplos. Ahora presentamos una clase Personne [classes-04.php] que cumple con las buenas prácticas de PHP 7:
<?php
// cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);
class Personne {
// atributos de la clase
private $prenom;
private $nom;
private $age;
// métodos getter y setter
public function getPrenom(): string {
return $this->prenom;
}
public function getNom(): string {
return $this->nom;
}
public function getAge(): int {
return $this->age;
}
public function setPrenom(string $prenom): void {
$this->prenom = $prenom;
}
public function setNom(string $nom): void {
$this->nom = $nom;
}
public function setAge(int $age): void {
$this->age = $age;
}
// constructor
public function __construct(string $prenom, string $nom, int $age) {
// se utiliza el método set
$this->setPrenom($prenom);
$this->setNom($nom);
$this->setAge($age);
}
// método toString
public function __toString(): string {
return "[$this->prenom,$this->nom,$this->age]";
}
}
// prueba
// creación de un objeto Persona
$p = new Personne("Paul", "Langevin", 48);
// identidad de esta persona
print "personne=$p\n";
// se cambia la edad
$p->setAge(14);
// identidad de la persona
print "personne=$p\n";
// fin
exit;
Resultados:
Comentarios
- líneas 6-50: la clase Persona;
- líneas 7-9: los atributos privados (private) de la clase. Estos atributos solo son visibles dentro de la clase. Otras palabras clave que se pueden utilizar son:
- public: hace que el atributo sea visible desde fuera de la clase,
- protected: hace que el atributo sea visible desde dentro de la clase y de las clases derivadas de ella;
- dado que los atributos son privados, no se puede acceder a ellos desde fuera de la clase. Por lo tanto, no se puede escribir el siguiente código:
Aquí nos encontramos fuera de la clase Personne. Como el atributo nom es privado, la línea 2 es incorrecta. Para inicializar los campos privados del objeto $p, hay dos formas:
- utilizar los métodos públicos set y get (el nombre de estos métodos puede ser cualquiera) de las líneas 12 a 34. Entonces se podría escribir:
- utilizar el constructor de las líneas 37 a 42. En ese caso, se escribirá:
La escritura anterior invoca automáticamente el método de la clase Personne denominado __construct;
- línea 59: esta línea muestra la persona $p en forma de cadena de caracteres. Para ello, se utiliza el método de la clase Personne llamado __toString (líneas 45-47);
- todos los métodos de la clase (funciones) han sido precedidos por la palabra clave «public», que indica que la función es visible fuera de la clase. Las otras palabras clave que se pueden utilizar son, al igual que para los atributos y con el mismo significado, «private» y «protected». Sin un atributo explícito de visibilidad, la función tiene una visibilidad implícita «public»;
5.6. La clase Persona con controles de validez en el constructor
El constructor de una clase es el lugar adecuado para verificar que los valores de inicialización del objeto sean correctos. Sin embargo, un objeto también puede inicializarse mediante sus métodos set o equivalentes. Para evitar repetir las mismas verificaciones en dos lugares distintos, estas pueden incluirse en los métodos set. Si un valor de inicialización de un objeto resulta incorrecto, se lanzará una excepción. A continuación se muestra un ejemplo.
En primer lugar, movemos la definición de la clase Personne a su propio archivo [Personne.php]:
<?php
// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);
// espacio de nombres;
namespace Exemples;
// clase Persona
class Personne {
// atributos de la clase
private $prenom;
private $nom;
private $age;
// métodos getter y setter
public function getPrenom(): string {
return $this->prenom;
}
public function getNom(): string {
return $this->nom;
}
public function getAge(): int {
return $this->age;
}
public function setPrenom(string $prénom): void {
// el nombre debe ser distinto de vacío
$prénom = trim($prénom);
if ($prénom === "") {
throw new \Exception("Le prénom doit être non vide");
} else {
$this->prenom = $prénom;
}
}
public function setNom(string $nom): void {
// el apellido no debe estar vacío
$nom = trim($nom);
if ($nom === "") {
throw new \Exception("Le nom doit être non vide");
} else {
$this->nom = $nom;
}
}
public function setAge(int $âge): void {
// la edad debe ser válida
if ($âge < 0) {
throw new \Exception("L'âge doit être un entier positif ou nul");
} else {
$this->age = $âge;
}
}
// constructor
public function __construct(string $prenom, string $nom, int $age) {
// se utilizan los setters
$this->setPrenom($prenom);
$this->setNom($nom);
$this->setAge($age);
}
// método
public function initWithPersonne(Personne $p): void {
// inicializa el objeto actual con una persona $p
$this->__construct($p->prenom, $p->nom, $p->age);
}
// método toString
function __toString(): string {
return "[$this->prenom,$this->nom,$this->age]";
}
}
Comentarios
- línea 4: se especifica que se respete el tipo de los parámetros de las funciones tal como se declara;
- línea 7: define un espacio de nombres (namespace). El nombre completo (se dice qualifié) de la clase Personne es entonces \Exemples\Personne. Observe el carácter \ que inicia el nombre calificado: entonces tenemos un nombre calificado absolu. Si este carácter no está presente, tenemos un nombre calificado relatif (relativo al espacio de nombres actual). Así, si dos clases A y B forman parte del mismo espacio de nombres E, en el código de la clase A se podrá acceder a la clase B mediante la notación relative B. Si la clase A forma parte del espacio de nombres E1 y la clase B del espacio de nombres E2, en el código de A se accederá a B mediante la notación absolue \E2\B. Definir una clase dentro de un espacio de nombres no es obligatorio, pero NetBeans emite una advertencia si no se hace. Por lo tanto, lo haremos. Además, los espacios de nombres deben corresponder a la estructura de árbol de los archivos. Así, la clase A en un espacio de nombres E1 debería estar en un archivo E1/A.php. No es obligatorio, pero, de nuevo, NetBeans emite una advertencia si no se hace así. En el ejemplo de la clase [\Exemples\Personne], NetBeans emite una advertencia porque la estructura de directorios del archivo [Personne.php] es [exemples/classes/Personne.php] y, por lo tanto, no coincide con el espacio de nombres. No hay que confundir la estructura de archivos con el espacio de nombres. El nombre completamente calificado de una clase utiliza un espacio de nombres y no tiene nada que ver con la estructura de archivos del archivo PHP de la clase. La relación entre la estructura de archivos y el espacio de nombres es opcional y puede no respetarse, como lo hemos hecho aquí;
- líneas 12-14: los tres atributos privados de la clase;
- líneas 29-37: inicialización del atributo prenom y verificación del valor de inicialización;
- línea 31: la función trim($chaine) elimina los espacios que se encuentran al principio y al final de $chaine. Así, trim(«abcd ») es la cadena «abcd» y trim« » es la cadena vacía;
- línea 32: si el nombre es vacío, se lanza una excepción (línea 33); de lo contrario, se almacena el nombre (línea 35). Para lanzar una excepción, aquí se utilizó la clase predefinida [Exception]. Aquí nos vemos obligados a utilizar su nombre absoluto [\Exception]. Si se utiliza su nombre relativo [Exception], entonces esta clase se buscará en el espacio de nombres actual, es decir, el espacio de nombres [Exemples] de la clase Personne. De este modo, el intérprete PHP buscará una clase con el nombre absoluto [\Exemples\Exception] que no existe;
La clase [Personne] es utilizada por el siguiente script [classes-05.php]:
<?php
// cumplimiento estricto del tipo de los parámetros de las funciones
declare(strict_types=1);
// inclusión de la definición de la clase Persona
require_once __DIR__."/Personne.php";
// nombre calificado de la clase Persona
use \Exemples\Personne;
// prueba
// creación de un objeto Persona
$p = new Personne("Paul", "Langevin", 48);
// identidad de esta persona
print "Exemple1, personne=$p\n";
// creación de un objeto «Persona» erróneo
try {
$p = new Personne("xx", "yy", "zz");
} catch (\Exception $e1) {
print "Exemple2, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
print "Exemple2, erreur : " . $e2->getMessage() . "\n";
}
// creación de un objeto Persona con error
try {
$p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}
// fin
exit;
Comentarios
- línea 7: el script utilizará la clase [Personne]. Por lo tanto, hay que indicarle al intérprete PHP dónde puede encontrar la definición de esta clase. Para ello sirven las instrucciones [include fichier] y [require fichier]. Aquí se utilizó la instrucción [include]. La diferencia entre ambas instrucciones es la siguiente: si la instrucción [include fichier] encuentra errores al cargar [fichier], se emite un error de nivel [E_WARNING], pero lala ejecución continúa, mientras que [require], en el mismo caso, genera un error fatal y la ejecución del script se detiene. Cada una de las dos instrucciones tiene una variante: [include_once] y [require_once]. Estas dos variantes permiten manejar el caso de inclusiones múltiples de un mismo archivo. Podemos imaginar aquí un proyecto compuesto por varios scripts PHP, de los cuales varios hacen referencia a la clase [Personne]. Su ejecución provocará entonces que el archivo [Personne.php] se incluya varias veces y generará un error, ya que una clase no puede definirse dos veces. La solución es utilizar las variantes [_once], que garantizan que el archivo solo se incluirá una vez en el script global del proyecto;
- línea 7: la constante [__DIR__] es una constante PHP que designa el nombre completo de la carpeta en la que se encuentra el script que contiene la constante [__DIR__]. Así, la expresión de la línea 17:
require_once __DIR__."/Personne.php";
será equivalente a algo como:
require_once ‘C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes/Personnes.php’
En la ruta del archivo, se pueden usar indistintamente los caracteres / y \;
- línea 14: se utiliza la clase [Personne] que acabamos de definir. El script [classes-05.php] no tiene espacios de nombres. La línea 14 utiliza el nombre relativo de la clase [Personne] sin espacio de nombres. Al no haber un espacio de nombres para la clase [Personne], esta se busca dentro del propio script [classes-05.php] y, por lo tanto, no se encontrará. Hay dos soluciones para este problema:
- utilizar el nombre completo de la clase [\Exemples\Personne];
- utilizar la instrucción «use» de la línea 10. Esta indica que el código que sigue utiliza la clase [\Exemples\Personne];
- línea 10: la instrucción «use» le permite al intérprete saber que la clase [Personne] a la que se hace referencia en la línea 14 es, en realidad, la clase [\Exemples\Personne]. Dicho esto, ¿dónde encontrará el intérprete el código de esta clase? La línea 7 se lo indica. Esta línea señala que, para ejecutar el script actual, también es necesario cargar el script [Personne.php]. Aquí se ha utilizado el nombre relativo del archivo. Por lo tanto, se buscará en la carpeta que contiene el script [classes-05.php]. Por lo tanto, los scripts [Personne.php] y [classes-05.php] deben estar en la misma carpeta. Este es el caso aquí, donde ambos se encuentran en la carpeta [exemples/classes]. La instrucción de la línea 10 es equivalente a:
use \Exemples\Personne as Personne;
La instrucción [use] anterior indica que el alias [Personne] hace referencia a la clase [\Exemples\Personne];
- línea 14: se crea un objeto [Personne]. Aquí se ejecutará implícitamente el método [__construct] de la clase [Personne];
- línea 16: muestra la persona $p. Para que se muestre, el valor de la variable $p debe transformarse en una cadena de caracteres. De manera implícita, es el método [Personne.__toString] el que se ejecuta en ese momento. Por lo tanto, este debe devolver una cadena de caracteres;
- hemos visto que el constructor de la clase [Personne] puede lanzar una excepción de tipo [\Exception]. Por lo tanto, es necesario manejarla. Por eso, el código de la línea 14 está incompleto. Se debe utilizar el código de las líneas 18 a 24 para manejar correctamente la excepción que pueda ocurrir. Aquí se genera una a propósito al pasar una edad que no es un entero. En este caso particular, la excepción que se produce es lanzada por el intérprete PHP y no por el código de la clase [Personne]. De hecho, la firma del método [Personne.__construct] es la siguiente:
function __construct(string $prenom, string $nom, int $age)
Por lo tanto, el parámetro [age] que se pasa al constructor debe ser de tipo entero. Si no es así, el intérprete PHP genera un error de tipo [TypeError]. Por otra parte, los métodos [set] de la clase [Personne] lanzan una excepción de tipo [\Exception]. Dado que el constructor que los llama no cuenta con una estructura try/catch, la excepción se propaga un nivel hacia arriba, al código que llamó al constructor, es decir, el código del script [classes-05.php]. Finalmente, el script [classes-05.php] puede recibir dos tipos de excepción: \Exception o \TypeError. Cabe señalar que, cuando el desarrollador está seguro de que ciertas excepciones no pueden ocurrir, no utilizará las opciones catch correspondientes. Aquí se utilizan sistemáticamente con el único fin de hacer una demostración. Sin embargo, las opciones catch se utilizarán para cualquier excepción posible, incluso si es poco probable;
Por esta razón, la estructura try de las líneas 18 a 24 cuenta con dos catch para manejar por separado los dos tipos de excepción;
- línea 20: se puede escribir indistintamente [Exception] o [\Exception]:
- la primera versión utiliza el nombre relativo de la clase, en relación con el espacio de nombres del script. Este no tiene espacio de nombres propio. Por lo tanto, su espacio de nombres es la raíz de los espacios de nombres: \. Así que escribir aquí [Exception] equivale a escribir [\Exception]. Sin embargo, la clase [Exception] sí se encuentra en el espacio de nombres [\];
Es preferible utilizar el nombre absoluto de las excepciones predefinidas de PHP en un script que no cuente con espacios de nombres propios. Así, si se decide asignarle un espacio de nombres a este script, la escritura de los nombres absolutos de las clases sigue siendo válida, mientras que en el otro caso, el cambio de espacio de nombres provocará errores en los nombres relativos de las clases;
- línea 21: cuando se produce una excepción, el método [Exception→getMessage] permite obtener el mensaje de error de la excepción. Lo mismo ocurre con un error de tipo [TypeError]. En el método [Personne.setPrenom], se ha escrito:
public function setPrenom(string $prénom) {
// el nombre debe ser válido
$prénom = trim($prénom);
if ($prénom === "") {
throw new \Exception("Le prénom doit être non vide");
} else {
$this->prenom = $prénom;
}
}
En la línea 5, se lanza una excepción con el mensaje de error [Le prénom doit être non vide]. Esto es lo que recuperará el método [Exception→getMessage] de la línea 29 del script [classes-05.php].
Resultados:
Exemple1, personne=[Paul,Langevin,48]
Exemple2, erreur : Argument 3 passed to Exemples\Personne::__construct() must be of the type integer, string given, called in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_18.php on line 19
Exemple3, erreur : Le prénom doit être non vide
5.7. Agregación de un método que funciona como un segundo constructor
En PHP 7, no es posible tener varios constructores con parámetros diferentes que permitan construir un objeto de diversas maneras. Por lo tanto, se pueden utilizar métodos que actúen como constructores:
// método
public function initWithPersonne(Personne $p) {
// inicializa el objeto actual con una persona $p
$this->__construct($p->prenom, $p->nom, $p->age);
}
Comentarios
- líneas 2-5: el método initWithPersonne permite asignar al objeto actual los valores de los atributos de otro objeto Personne. Aquí, recurre al constructor __construct, pero esto no es obligatorio. Podría inicializar por sí misma los atributos de la clase [Personne];
La nueva clase [Personne] es utilizada por el siguiente script [classes-06.php]:
<?php
// inclusión de la definición de la clase Persona
require_once __DIR__."/Personne.php";
// declaración del nombre calificado de la clase Persona
use \Exemples\Personne;
// prueba
// creación de un objeto Persona
try {
$p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// identidad de esta persona
print "personne=$p\n";
// creación de una segunda persona
try {
$p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// inicialización de la primera con los valores de la segunda
try {
$p->initWithPersonne($p2);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// verificación
print "personne=$p\n";
// fin
exit;
- líneas 14, 23, 30: es común que, tras una excepción, se deba detener la ejecución de un script de consola si el error encontrado es irrecuperable. Este no es el caso en un script web: no se detiene la ejecución del script, sino que se muestra una página de error. Si se está dentro de una función, no se utilizará la instrucción exit, sino return: no se detiene la ejecución del script (exit), sino que se sale de la función (return) después de generar un error;
Resultados:
5.8. Una tabla de objetos [Personne]
El siguiente ejemplo [classes-07.php] muestra que se pueden tener tablas de objetos.
<?php
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
// prueba
// creación de un array de objetos Persona
// para facilitar la comprensión del código, no se maneja la posible excepción
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];
// identidad de estas personas
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fin
exit;
Resultados:
Comentarios
- línea 9: creación de un array de 2 personas;
- línea 12: recorrido por la tabla;
- línea 13: $groupe[$i] es un objeto de tipo Personne. Se utiliza el método [Personne.__toString] para mostrarlo;
5.9. Creación de una clase derivada de la clase Persona
Se crea en un archivo [Enseignant.php] la siguiente clase [Enseignant]:
<?php
// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);
// espacio de nombres
namespace Exemples;
// una clase derivada de persona
class Enseignant extends Personne {
// atributos
private $discipline; // materia que imparte
// getter y setter
public function getDiscipline(): string {
return $this->discipline;
}
public function setDiscipline(string $discipline): void {
$this->discipline = $discipline;
}
// constructor
public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
// atributos del padre
parent::__construct($prénom, $nom, $âge);
// otros atributos
$this->setDiscipline($discipline);
}
// sobrecarga de la función __toString de la clase padre
public function __toString(): string {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
Comentarios
- línea 7: la clase [Enseignant] también forma parte del espacio de nombres [Exemples];
- línea 10: la clase Enseignant deriva (extends) de la clase Personne.. La clase derivada Enseignant hereda los atributos y métodos de su clase madre;
- línea 12: la clase Enseignant agrega un nuevo atributo, discipline, que le es propio;
- línea 25: el constructor de la clase Enseignant recibe 4 parámetros:
- 3 para inicializar su clase padre (nombre, apellido, edad), línea 27;
- 1 para su propia inicialización (disciplina), línea 29;
- línea 27: la clase derivada tiene acceso a los métodos y constructores de su clase padre mediante la palabra clave parent ::. Aquí se pasan los parámetros (nombre, apellido, edad) al constructor de la clase padre;
- líneas 33-35: el método __toString de la clase derivada utiliza el método __toString de la clase padre;
La clase [Enseignant] es utilizada por el siguiente script [classes-08.php]:
<?php
// inclusión de la definición de las dos clases
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// declaración de las dos clases utilizadas
use \Exemples\Personne;
use \Exemples\Enseignant;
// prueba
// creación de un array de objetos «Persona» y derivados
// Para simplificar el ejemplo, no se manejan las excepciones
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));
// identidad de estas personas
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fin
exit;
Comentarios
- líneas 4-5: debemos indicarle al intérprete PHP dónde se encuentran las dos clases [Enseignant, Personne];
- líneas 7-8: declaración de los nombres completos de las dos clases. Esto nos permitirá referirnos a ellas en el código simplemente por su nombre, sin el sufijo de sus espacios de nombres;
- línea 13: creamos un array que contiene un tipo [Personne] y un tipo [Enseignant];
- líneas 16-18: muestran los elementos de la tabla;
- línea 17: se llamará al método __toString de cada elemento $groupe[$i]. La clase Personne tiene un método __toString. La clase Enseignant tiene dos: el de su clase padre y el propio. Cabe preguntarse cuál se llamará. La ejecución muestra que se llamó el de la clase Enseignant. Siempre es así: cuando se invoca un método en un objeto, este se busca en el siguiente orden: en el propio objeto, en su clase padre si la tiene, luego en la clase padre de la clase padre, etc. La búsqueda se detiene tan pronto como se encuentra el método.
Resultados:
5.10. Creación de una segunda clase derivada de la clase Persona
El siguiente ejemplo crea una clase Etudiant derivada de la clase Personne, en el archivo [Etudiant.php]:
<?php
// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);
// espacio de nombres
namespace Exemples;
class Etudiant extends Personne {
// atributos
private $formation; // formación recibida
// getter y setter
public function getFormation(): string {
return $this->formation;
}
public function setFormation(string $formation): void {
$this->formation = $formation;
}
// constructor
public function __construct(string $prénom, string $nom, int $âge, string $formation) {
// atributos del padre
parent::__construct($prénom, $nom, $âge);
// otros atributos
$this->setFormation($formation);
}
// sobrecarga de la función __toString de la clase padre
public function __toString(): string {
return "[" . parent::__toString() . ",$this->formation]]";
}
}
Esta clase es utilizada por el siguiente script [classes-09.php]:
<?php
// inclusión y definición de las clases utilizadas por el script
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
require_once __DIR__."/Enseignant.php";
use \Exemples\Enseignant;
require_once __DIR__."/Etudiant.php";
use \Exemples\Etudiant;
// prueba
// creación de un array de objetos «persona» y derivados
// Para facilitar la comprensión del ejemplo, no se manejan las excepciones
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));
// identidad de estas personas
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fin
exit;
Resultados:
groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
groupe[2]=[[Steve,Boer,23],iup2 qualité]
5.11. Relación entre el constructor de una clase derivada y el de la clase padre
En algunos lenguajes orientados a objetos, el constructor de una clase derivada llama automáticamente al de su clase padre. El siguiente código [classes-16.php] muestra que con PHP 7 este no es el caso:
<?php
class Classe1 {
// constructor
public function __construct() {
print "constructeur de la classe Classe1\n";
}
}
class Classe2 extends Classe1 {
// constructor
public function __construct() {
// el constructor de la clase padre no se invoca implícitamente
print "constructeur de la classe Classe2\n";
}
}
class Classe3 extends Classe1 {
// constructor
public function __construct() {
// llamada explícita al constructor de la clase padre
parent::__construct();
// código propio de Clase3
print "constructeur de la classe Classe3\n";
}
}
// pruebas
print "test1---------\n";
new Classe2();
print "test2---------\n";
new Classe3();
Resultados
5.12. Redefinición de un método de la clase padre
Ya hemos visto que un método de la clase padre puede redefinirse en una clase hija. Así, el método [__toString] de la clase [Personne] (véase el enlace) se ha redefinido en las clases hijas [Enseignant] (véase el enlace) y [Etudiant] (véase el enlace). El script [classes-13.php] ilustra nuevamente el concepto:
<?php
// cumplimiento estricto del tipo de los parámetros de las funciones
declare(strict_types=1);
// clase principal
class Classe1 {
public function f(): int {
return 1;
}
function g(): int {
return 2;
}
}
// clase derivada
class Classe2 extends Classe1 {
// se redefine la función f de la clase padre
public function f(): int {
return parent::f() + 10;
}
}
// código
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c1=new Classe1();
print $c1->f()."\n";
Comentarios
- líneas 7-17: la clase [Classe1] define dos métodos, f y g;
- líneas 20-27: la clase [Classe2] extiende la clase [Classe1] y redefine su método f;
Resultados
Comentarios
- la línea 30 del código crea un objeto $c2 de tipo [Classe2];
- la línea 31 del código llama al método f del objeto $c2. Como este existe, se ejecuta;
- la línea 32 del código llama al método g del objeto $c2. Como este no existe, se busca en la clase padre, donde se encuentra y se ejecuta;
- la línea 33 del código crea un objeto $c1 de tipo [Classe1];
- la línea 34 del código llama al método f del objeto $c1. Como este existe, se ejecuta;
5.13. Pasar un objeto como parámetro de una función
Consideremos el siguiente script [classes-14.php]:
<?php
// cumplimiento estricto del tipo de los parámetros de las funciones
declare(strict_types=1);
// clase principal
class Classe1 {
public function f(): int {
return 1;
}
function g(): int {
return 2;
}
}
// clase derivada
class Classe2 extends Classe1 {
// se redefine la función f de la clase padre
public function f(): int {
return parent::f() + 10;
}
}
// el parámetro de la función es de tipo Clase1 o derivado
function doSomething(Classe1 $c1): void {
print $c1->f() + $c1->g() . "\n";
}
// código
// se crea un objeto de tipo Clase2 derivado de Clase1
$c2 = new Classe2();
// se llama a doSomething con
doSomething($c2);
Comentarios
- líneas 7-17: la clase [Classe1];
- líneas 20-27: una clase [Classe2] derivada de [Classe1];
- línea 30: una función que espera un parámetro de tipo [Classe1]. Cuando el tipo esperado es una clase, el parámetro efectivo puede ser un objeto del tipo esperado o de un tipo derivado;
- líneas 35-38: se llama a la función [doSomething] con un parámetro de tipo [Classe2], cuando lo que se espera es el tipo [Classe1];
Resultados
5.14. Clases abstractas
Una clase abstracta es una clase incompleta que no puede ser instanciada. Para poder usarla, es obligatorio derivarla.
¿Para qué sirve una clase abstracta? A veces tenemos clases que comparten uno o varios métodos, pero que se diferencian por otros métodos u otros atributos. En ese caso, es recomendable reunir todo lo que es común en una clase padre. Por el momento no necesitamos una clase abstracta. Pero supongamos que las clases hijas solo difieren en un método M: la firma del método sería la misma en todas las clases hijas, pero su implementación sería diferente. Para obligar a las clases hijas a implementar el método M:
- vamos a declarar la firma del método M en la clase padre. Como esta no sabe cómo implementarlo, le anteponemos al método la palabra clave abstract: esto significa que la implementación del método M se delega a las clases hijas;
- dado que la clase padre no está totalmente implementada, también se declara abstracta con la misma palabra clave «abstract». Esto hace que la clase ya no pueda ser instanciada. Es obligatorio crear una clase hija que defina la implementación del método M, para que el cuerpo de la clase padre sea utilizable;
A continuación se muestra un ejemplo [classes-15.php]:
<?php
// respetando estrictamente el tipo de los parámetros de las funciones
declare(strict_types=1);
// clase principal abstracta
abstract Class Classe1 {
// método conocido por todas las clases derivadas
public function f(): int {
return 1;
}
// método g abstracto: será definido por las clases derivadas
abstract function g(): int;
}
// clase derivada
Class Classe2 extends Classe1 {
// el método g de la clase padre debe definirse
public function g(): int {
return parent::f() + 10;
}
}
// clase derivada
Class Classe3 extends Classe1 {
// El método g de la clase padre debe definirse
public function g(): int {
return parent::f() + 20;
}
// se puede redefinir el método f de la clase padre
public function f(): int {
return 2;
}
}
// código
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c3 = new Classe3();
print $c3->f() . "\n";
print $c3->g() . "\n";
Comentarios
- líneas 7-16: la clase [Classe1] es abstracta (línea 7) porque no sabe implementar el método g de la línea 15. Por lo tanto, debe derivarse obligatoriamente para poder usarla;
- líneas 19-26: la clase [Classe2] extiende la clase [Classe1] y redefine el método g de su clase padre (líneas 22-24);
- líneas 29-41: la clase [Classe3] extiende la clase [Classe1] y redefine el método g de su clase padre (líneas 32-34);
- líneas 37-39: la clase [Classe3] redefine el método f de su clase padre;
- líneas 44-49: se crean dos objetos de tipo [Classe2] y [Classe3] y se invocan sus métodos f y g;
Resultados
5.15. Clases finales
Una clase final es una clase de la que no se pueden derivar otras clases. Consideremos el siguiente script [classes-11.php]:
<?php
// espacio de nombres
namespace Exemples;
// clase no derivable
final Class Classe1 {
}
// clase derivada
Class Classe2 extends Classe1 {
}
// código: debe generar un error
new Classe2();
Comentarios
- líneas 7-9: la palabra clave final convierte a la clase [Classe1] en una clase final de la que no se puede derivar;
- líneas 12-14: la clase [Classe2] extiende la clase final [Classe1], lo cual es un error;
- línea 17: el error solo se señalará al ejecutar el script cuando se intente manipular un objeto de tipo [Classe2];
Resultados
5.16. Métodos finales
Un método final es un método que no se puede redefinir mediante derivación. A continuación se muestra un ejemplo [classes-12.php]:
<?php
// cumplimiento estricto del tipo de los parámetros de las funciones
declare(strict_types=1);
// espacio de nombres
namespace Exemples;
// clase principal
Class Classe1 {
// este método no se puede redefinir en una clase derivada
public final function f(): int {
return 1;
}
}
// clase derivada
Class Classe2 extends Classe1 {
public function f(): int {
return 2;
}
}
// código: debe provocar un error
new Classe2();
Comentarios
- línea 13: el método f de la clase [Classe1] se declara final mediante la palabra clave final;
- línea 20: la clase [Classe2] hereda de la clase [Classe1];
- líneas 22-23: se redefine la función f de la clase padre [Classe1]. Esto debería provocar un error;
- línea 29: se crea un objeto de tipo [Classe2] para obligar al intérprete PHP a inspeccionar la clase [Classe2];
Resultados
5.17. Métodos y atributos estáticos
Un método estático es un método vinculado a la clase en la que está definido y no a los objetos que son instancias de la clase. Así, si la clase C declara un método estático M, para utilizarlo se escribirá:
- C::M si se está fuera de la clase;
- self::M si estamos dentro de la clase;
A continuación se muestra un ejemplo [classes-17.php]:
<?php
class Classe1 {
// método estático
static function say(string $message): void {
print "$message\n";
}
}
// prueba -------------------
Classe1::say("hello");
Comentarios
- línea 6: el método [say] se declara estático con la palabra clave static;
- línea 13: llamada al método estático [say] con la notación: Clase1::say;
Resultados
Consideremos ahora el siguiente código [classes-18.php]:
<?php
class Classe1 {
// atributo estático
private static $nbObjects = 0;
public function __construct() {
print "constructeur Classe1\n";
self::$nbObjects++;
}
// método estático
static function say(): void {
print self::$nbObjects ." objets de type [Classe1] ont été construits\n";
}
}
// prueba -------------------
new Classe1();
new Classe1();
Classe1::say();
Comentarios
- línea 5: se declara un atributo estático que contará el número de instancias creadas de la clase [Classe1]. No se trata de un atributo que pueda pertenecer a una instancia de la clase. De hecho, si se crean dos objetos O1 y O2, ninguno de los dos tiene conocimiento del otro. Tener un contador en la instancia no tiene sentido: cuando se crea un nuevo objeto, ¿en qué instancia se incrementará el contador? Nos veríamos obligados a incrementar el contador de un objeto en particular, dejando de lado los contadores de las otras instancias. Un atributo estático es un atributo de clase y no de instancia de la clase;
- líneas 7-10: es en el constructor donde se contarán los objetos creados, ya que la creación de cada nuevo objeto provoca la ejecución del constructor;
- línea 14: cabe destacar la notación self::$nbObjects para indicar que se hace referencia a un atributo estático de la clase en la que se encuentra el código ejecutado;
- líneas 13-15: el método estático [say] tiene como función mostrar el número de objetos creados;
- líneas 20-22: se crean dos objetos y se muestra el contador de objetos;
Resultados
constructeur Classe1
constructeur Classe1
2 objets de type [Classe1] ont été construits
5.18. Visibilidad entre la clase padre y la clase hija
Analicemos el siguiente script [classes-19.php]:
<?php
class SomeParent {
// atributo
private $attributeOfParent = 4;
// método
public function doTest(): void {
// ¿quién lo llama?
print "parent :\n";
var_dump($this);
// visualización principal
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// atributo
private $attributeOfChild = 14;
// método
public function doTest(): void {
// visualización secundaria
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// padre
parent::doTest();
}
}
// script principal
print "---test1\n";
(new SomeParent())->doTest();
print "---test2\n";
(new SomeChild())->doTest();
Comentarios
- líneas 3-17: la clase [SomeParent];
- líneas 19-32: la clase hija [SomeChild]. Se observa que extiende la clase [SomeParent] (línea 19);
- línea 5: la clase [SomeParent] solo tiene un atributo;
- líneas 8-17: el método [SomeParent::doTest] tiene como objetivo mostrar dos atributos:
- [$attributeOfParent], que pertenece a la clase [SomeParent];
- [$attributeOfChild], que pertenece a la clase [SomeChild] (línea 21);
- líneas 10-11: se muestra la identidad de quien llama; de hecho, se llamará al método de dos formas diferentes:
- desde la clase padre [SomeParent];
- desde la clase hija [SomeChild];
- líneas 13-14: se muestran los dos atributos;
- líneas 19-32: la clase hija [SomeChild], que extiende la clase [SomeParent] (línea 19);
- línea 21: la clase [SomeChild] solo tiene un atributo;
- línea 24: el método [SomeChild::doTest] tiene como objetivo mostrar dos atributos:
- [$attributeOfParent], que pertenece a la clase [SomeParent];
- [$attributeOfChild], que pertenece a la clase [SomeChild];
- líneas 26-27: visualización de los dos atributos;
- línea 30: se invoca el método [doTest] de la clase padre, que a su vez mostrará los dos atributos;
- línea 36: se invoca el método [SomeParent::doTest];
- línea 38: se invoca el método [SomeChild::doTest];
En la primera prueba, la visibilidad de ambos atributos es [private]. Por lo tanto, es de esperarse que la clase hija no vea el atributo de su clase padre. Este último debería tener al menos la visibilidad [protected]. Pero, ¿qué pasa con el atributo de la clase hija? ¿Es visible en la clase padre?
Estos son los resultados de esta primera prueba:
Comentarios
- líneas 1-10: resultados de la primera prueba en la que se invoca el método [SomeParent::doTest];
- líneas 3-6: se observa que el objeto que llama al método es de tipo [SomeParent];
- línea 7: visualización del atributo [$attributeOfParent];
- líneas 9-10: se observa que el atributo [SomeParent::$attributeOfChild] no existe. Por lo tanto, no se muestra;
- líneas 11-30: resultados de la segunda prueba, en la que se invoca el método [SomeChild::doTest];
- líneas 13-14: se observa que el atributo [SomeChild::$attributeOfParent] no existe. Por lo tanto, no se muestra. Esto es normal: el atributo [SomeParent::$attributeOfParent] es [private] y, por lo tanto, no se conoce en la clase hija;
- línea 15: se muestra el atributo [$attributeOfChild];
- líneas 16-30: nos encontramos en el método [SomeParent::doTest] llamado por la clase hija;
- líneas 17-22: se observa que [$this] es de tipo [SomeChild] con dos atributos privados;
- línea 23: sorprendentemente, [$this], de tipo [SomeChild], muestra aquí el atributo de su padre [$attributeOfParent];
- líneas 25-30: de manera igualmente sorprendente, [$this], de tipo [SomeChild], no muestra su atributo [$attributeOfChild];
Este resultado es muy sorprendente: aunque las líneas 17-21 indican que [$this] es del tipo [SomeChild], este [$this] dentro del método [SomeParent::doTest] se comporta como si fuera una instancia de la clase [SomeParent] y no de la clase [SomeChild].
Hagamos una nueva prueba con el script [classes-20.php]. El atributo [$attributeOfParent] ahora tiene visibilidad [protected] (línea 5):
<?php
class SomeParent {
// atributo
protected $attributeOfParent = 4;
// método
public function doTest(): void {
// ¿quién lo llama?
print "parent :\n";
var_dump($this);
// visualización principal
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// atributo
private $attributeOfChild = 14;
// método
public function doTest(): void {
// visualización secundaria
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// padre
parent::doTest();
}
}
// script principal
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();
Resultados
Comentarios
- línea 12: la clase [SomeChild] ahora puede ver el atributo de su padre [$attributeOfParent]. Esto es normal, ya que este último ahora tiene un alcance [protected];
- en el método [someParent::doTest], el objeto [$this] es de tipo [SomeChild] (líneas 15-20). Ve el atributo de su padre [$attributeOfparent] (línea 21), pero aún no ve su propio atributo [$attributeOfChild] (líneas 23-28);
En el tercer intento, el atributo [$attributeOfChild] también tiene un ámbito [protected]:
<?php
class SomeParent {
// atributo
protected $attributeOfParent = 4;
// método
public function doTest(): void {
// ¿quién lo llama?
print "parent :\n";
var_dump($this);
// visualización principal
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// atributo
protected $attributeOfChild = 14;
// método
public function doTest(): void {
// visualización secundaria
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// padre
parent::doTest();
}
}
// script principal
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();
Los resultados de la ejecución son los siguientes:
- línea 22: esta vez, dentro del elemento padre, [$this], de tipo [SomeChild] (líneas 15-20), ve el atributo protegido [$attributeOfChild] de su propia clase [SomeChild].
¿Qué podemos aprender de estas pruebas?
- que la instancia [$this] de una clase padre, utilizada en un método de la clase padre, ve:
- los atributos y métodos de la clase padre, independientemente de su visibilidad;
- no ve nada de los atributos y métodos de sus clases hijas;
Este es el comportamiento esperado.
- que la instancia [$this] de una clase hija, utilizada en un método de la clase hija, ve:
- los atributos y métodos de la clase padre si tienen al menos la visibilidad [protected]. Aquellos que tienen la visibilidad [private] no se ven;
- los atributos y métodos de la clase hija, independientemente de su visibilidad;
Este es el comportamiento esperado.
- que la instancia [$this] de una clase hija, utilizada en un método de la clase padre, vea:
- los atributos y métodos de la clase padre, independientemente de su visibilidad;
- los atributos y métodos de su propia clase únicamente si tienen al menos la visibilidad [protected]. Aquellos con visibilidad [private] no se ven;
Este es un comportamiento inesperado.
5.19. Codificación jSON de una clase
En una clase suele estar presente el método [__toString]: se supone que debe devolver una cadena de caracteres que represente el objeto que lo invoca. Puede resultar tentador que esta cadena sea una cadena jSON. Exploraremos esta posibilidad ahora.

Utilizaremos la siguiente clase [Personne]:
<?php
class Personne {
// atributos
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter global
public function setFromArray(array $arrayOfAttributes): Personne {
// inicialización de ciertos atributos de la clase
foreach ($arrayOfAttributes as $attribute => $value) {
$this->$attribute = $value;
}
// se devuelve el objeto
return $this;
}
// getters
public function getNom() {
return $this->nom;
}
public function getPrénom() {
return $this->prénom;
}
public function getÂge() {
return $this->âge;
}
public function getEnfants() {
return $this->enfants;
}
// __toString
public function __toString(): string {
// se identifica el objeto
var_dump($this);
// se recuperan sus atributos
$attributes = \get_object_vars($this);
var_dump($attributes);
// se devuelve la cadena jSON de los atributos
return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
}
}
Comentarios
- líneas 5-8: los cuatro atributos de la clase;
- líneas 20-35: los getter que permiten obtener el valor de estos atributos;
- líneas 11-18: un setter global que permite inicializar los atributos a partir de un array asociativo [$arrayOfAttributes] cuyas claves coinciden con los atributos de la clase;
- líneas 38-46: el método [__toString] de la clase;
- línea 42: la función PHP [get_object_vars] permite obtener el valor de los atributos de la clase en forma de un array asociativo [‘nombre’=>’nombre1’, ‘apellido’=>’apellido1’, ‘edad’=>‘edad1’, ‘hijos’=>[]];
- línea 45: se devuelve la cadena jSON de esta tabla de atributos;
Analicemos el script [json-01.php] que utiliza la clase [Personne]:
<?php
// clase Persona
require "Personne.php";
// instanciación del padre
$père = new Personne();
// inicialización del padre
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// instanciación e inicialización de hijo1
$enfant1 = (new Personne())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 17
]);
// instanciación e inicialización del hijo2
$enfant2 = (new Personne())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inicialización de los hijos del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// visualización de los elementos del padre
$enfant1=($père->getEnfants())[0];
$enfant2=($père->getEnfants())[1];
print "------------------------enfant1\n";
print "enfant1=$enfant1\n";
print "------------------------enfant2\n";
print "enfant2=$enfant2\n";
print "------------------------père\n";
print "père=$père\n";
Comentarios
- líneas 6-13: se inicializa un objeto [Personne] [$père] con el método [Personne::setFromArray], que permiteinicializar un objeto [Personne] con un array cuyas claves coincidan con los atributos de la clase [Personne];
- líneas 14-19: se inicializa un objeto [Personne] [$enfant1] de la misma manera;
- líneas 21-25: se inicializa un objeto [Personne] [$enfant2];
- líneas 27-29: se inicializa el atributo [$père→enfants] con una matriz que contiene a los dos hijos;
- líneas 32-33: se asignan los dos hijos al padre;
- línea 35: la operación [print] intentará transformar el objeto [$enfant1] en una cadena de caracteres. Para ello, utiliza el método [__toString] del objeto. Por lo tanto, esperamos ver la cadena jSON del objeto;
- líneas 38-39: hacemos lo mismo con el padre;
Los resultados son los siguientes:
------------------------enfant1
object(Personne)#2 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(7) "Sylvain"
["âge":"Personne":private]=>
int(17)
["enfants":"Personne":private]=>
NULL
}
array(4) {
["nom"]=>
string(11) "Bertholomé"
["prénom"]=>
string(7) "Sylvain"
["âge"]=>
int(17)
["enfants"]=>
NULL
}
enfant1={"nom":"Bertholomé","prénom":"Sylvain","âge":17,"enfants":null}
------------------------enfant2
object(Personne)#3 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(10) "Géraldine"
["âge":"Personne":private]=>
int(12)
["enfants":"Personne":private]=>
NULL
}
array(4) {
["nom"]=>
string(11) "Bertholomé"
["prénom"]=>
string(10) "Géraldine"
["âge"]=>
int(12)
["enfants"]=>
NULL
}
enfant2={"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}
------------------------père
object(Personne)#1 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(10) "Dieudonné"
["âge":"Personne":private]=>
int(58)
["enfants":"Personne":private]=>
array(2) {
[0]=>
object(Personne)#2 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(7) "Sylvain"
["âge":"Personne":private]=>
int(17)
["enfants":"Personne":private]=>
NULL
}
[1]=>
object(Personne)#3 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(10) "Géraldine"
["âge":"Personne":private]=>
int(12)
["enfants":"Personne":private]=>
NULL
}
}
}
array(4) {
["nom"]=>
string(11) "Bertholomé"
["prénom"]=>
string(10) "Dieudonné"
["âge"]=>
int(58)
["enfants"]=>
array(2) {
[0]=>
object(Personne)#2 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(7) "Sylvain"
["âge":"Personne":private]=>
int(17)
["enfants":"Personne":private]=>
NULL
}
[1]=>
object(Personne)#3 (4) {
["nom":"Personne":private]=>
string(11) "Bertholomé"
["prénom":"Personne":private]=>
string(10) "Géraldine"
["âge":"Personne":private]=>
int(12)
["enfants":"Personne":private]=>
NULL
}
}
}
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{},{}]}
Comentarios
- líneas 2-11: el objeto [$enfant1];
- líneas 12-21: la tabla de atributos del objeto [$enfant1]. Los tenemos todos;
- línea 22: tenemos la cadena jSON del objeto [$enfant1];
- líneas 23-44: lo mismo ocurre con el objeto [$enfant2];
- líneas 45-112: para el padre es un poco diferente, ya que su atributo [enfants] no es NULL como lo era en el caso de los hijos;
- línea 112: se observa que en la cadena jSON del padre faltan los hijos;
- líneas 79-111: se observa que, en la tabla de atributos del padre, el hijo 1 (líneas 89-98) sigue siendo un objeto, al igual que el hijo 2 (líneas 99-110). En resumen, la expresión [\get_object_vars($this)], donde [$this] representa al padre, no es recursiva: si un atributo de la clase [Personne] es a su vez un objeto, la expresión [\get_object_vars($this)] no intenta obtener su matriz de atributos;
Podemos mejorar esto. Modificamos la clase [Personne] para convertirla en la siguiente clase [Personne2]:
<?php
class Personne2 {
// atributos
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter global
public function setFromArray(array $arrayOfAttributes): Personne2 {
…
// se devuelve el objeto
return $this;
}
// getters
public function getNom() {
return $this->nom;
}
…
// __toString
public function __toString(): string {
// se recuperan los atributos del objeto
$attributes = $this->getAttributes($this);
$enfants = $attributes["enfants"];
if ($enfants != NULL) {
$attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
}
// se devuelve la cadena JSON de los atributos
return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
}
public function getAttributes(): array {
return \get_object_vars($this);
}
}
Comentarios
- líneas 36-38: la función [getAttributes] devuelve la matriz de atributos del objeto que la invoca;
- líneas 25-34: la función [__toString];
- línea 27: se recuperan los atributos de la clase [Personne] en la matriz [$attributes];
- línea 28: sabemos, según el ejemplo anterior, que [$attributes["enfants"]] es una matriz de dos objetos de tipo [Personne];
- líneas 29-31: los dos objetos se reemplazan por su matriz de atributos;
- línea 33: solo queda codificar en jSON la matriz de atributos construida;
El script [json-02.php] utiliza la clase [Personne2] de la siguiente manera:
<?php
// clase Persona2
require "Personne2.php";
// instanciación del padre
$père = new Personne2();
// inicialización
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// instanciación e inicialización de hijo1
$enfant1 = (new Personne2())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 17
]);
// instanciación e inicialización del hijo2
$enfant2 = (new Personne2())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inicialización de los hijos del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// visualización del padre
print "------------------------père\n";
print "père=$père\n";
El script [json-02.php] es idéntico al script [json-01.php], salvo que la clase [Personne2] ha sustituido a la clase [Personne].
Los resultados de la ejecución son los siguientes:
Esta vez sí se obtuvieron los hijos junto con el padre.
La solución anterior no es satisfactoria porque los hijos pueden tener hijos a su vez. Así que volvemos al problema anterior.
La clase [Personne3] resuelve este problema de la siguiente manera:
<?php
class Personne3 {
// atributos
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter global
public function setFromArray(array $arrayOfAttributes): Personne3 {
…
}
// getters
…
// __toString
public function __toString(): string {
// se devuelve la cadena JSON de los atributos
$attributes = [];
$this->getRecursiveAttributes($attributes, $this, []);
// cadena JSON de los atributos
return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
}
public function getAttributes(): array {
return \get_object_vars($this);
}
private function getRecursiveAttributes(array &$attributes, $value, $keys): void {
// análisis del valor [$value]
// $keys es una matriz [key1, key2, .., keyn]
// $value=$attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX….[keyn]
// si [$value] es un objeto, se utiliza su método [getAttributes]
if (\is_object($value)) {
// atributos del objeto [$value]
$objectAttributes = $value->getAttributes();
// ¿qué hacemos con el resultado?
if ($keys) {
// en [$attributes], vamos a reemplazar $value por la tabla de sus atributos
// hay que construir el elemento $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]
// donde $keys es la matriz [key1, key2, .., keyn]
// se toma la referencia de la tabla [$attributes]
$attribute = &$attributes;
// se escanea la tabla de claves
foreach ($keys as $key) {
// se toma la referencia del atributo
$attribute = &$attribute[$key];
}
// aquí, $attribut y $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[key(n)] son idénticos
//, ya que comparten la misma ubicación en memoria
// el objeto [$value] se reemplaza por su matriz de atributos;
// hay que escribir $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]=$objectAttributes
// lo cual equivale a $attribute = $objectAttributes
$attribute = $objectAttributes;
} else {
// sin claves: estamos al inicio de la exploración del objeto
// $objectAttributes representa los atributos de primer nivel de la clase
$attributes += $objectAttributes;
}
// quizás en [$objectAttributes] todavía haya objetos
// se exploran los atributos de [$objectAttributes]
$this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
} else {
if (\is_array($value)) {
// tenemos un array; analizamos cada uno de sus elementos
foreach ($value as $key => $élément) {
// se agrega la clave actual al array $keys
\array_push($keys, $key);
// se analiza $élément
$this->getRecursiveAttributes($attributes, $élément, $keys);
// se elimina del array $keys la clave que acaba de analizarse
\array_pop($keys);
}
}
}
}
Comentarios
- líneas 21-22: esta vez, el método [__toString] solicita los atributos de su clase y pide que esto se haga de manera recursiva: si un atributo es un objeto o una matriz de objetos, entonces cada objeto debe ser reemplazado por su matriz de atributos en la matriz de atributos final de la clase;
- líneas 31-78: la función [getRecursiveAttributes] realiza esta tarea. Se ha comentado su código. Escribir una función recursiva suele ser algo complejo. Este es el caso aquí. El lector no se perderá nada si no lo entiende. Existen bibliotecas que realizan esta tarea. La llamada recursiva se produce en las líneas 64 y 72;
- lo interesante de este código es que no se escribió únicamente para la clase [Personne3]. Es válido para cualquier clase que tenga atributos con valores de diferentes tipos de objetos, siempre y cuando las clases utilizadas por la clase principal cuenten, al igual que ella, con el método [getAttributes] de las líneas 27 a 29
El script [json-03.php] utiliza la clase [Personne3] de la siguiente manera:
<?php
// clase Persona3
require "Personne3.php";
// instanciación del padre
$père = new Personne3();
// inicialización
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// instanciación e inicialización de hijo1
$enfant1 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 27
]);
// instanciación e inicialización de hijo2
$enfant2 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inicialización de los hijos del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// instanciación e inicialización del hijo11
$enfant11 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Gaëtan",
"âge" => 2
]);
// instanciación e inicialización de hijo12
$enfant12 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Mathilde",
"âge" => 1
]);
// inicialización de los hijos de hijo1
$enfant1->setFromArray([
"enfants" => [$enfant11, $enfant12]
]);
// visualización del padre
print "------------------------père\n";
print "père=$père\n";
- líneas 30-45: se le asignan dos hijos a [$enfant1];
Los resultados de la ejecución son los siguientes:
Si le damos formato a este resultado, obtenemos lo siguiente:
père={
"nom": "Bertholomé",
"prénom": "Dieudonné",
"âge": 58,
"enfants": [
{
"nom": "Bertholomé",
"prénom": "Sylvain",
"âge": 27,
"enfants": [
{
"nom": "Bertholomé",
"prénom": "Gaëtan",
"âge": 2,
"enfants": null
},
{
"nom": "Bertholomé",
"prénom": "Mathilde",
"âge": 1,
"enfants": null
}
]
},
{
"nom": "Bertholomé",
"prénom": "Géraldine",
"âge": 12,
"enfants": null
}
]
}
Se han recuperado correctamente las cadenas jSON de todos los objetos [Personne] que forman el padre.