5. Los objetos
5.1. Cualquier variable puede convertirse en un objeto con atributos (exemple_14)
<?php
// 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";
// asigna el valor de objeto1 a objeto2
$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";
// asigna la referencia de objeto1 a objeto3
$obj3 = &$obj1;
// modifica obj3
$obj3->attr2 = 10;
// muestra los dos objetos
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// ¿Es un objeto un diccionario?
print count($obj1)."\n";
while (list($attribut, $valeur) = each($obj1))
print "obj1[$attribut]=$valeur\n";
// fin
exit;
?>
Resultados:
objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
objet1=[un,10]
objet3=[un,10]
1
obj1[attr1]=un
obj1[attr2]=10
Comentarios
- línea 4: 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.
- línea 13: la expresión $obj2=$obj1, cuando $obj1 es un objeto, es una copia de objetos por referencia. Por lo tanto, $obj2 y $obj1 son referencias al mismo objeto. El objeto en sí puede ser modificado por cualquiera de las dos referencias.
- línea 20: la expresión $obj3=&$obj1 asigna a $obj3 la referencia (la dirección) de la variable $obj1. Si $obj1 es un objeto, como es el caso aquí, la expresión anterior es equivalente a $obj3=$obj1.
- líneas 28-29: muestran que un objeto se puede recorrer 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 27: la función count se puede aplicar a un objeto, 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.2. Una clase Persona sin atributos declarados (exemple_15)
<?php
class Personne {
// atributos de la clase
// no declarados: se pueden crear dinámicamente
// método
function identité() {
// a primera vista, utiliza atributos inexistentes
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// prueba
// los atributos son públicos y se pueden crear dinámicamente
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// llamada a un método
print "personne=" . $p->identité() . "\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. Una clase es un conjunto que agrupa atributos y funciones llamadas métodos. No es obligatorio declarar los atributos.
- líneas 9-12: 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 9-11) del método identité, $this hace referencia al mismo objeto que $p.
5.3. La clase Persona con atributos declarados (exemple_16)
<?php
class Personne {
// atributos de la clase
var $prénom;
var $nom;
var $âge;
// método
function identité() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// prueba
// los atributos son públicos
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// llamada a un método
print "personne=" . $p->identité() . "\n";
// fin
exit;
?>
Resultados:
Comentarios
- líneas 6-8: los atributos de la clase se declaran explícitamente con la palabra clave var.
5.4. La clase Persona con un constructor (exemple_17)
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 que cumple con las buenas prácticas de PHP 5:
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// métodos de lectura y escritura
public function getPrénom() {
return $this->prénom;
}
public function getNom() {
return $this->nom;
}
public function getAge() {
return $this->âge;
}
public function setPrénom($prénom) {
$this->prénom = $prénom;
}
public function setNom($nom) {
$this->nom = $nom;
}
public function setAge($age) {
$this->âge = $age;
}
// constructor
function __construct($prénom, $nom, $âge) {
// se utilizan los setters
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 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 3-48: la clase Personne
- líneas 6-8: 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 uno protegido, visible desde el interior 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:
- (continuación)
- utilizar los métodos públicos set y get (el nombre de estos métodos puede ser cualquiera) de las líneas 11 a 33. Entonces se podrá escribir:
- (continuación)
- utilizar el constructor de las líneas 36 a 41. En ese caso, se escribirá:
La escritura anterior invoca automáticamente el método de la clase Personne denominado __construct.
- Línea 54: esta línea muestra la persona $p en forma de cadena de caracteres. Para ello, se utiliza el método de la clase Personne denominado __toString.
5.5. La clase Persona con control de validez en el constructor (exemple_18)
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 se colocarán 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:
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// getters y setters
public function getPrénom() {
return $this->prénom;
}
public function getNom() {
return $this->nom;
}
public function getAge() {
return $this->âge;
}
public function setPrénom($prénom) {
// el nombre debe ser distinto de vacío
if (!preg_match("/^\s*(.+?)\s*$/", $prénom, $champs)) {
throw new Exception("Le prénom doit être non vide");
} else {
$this->prénom = $champs[1];
}
}
public function setNom($nom) {
// el apellido no debe estar vacío
if (!preg_match("/^\s*(.+?)\s*$/", $nom, $champs)) {
throw new Exception("Le nom doit être non vide");
} else {
$this->nom = $champs[1];
}
}
public function setAge($âge) {
// la edad debe ser válida
if (!preg_match("/^\s*(\d+)\s*$/", $âge, $champs)) {
throw new Exception("L'âge doit être un entier positif ou nul");
} else {
$this->âge = $champs[1];
}
}
// constructor
function __construct($prénom, $nom, $âge) {
// se utilizan los setters
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// prueba
// creación de un objeto Persona
$p = new Personne("Paul", "Langevin", 48);
// identidad de esta persona
print "personne=$p\n";
// creación de un objeto Persona con error
try {
$p = new Personne("xx", "yy", "zz");
} catch (Exception $e) {
print $e->getMessage();
}
// fin
exit;
?>
Resultados:
Comentarios
- líneas 23-30: inicialización del atributo prénom y verificación del valor de inicialización
- línea 25: uso de una expresión regular. El nombre es una secuencia de uno o más caracteres, que puede ir precedida o seguida de espacios.
- línea 26: si el apellido está vacío, se lanza una excepción; de lo contrario, se almacena el nombre (línea 28)
- línea 66: uso del constructor de la clase Personne
- línea 68: uso del método __toString de la clase Personne
- líneas 70-74: manejo de la posible excepción lanzada por el constructor de la clase Personne.
5.6. Agregación de un método que funciona como segundo constructor (exemple_19)
En PHP 5, 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:
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// getters y setters
public function getPrénom() {
return $this->prénom;
}
….
// constructor
function __construct($prénom, $nom, $âge) {
// se utilizan los set
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método
public function initWithPersonne($p) {
// inicializa el objeto actual con una persona $p
$this->__construct($p->prénom, $p->nom, $p->âge);
}
// método toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// prueba
// creación de un objeto Persona
$p = new Personne("Paul", "Langevin", 48);
// identidad de esta persona
print "personne=$p\n";
// creación de una segunda persona
$p2 = new Personne("Laure", "Adeline", 67);
// inicialización de la primera con los valores de la segunda
$p->initWithPersonne($p2);
// verificación
print "personne=$p\n";
// fin
exit;
?>
Resultados:
Comentarios
- líneas 26-29: el método initWithPersonne permite asignar al objeto actual los valores de los atributos de otro objeto Personne. Aquí, utiliza el constructor __construct, pero no es obligatorio.
5.7. Una matriz de objetos Persona (exemple_20)
El siguiente ejemplo muestra que se pueden tener tablas de objetos.
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// métodos getter y setter
public function getPrénom() {
return $this->prénom;
}
...
// constructor
function __construct($prénom, $nom, $âge) {
// se utilizan los set
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método
public function initWithPersonne($p) {
….
}
// método toString
function __toString() {
...
}
}
// prueba
// creación de una tabla de objetos Persona
$groupe = array(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 39: creación de una tabla con 2 personas
- línea 42: recorrido por la tabla
- línea 43: $groupe[$i] es un objeto de tipo Personne. Se utiliza el método __toString para mostrarlo.
5.8. Creación de una clase derivada de la clase Persona (exemple_21)
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// getters y setters
public function getPrénom() {
return $this->prénom;
}
...
// constructor
function __construct($prénom, $nom, $âge) {
// se utiliza el método set
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método
public function initWithPersonne($p) {
// inicializa el objeto actual con una persona $p
$this->__construct($p->prénom, $p->nom, $p->âge);
}
// método toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// una clase derivada de Persona
class Enseignant extends Personne {
// atributos
private $discipline; // materia que imparte
// getter y setter
public function getDiscipline() {
return $this->discipline;
}
public function setDiscipline($discipline) {
$this->discipline = $discipline;
}
// constructor
public function __construct($prénom, $nom, $âge, $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() {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
// prueba
// creación de una tabla de objetos «Persona» y derivados
$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;
?>
Resultados:
Comentarios
- líneas 3-35: la clase Personne
- línea 38: 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 41: la clase Enseignant agrega un nuevo atributo, discipline, que le es propio
- línea 55: el constructor de la clase Enseignant recibe 4 parámetros
- 3 para inicializar su clase padre (nombre, apellido, edad)
- 1 para su propia inicialización (disciplina)
- línea 57: 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ínea 64: el método __toString de la clase derivada utiliza el método __toString de la clase padre.
- línea 71: un array que contiene un objeto Enseignant y un objeto Personne.
- línea 74: se recorre el array
- línea 75: 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ó al de la clase Enseignant. Siempre es así: cuando se invoca un método en un objeto, se busca dicho método 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.
5.9. Creación de una segunda clase derivada de la clase Persona (exemple_22)
El siguiente ejemplo crea una clase Etudiant derivada de la clase Personne:
<?php
class Personne {
// atributos de la clase
private $prénom;
private $nom;
private $âge;
// getters y setters
public function getPrénom() {
return $this->prénom;
}
...
// constructor
function __construct($prénom, $nom, $âge) {
// se utiliza el método set
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// método
public function initWithPersonne($p) {
// inicializa el objeto actual con una persona $p
...
}
// método toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// una clase derivada de persona
class Enseignant extends Personne {
// atributos
private $discipline; // materia que imparte
// getter y setter
...
// constructor
public function __construct($prénom, $nom, $âge, $discipline) {
// atributos del padre
parent::__construct($prénom, $nom, $âge);
// otros atributos
$this->setDiscipline($discipline);
}
// sobrecarga de la función _identité__toString de la clase padre
public function __toString() {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
// una clase derivada de Persona
class Etudiant extends Personne {
// atributos
private $formation; // materia que imparte
// getter y setter
public function getFormation() {
return $this->formation;
}
public function setFormation($formation) {
$this->formation = $formation;
}
// constructor
public function __construct($prénom, $nom, $âge, $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() {
return "[" . parent::__toString() . ",$this->formation]";
}
}
// prueba
// creación de una tabla de objetos «persona» y derivados
$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.10. Las interfaces (exemple_23)
Una interfaz es una estructura que define prototipos de métodos. Una clase que implementa una interfaz define el código de todos los métodos de la interfaz.
<?php
// una interfaz
interface IExemple {
function ajouter($i, $j);
function soustraire($i, $j);
}
// implementación 1 de la interfaz
class Classe1 implements IExemple {
public function ajouter($a, $b) {
return $a + $b + 10;
}
public function soustraire($a, $b) {
return $a - $b + 20;
}
}
// implementación 2 de la interfaz
class Classe2 implements IExemple {
public function ajouter($a, $b) {
return $a + $b + 100;
}
public function soustraire($a, $b) {
return $a - $b + 200;
}
}
// función
function calculer($a, $b, IExemple $interface) {
print $interface->ajouter($a, $b)."\n";
print $interface->soustraire($a, $b)."\n";
}
// ------------------- main
// creación de 2 objetos de tipo Clase1 y Clase2
$c1 = new Classe1();
$c2 = new Classe2();
// llamada a la función calcular
calculer(4, 3, $c1);
calculer(14, 13, $c2);
?>
Resultados
Comentarios
- líneas 4-8: la interfaz IExemple define dos métodos: la función ajouter (línea 5) y la función soustraire (línea 7). La interfaz no define el código de estos métodos. Son las clases que implementan la interfaz las que lo harán.
- línea 11: la clase Classe1 implementa la interfaz IExemple. Por lo tanto, define un código para los métodos ajouter (línea 13) y soustraire (línea 17).
- líneas 24-34: lo mismo ocurre con la clase Classe2
- línea 37: el método calculer admite tres parámetros: dos enteros, $a y $b, y un objeto $interface que implementa la interfaz IExemple. Se podría haber escrito function calcular($a,$b,$interface) sin indicar el tipo IExemple del parámetro $interface. Al indicarlo, se permite que el intérprete PHP verifique que el parámetro efectivo sea efectivamente un objeto que implemente la interfaz IExemple.
- líneas 38-39: uso de los métodos ajouter y soustraire del objeto $interface. Dado que este implementa la interfaz IExemple, sabemos que cuenta con estos dos métodos.
- líneas 44-45: creación de dos objetos de tipos diferentes, pero que ambos implementan la interfaz IExemple.
- línea 47: se pasa a la función calculer el objeto $c1, de tipo Classe1, que implementa la interfaz IExemple.
- línea 48: se pasa a la función calculer, el objeto $c2 de tipo Classe2 que implementa la interfaz IExemple.