miércoles, 31 de julio de 2013

PHP orientado a objetos - Introducción

Definición de clase

Una clase se podría definir como una plantilla de código para generar objetos.

En PHP una clase la definiremos con la palabra clave class seguida del nombre que queramos que tenga dicha clase. El nombre puede ser cualquier combinación de letras, guiones 'bajos' y números. Pero no puede empezar por un número. Ni el nombre puede ser igual al de alguna palabra reservada de PHP. Finalmente una par de llaves limitarán el contenido de la clase.

 class Producto{  
   //contenido de la clase  
 }  

Un objeto se compone  de datos que han sido estructurados de acuerdo con la plantilla definida en una clase. Por lo tanto un objeto es una instancia (representación) o un tipo definido por una clase.
Entonces vamos a ver como generar una instancia de una clase. Para ello utilizaremos el operador new de la siguiente forma:

 p1 = new Producto();  
 p2 = new Producto();  
 p3 = new Producto();  

Como vemos podemos generar tantas instancias (objetos) como queramos de un mismo tipo (clase).

Atributos y visibilidad

Las clases pueden definir variables que almacenarán datos que pueden variar de objeto a objeto. Y que se llamarán atributos. Estas variables son similares a cualquier variable que hemos utilizado hasta ahora, salvo por el hecho que debemos definir su visibilidad en el ecosistema de la aplicación web. Esta visibilidad tiene tres niveles especificados por las tres opciones de visibilidad disponibles.

- Las variables en PHP5 son declaradas public a menos que se indique lo contrario, esto significa que se puede interactuar con ellas desde el exterior de un objeto.
- Evidentemente este comportamiento no es siempre el más deseable, ya que las variables públicas podrían ser accedidas o modificadas desde un contexto no deseado. Por lo tanto si definimos un atributo como private solo podrá ser accedida desde el interior del objeto que la contiene.
- Sin embargo, si hemos dicho que un atributo es privado solo podrá ser ser accedido o modificado desde el contexto de la clase que la define, ¿que pasará con la herencia (concepto que veremos posteriormente) de dicha clase? Para ello podemos definir un atributo como protected. Con lo que dicho atributo solo tendrá visibilidad para la propia clase y sus 'herederas'.

Ahora que ya hemos definido atributosen nuestra clase llega el momento de poder acceder a ellas. Para ello utilizaremos los caracteres '->' y seguidamente el nombre del atributo a la que queremos acceder. Recuerda que solo los atributos públicas son accesibles desde fuera del contexto de un objeto instanciado de una clase. Intentar acceder desde 'fuera' del objeto a un atributo privado  producirá un error.

 class Producto{  
   public $precio;  
   public $nombre;  
 }  
 $p1 = new Producto();  
 $p1->precio = 15;
 echo "Precio del producto" . $p1->precio;  


Métodos

Hemos hablado de la visibilidad de los atributos y lo aconsejable que es que estos atributos solo puedan ser accedidos desde dentro de un objeto. Pero hasta ahora solo hemos visto que una clase esta compuesta de atributos. Por lo tanto nos falta algo en la definición de una clase para que tenga sentido el uso de la visibilidad en los atributos. De hecho nos falta el elemento más importante de las clases: lo métodos.
Con los atributos podemos almacenar datos mientras exista un objeto. Pero necesitamos algún mecanismo para realizar acciones sobre estos datos. Y para eso están los métodos de las clases.
Podemos pensar en la equivalencia de los métodos de una clase y las funciones que utilizábamos hasta ahora. De hecho la sintaxis para definir un método de una clase es muy similar a la definición de una función. La única diferencia, a parte de que se definen en el cuerpo de una clase, es que los métodos aceptan un número de calificadores antes de la palabra clave function. Y entre dichos calificadores nuevamente tenemos la definición de visibilidad. Por lo tanto, al igual que con los atributos, tenemos que definir la visibilidad de los métodos. Y está funciona de la misma forma: un método puede ser public, private o protected.

 public function metodo($arg1, $arg2){  
 }  

Y finalmente para acceder al método desde fuera de un objeto utilizaremos la misma sintaxis que hemos usado para el acceso a atributos.

 objeto -> metodo($a,$b);  


Acceso a atributos y métodos desde dentro de la clase

Anteriormente hemos mostrado como acceder a atributos y métodos desde fuera del objeto. Pero evidentemente necesitaremos acceder a ellos desde dentro para poder definir correctamente el comportamiento de los métodos.
Para poder acceder a los atributos y métodos desde dentro de otro método de la clase necesitaremos utilizar una variable especial: $this. Esta variable es un puntero al objeto actual. Y como veremos en un posterior tutorial, no estará accesible para métodos estáticos.

 class Producto {  
   private $nombre;  
   private $precio;  
   private $id;  
   public function getNombre() {  
     return $this->nombre;  
   }  
   public function setNombre($nombre) {  
     if ($nombre != $this->getNombre())  
       $this->nombre = $nombre;  
   }  
 }  

Nota: Es importante observar como el carácter $ se utiliza en $this y no en el nombre del atributo.

Getters/Setters

Ahora tiene más sentido el uso de atributos privados y protegidos. Ya que ahora podremos definir métodos públicos  y por lo tanto accesibles desde fuera del objeto definido, y que estos interaccionen con los atributos privados o protegidos. Y de esta forma podemos ocultar o proteger lo que no queremos que se pueda hacer desde fuera del objeto.

Imaginemos un escenario con las siguientes características:
- Se quiere conocer y modificar el precio de un producto.
- Se quiere saber el nombre y el identificador de un producto pero no se podrán modificar.

 class Producto {  
   private $nombre;  
   private $precio;  
   private $id;  
   public function getNombre() {  
     return $this->nombre;  
   }  
   public function getPrecio() {  
     return $this->precio;  
   }  
   public function setPrecio($precio) {  
     $this->precio = $precio;  
   }  
   public function getId() {  
     return $this->id;  
   }  
 }  

Constantes

Algunos atributos no deben variar. Por eso PHP 5 proporciona unos atributos constantes que no se pueden variar una vez se han definido. Estos atributos son declarados antecediendo la palabra clave const. Y a diferencia de los demás atributos no llevan el carácter $. Por convención los nombres de los atributos constantes se suelen definir en letras mayúsculas.
Los atributos constantes solo pueden almacenar datos primitivos: enteros, flotantes, cadenas, arrays. Pero no pueden almacenar objetos.

La sintaxis para acceder a estas es un poco diferente a como accedemos a los demás atributos.:
- Si accedemos desde fuera de la clase, ya que las constantes se consideran atributos públicos, utilizaremos el nombre de la clase seguido de '::' y el nombre de la constante.
- Si accedemos desde dentro de la clase utilizaremos la palabra reservada self seguida de '::' y del nombre de la constante.
Esta sintaxis es la misma que veremos cuando hablemos de los atributos estáticos.

 class Producto {  
   const DISPONIBLE = 0;  
   const FUERA_DE_STOCK = 1;  
   //..  
   function prueba(){  
     echo self::DISPONIBLE;  
   }  
 }  
 echo Producto::DISPONIBLE;  

Una vez definida una constante si se intenta cambiar el valor obtendremos un error.

Constructor

Para terminar de definir una clase simple, necesitamos una forma de inicializar los atributos definidos. Esta inicialización la haremos mediante un método especial llamado constructor. Este constructor es llamado automáticamente cada vez que se crea un objeto. Por lo que no dispone de visibilidad. De hecho no se puede invocar desde dentro o fuera del objeto. Con la excepción de la llamada al constructor de la clase padre. Pero esto ya se explicará cuando hablemos de herencia en PHP.
La sintaxis será similar a la vista para los métodos de la clase, pero con un nombre reservado y sin definición de visibilidad.

   function __construct($nombre, $precio, $id) {  
     $this->nombre = $nombre;  
     $this->precio = $precio;  
     $this->id = $id;  
   }  

Una clase en PHP (al menos en hasta PHP 5.4) solo puede tener un constructor a diferencia de otros lenguajes como Java. Para simular  múltiples constructores hay diferentes técnicas. Vamos a ver una de ellas, la cual no implica un conocimiento más avanzado de las clases en PHP. En el tutorial de conceptos avanzados de programación orientada a objetos en PHP veremos una segunda técnica. Que además es la más común.

Para simular el uso de múltiples constructores vamos a utilizar 4 funciones clave:

- Obtener una lista (array) con los argumentos pasados a la función invocada. Con la función func_get_args() obtendremos dicha lista. En este caso obtendremos una lista con los argumentos con los que se ha llamado al constructor de la clase.
- Obtener el número de argumentos con los que se ha llamado, en este caso, al constructor de la clase. Dicho número lo obtendremos con la función func_num_args().
- Una vez conocidos la lista y el número de argumentos podremos utilizarlos para componer un nombre de un método que simulará a un constructor. Hemos nombrado a los métodos, que simulan los distintos constructores, con nombres que solo varían por un número que representa la cantidad de argumentos. Pero antes de llamar a dichos métodos tenemos que asegurarnos de que existen. Y de ello se encarga la función method_exists(). Esta función necesita dos argumentos. El primero de ellos será una instancia o nombre de clase donde supuestamente se encuentra el método. Y el segundo parámetro es el nombre de dicho método.
- Finalmente vamos a llamar a la función que simulará el constructor. Para ello vamos a utilizar la función call_user_func_array(). El primer argumento será el nombre de la función a llamar. Si la función es en realidad un método de una clase, como es nuestro caso, habrá que indicarlo de la siguiente forma array('nombreClase o Objeto', 'nombreFunción'). El segundo parámetro es un array de parámetros que queremos pasar a la función o método de la clase.

 class Ejemplo  
 {   
   function __construct()   
   {   
     $args = func_get_args();   
     $num = func_num_args();   
     $f='__construct'. $num;
     if (method_exists($this,$f)) {   
       call_user_func_array(array($this,$f),$args);   
     }   
   }   
   function __construct1($a1)   
   {   
     echo "__construct con 1 param llamado: " . $a1;   
   }   
   function __construct2($a1,$a2)   
   {   
     echo "__construct con 2 params llamado: " . $a1 . "," . $a2;   
   }   
   function __construct3($a1,$a2,$a3)   
   {   
     echo "__construct con 3 params llamado: " . $a1 . "," . $a2 . "," . $a3;   
   }   
 }   
 $ej1 = new Ejemplo('a');   
 $ej2 = new Ejemplo('a','b');   
 $ej3 = new Ejemplo('a','b','c');   

Destructor

 El destructor es un método que se dispara automáticamente (por lo tanto no hay que invocarlo) cuando se elimina un objeto, más concretamente antes de que se elimine el objeto. Se definirá con el nombre de __destruct(). Este método no aceptará argumentos y su funcionalidad puede variar dependiendo de la clase y de las inicializaciones realizadas en el constructor de la clase. Pero normalmente se usará para liberar recursos inicializados. Como pueden ser liberar una conexión con la bases de datos, liberar los punteros a ficheros, liberar variables, etc...

 class FileLogger{  
   private $gestor;  
   private $ficheroLog;  
   function __construct($nombreFichero,$modoApertura = 'a'){  
     $this->ficheroLog = $nombreFichero;    
     $this->gestor = fopen($nombreFichero, $modoApertura) or die('No es posible abrir el fichero');    
   }      
   public function log($mensaje){  
     $mensaje = date("F j, Y, g:i a") . ': ' . $mensaje . "\n";  
     fwrite($this->gestor,$mensaje);  
   }  
   function __destruct(){  
     if($this->gestor){  
       fclose($this->gestor);  
     }  
   }  
 }  

Cuando un script sea interrumpido mediante la función exit() se llamarán a los destructores de las clases. Cuando un el valor de un objeto es puesto a 'null' también se llama al destructor del mismo.

No estamos obligados a definir un destructor, sin embargo es aconsejable ya que de esta forma se tendrá el control de lo que se libera o no al eliminarse un objeto. Y siempre es mejor que el programador controle esto y no que sea PHP.

Nota: intentar lanzar una excepción en el destructor ocasionará un error 'fatal'.

Encapsulación

Hasta ahora hemos explicado la importancia de la visibilidad de los atributos y métodos de una clase para ocultar los datos de un objeto. De tal forma que solo se puedan modificar con operaciones definidas para ese tipo de objeto. Y este concepto tiene un nombre reservado: Encapsulación.

Podríamos definir la encapsulación como una técnica que permite localizar y ocultar los detalles de un objeto. La encapsulación previene que un objeto sea manipulado por operaciones distintas de las definidas. La encapsulación es como una caja negra que esconde los datos y solamente permite acceder a ellos de forma controlada. Y de esta manera mantener a salvo los detalles de la representación si solo nos interesa el comportamiento del objeto.

Abstracción

La abstracción consiste en encontrar características y comportamientos esenciales de un objeto.
Vamos a pensar en los automóviles:
- Podríamos pensar que características similares tienen todos los automóviles.  Todos tendrán una marca, un modelo, número de bastidor, peso, llantas, puertas, ventanas, etc.
- Y en cuanto a su comportamiento todos los automóviles podrán frenar, acelerar, ir marcha atrás, derrapar, etc.

Pero, ¿Qué tiene que ver esto con las clases introducidas en este tutorial?. Pues todo, de hecho el concepto de clase es la representación y el mecanismo por el cual se gestionan las abstracciones. Por lo tanto el objetivo de la programación orientada a objetos es conseguir abstraer entidades del mundo real en clases. Y de esta manera obtener objetos que interaccionarán entre sí para crear aplicaciones.

Entradas relacionadas

PHP orientado a objetos - Herencia

No hay comentarios:

Publicar un comentario