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. El árbol de scripts

5.2. Cualquier variable puede convertirse en un objeto con 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
// las dos variables son entonces 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 designa el 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í mismo puede ser modificado por cualquiera de las dos referencias;
- líneas 23-27: pretenden mostrar que $obj1 y $obj2 son dos variables diferentes: no se encuentran en la misma dirección de memoria:
- $obj2=$obj1 ha copiado 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 siguiente, 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 las dos 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, 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 de 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 Persona. 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 identidad 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 Persona. 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 escribir de diversas formas: 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 identity en el objeto $p;
- línea 22: el método identity de la clase Personne se aplica al objeto $p. En el código (líneas 8-11) del método identity, $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 Persona poco habituales, como las que se podían encontrar en PHP 4. No se recomienda seguir estos ejemplos. A continuación, presentamos una clase Persona [classes-04.php] que se ajusta a 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;
// getters y setters
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 pasa por los setters
$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";
// cambio de 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. Las otras palabras clave que se pueden utilizar son:
- public: convierte el atributo en un atributo público visible desde fuera de la clase,
- protected: convierte el atributo en un atributo protegido, visible desde el interior de la clase y de las clases derivadas de esta;
- 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 Persona. Como el atributo nombre 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-34. Entonces se podrá escribir:
- utilizar el constructor de las líneas 37-42. Entonces se escribirá:
La escritura anterior llama automáticamente al método de la clase Persona 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 Persona denominado __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 de visibilidad explícito, la función tiene una visibilidad implícita public;
5.6. La clase «Persona» con comprobaciones 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 colocar las mismas verificaciones en dos lugares diferentes, se pueden incluir estas últimas en los métodos set. Si un valor de inicialización de un objeto resulta ser incorrecto, se lanzará una excepción. A continuación se muestra un ejemplo.
En primer lugar, trasladamos la definición de la clase Persona 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;
// getters y setters
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 pasa por los set
$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 exige que se respete el tipo de los parámetros de las funciones tal y como se declara;
- línea 7: define un espacio de nombres (namespace). El nombre completo (denominado «calificado») de la clase Persona es entonces \Ejemplos\Persona. Obsérvese el carácter \ que inicia el nombre calificado: se trata entonces de un nombre calificado absoluto. Si este carácter no está presente, se trata de un nombre calificado relativo (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 relativa B. Si la clase A forma parte del espacio de nombres E1 y B del espacio de nombres E2, en el código de A se accederá a B mediante la notación absoluta \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. Por otra parte, 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 árbol del archivo [Personne.php] es [exemples/classes/Personne.php] y, por lo tanto, no se corresponde con el espacio de nombres. No hay que confundir árbol con espacio de nombres. El nombre completo de una clase utiliza un espacio de nombres y no tiene nada que ver con el árbol del archivo PHP de la clase. La relación entre el árbol y el espacio de nombres es opcional y puede no respetarse, como hemos hecho aquí;
- líneas 12-14: los tres atributos privados de la clase;
- líneas 29-37: inicialización del atributo prenom y comprobació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 está 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 ha utilizado la clase predefinida [Exception]. Aquí nos vemos obligados a utilizar su nombre absoluto [\Exception]. Si utilizamos 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 Persona. Así, 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 erróneo
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 indicar al intérprete PHP dónde puede encontrar la definición de esta clase. De eso se encargan las instrucciones [include fichier] y [require fichier]. Aquí se ha utilizado 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 gestionar el caso de inclusiones múltiples de un mismo archivo. Podemos imaginar aquí un proyecto compuesto por varios scripts PHP, varios de los cuales hacen referencia a la clase [Personne]. Su ejecución provocará entonces la inclusión del archivo [Personne.php] 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 utilizar indistintamente los signos / 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 espacio de nombres para la clase [Personne], esta se busca en el 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 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 va a encontrar el intérprete el código de esta clase? Es la línea 7 la que se lo indica. Esta indica que, para ejecutar el script actual, también hay que 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]. Es el método [__construct] de la clase [Personne] el que se ejecutará aquí de forma implícita;
- línea 16: muestra la persona $p. Para que se muestre, el valor de la variable $p debe transformarse en una cadena de caracteres. Implícitamente, es el método [Personne.__toString] el que se ejecuta entonces. 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, hay que gestionar esta excepción. Por eso, el código de la línea 14 está incompleto. Hay que utilizar el de las líneas 18-24 para gestionar correctamente la excepción que pueda producirse. Aquí generamos una a propósito pasando una edad que no es un entero. En este caso concreto, 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] pasado 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, por su parte, una excepción de tipo [\Exception]. Dado que el constructor que los llama no tiene una estructura try/catch, la excepción se remonta un nivel, hasta el 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 producirse, no utilizará las opciones catch correspondientes. Aquí se utilizan sistemáticamente con el único fin de realizar una demostración. No obstante, las opciones catch se utilizarán para cualquier excepción posible, incluso si es poco probable;
Por este motivo, la estructura try de las líneas 18-24 tiene dos catch para gestionar por separado los dos tipos de excepción;
- línea 20: se puede escribir indistintamente [Exception] o [\Exception]:
- el primero, version, utiliza el nombre relativo de la clase, relativo al espacio de nombres del script. Este no tiene ninguno. Su espacio de nombres es, por tanto, la raíz de los espacios de nombres: \. Por lo tanto, escribir aquí [Exception] equivale a escribir [\Exception]. Sin embargo, la clase [Exception] se encuentra efectivamente en el espacio de nombres [\];
Es preferible utilizar el nombre absoluto de las excepciones predefinidas de PHP en un script que no tenga espacios de nombres propios. Así, si se decide asignar 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 no puede estar 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;
}
}
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. Añadido de un método que actúa como segundo constructor
En PHP 7, no es posible tener varios constructores con parámetros diferentes que permitan construir un objeto de diversas formas. Por lo tanto, se pueden utilizar métodos que actúan 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 Persona. Aquí, recurre al constructor __construct, pero no es obligatorio. Podría inicializar ella 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 frecuente que, tras una excepción, haya que detener la ejecución de un script de consola si el error encontrado es irrecuperable. 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) tras haber generado un error;
Resultados:
5.8. Una matriz 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 una matriz de objetos Persona
// para facilitar la comprensión del código, no se gestiona 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 una matriz de 2 personas;
- línea 12: recorrido por la tabla;
- línea 13: $groupe[$i] es un objeto de tipo Persona. 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 impartida
// 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 Profesor deriva (extends) de la clase Persona. La clase derivada Profesor hereda los atributos y métodos de su clase madre;
- línea 12: la clase Profesor añade un nuevo atributo disciplina que le es propio;
- línea 25: el constructor de la clase Profesor recibe 4 parámetros:
- 3 para inicializar su clase padre (nombre, apellidos, 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, apellidos, 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 una matriz de objetos Persona y derivados
// para simplificar el ejemplo, no se gestionan 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 indicar 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 una matriz que contiene un tipo [Personne] y un tipo [Enseignant];
- líneas 16-18: muestran los elementos de la matriz;
- línea 17: se va a llamar al método __toString de cada elemento $groupe[$i]. La clase Persona tiene un método __toString. La clase Profesor tiene dos: el de su clase padre y el propio. Cabe preguntarse cuál se llamará. La ejecución muestra que se ha llamado al de la clase Profesor. Siempre es así: cuando se llama a 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 en cuanto 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 «Estudiante» derivada de la clase «Persona» 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 una matriz de objetos persona y derivados
// para facilitar la comprensión del ejemplo, no se gestionan 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 no es así:
<?php
class Classe1 {
// fabricante
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 de nuevo 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 el método f de esta;
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 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 instanciarse. Para poder utilizarla, 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 se diferencian por 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, anteponemos al método la palabra clave abstract: esto significa que la implementación del método M se traslada 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 instanciarse. 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;
He aquí 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 estar definido
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 utilizarse;
- 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. 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 provocar 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. He aquí 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] extiende 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 instancia 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;
He aquí 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: Classe1::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 de la clase [Classe1] creadas. 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 va a incrementar el contador? Nos veríamos obligados a incrementar el contador de un objeto concreto, dejando de lado los contadores de las demás 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: observemos 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
Examinemos el siguiente script [classes-19.php]:
<?php
class SomeParent {
// atributo
private $attributeOfParent = 4;
// método
public function doTest(): void {
// ¿quién 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 la persona que llama: de hecho, vamos a llamar al método de dos formas diferentes:
- desde la clase padre [SomeParent];
- desde la clase hija [SomeChild];
- líneas 13-14: visualización de 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íneas 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: llamada al método [doTest] de la clase padre, que a su vez mostrará los dos atributos;
- línea 36: se llama al método [SomeParent::doTest];
- línea 38: se llama al método [SomeChild::doTest];
En la primera prueba, la visibilidad de los dos atributos es [private]. Por lo tanto, cabe esperar que la clase hija no vea el atributo de su clase padre. Este debería tener al menos la visibilidad [protected]. Pero, ¿qué ocurre 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 llama al método [SomeParent::doTest];
- líneas 3-6: se ve 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 ve 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 ve que el atributo [SomeChild::$attributeOfParent] no existe. Por lo tanto, no se muestra. Es normal: el atributo [SomeParent::$attributeOfParent] es [private] y, por lo tanto, no se conoce en la clase hija;
- línea 15: visualización del atributo [$attributeOfChild];
- líneas 16-30: nos encontramos en el método [SomeParent::doTest] llamado por la clase hija;
- líneas 17-22: vemos que [$this] es de tipo [SomeChild] con dos atributos privados;
- línea 23: sorprendentemente, [$this], de tipo [SomeChild], ve aquí el atributo del padre [$attributeOfParent];
- líneas 25-30: de forma igualmente sorprendente, [$this], de tipo [SomeChild], no ve su atributo [$attributeOfChild];
Este resultado es muy sorprendente: aunque las líneas 17-21 indican que [$this] es de 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] tiene ahora una visibilidad [protected] (línea 5):
<?php
class SomeParent {
// atributo
protected $attributeOfParent = 4;
// método
public function doTest(): void {
// ¿quién 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 ve el atributo de su padre [$attributeOfParent]. Esto es normal, ya que este último tiene ahora un ámbito [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 sigue sin ver 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 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 padre, [$this] de tipo [SomeChild] (líneas 15-20) ve el atributo protegido [$attributeOfChild] de su propia clase [SomeChild].
¿Qué conclusiones podemos sacar 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]. Los 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]. Los que tienen la visibilidad [private] no se ven;
Se trata de un comportamiento inesperado.
5.19. Codificación jSON de una clase
En una clase suele estar presente el método [__toString]: se supone que devuelve una cadena de caracteres que representa el objeto que lo invoca. Puede resultar tentador que esta cadena sea una cadena jSON. Exploramos esta vía 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 getters que permiten obtener el valor de estos atributos;
- líneas 11-18: un setter global que permite inicializar los atributos a partir de una tabla asociativa [$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 una tabla asociativa [‘nombre’=>’nombre1’, ‘apellido’=>’apellido1’, ‘edad’=>‘edad1’, ‘hijos’=>[]];
- línea 45: se devuelve la cadena jSON de esta tabla de atributos;
Examinemos 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 una matriz que tiene claves idénticas a 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 de 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:
------------------------hijo1
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}
------------------------hijo2
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}
------------------------padre
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 para 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 los hijos;
- línea 112: se ve que en la cadena jSON del padre faltan los hijos;
- líneas 79-111: se ve 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] por 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, por el ejemplo anterior, que [$attributes["enfants"]] es una matriz de dos objetos de tipo [Personne];
- líneas 29-31: los dos objetos se sustituyen 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í que hemos obtenido a los hijos junto con el padre.
La solución anterior no es satisfactoria, ya que los hijos pueden tener a su vez hijos. 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 sustituir $value por la matriz 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
// y comparten la misma ubicación en memoria
// el objeto [$value] se sustituye por su matriz de atributos;
// hay que escribir $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]=$objectAttributes
// lo que 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] aún haya objetos
// exploramos los atributos de [$objectAttributes]
$this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
} else {
if (\is_array($value)) {
// tenemos una matriz; analizamos cada uno de sus elementos
foreach ($value as $key => $élément) {
// se añade la clave actual a la matriz $keys
\array_push($keys, $key);
// analizamos $élément
$this->getRecursiveAttributes($attributes, $élément, $keys);
// se elimina de la tabla $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 se haga de forma recursiva: si un atributo es un objeto o una matriz de objetos, entonces cada objeto debe ser sustituido 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 este trabajo. La llamada recursiva tiene lugar en las líneas 64 y 72;
- el interés de este código es que no se ha escrito únicamente para la clase [Personne3]. Es válido para cualquier clase que tenga atributos con valores de diferentes tipos de objetos, siempre que las clases utilizadas por la clase principal tengan, al igual que ella, el método [getAttributes] de las líneas 27-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 hijo 11
$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 asignan dos hijos a [$enfant1];
Los resultados de la ejecución son los siguientes:
Si formateamos 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
}
]
}
Hemos recuperado correctamente las cadenas jSON de todos los objetos [Personne] que forman el padre.