sábado, 6 de diciembre de 2014

[Parte 3] Programacion orientada a objetos con PHP

POO Mas Avanzado

En la parte anterior vimos algunos conceptos de POO Avanzado, con un enfasis particular relacionado a herencia. 

Los temas de este capitulo llegan a ser mas esoterico, y no siempre es necesario una aplicacion web, pero estos conceptos deben estar en su radar a medida que se sienta mas comodo con la programacion orientada a objetos como un enfoque de programacion. 

Clases abstractas y Metodos

En algunas situaciones en la que se aplica herencia, nunca seria apropiado crear una instancia de una clase padre. Por ejemplo, en el ejemplo de Mascota, la intencion seria la creacion de una subclase especifica para cada tipo de Mascota y nunca realmente crear un objeto de tipo de Mascota. En tales casos, seria mas apropiado definir una clase base abstracta, en lugar de una clase base estandar. 

Las clases abstractas son versiones de la plantilla de una clase padre. Al definir una clase abstracta, puede indicar el comportamiento general de que las subclases deben tener. Dicho de otra manera, una clase abstracta define las interfaces: como las clases derivadas de este tipo de base van a ser utilizadas. Las subclases son responsables de definir las implementaciones reales de esas interfaces.

Las clases abstractas son diferentes de las clases normales en que el intento de crear un objeto de resultados de tipo de una clase abstracta resulta en un error fatal (Ver la imagen de abajo). En cambio, las clases abstractas estan destinadas a ser extendidas, y luego se crea una instancia de esa clase extendida. 



Este enfoque comienza con la palabra clave abstract:

abstract class NombreClase{
       
    }
Las clases abstractas suelen tener metodos abstractos. Estos se definen asi: 

abstract function nombreMetodo();
abstract function nombreMetodo($var1,$var2);
Eso es todo!, aun no se define la funcionalidad del metodo; en cambio, la funcionalidad sera determinada por la clase extendida de la clase abstracta. Si desea agregar visibilidad a la definicion, solo tiene que añadir la palabra clave correspondiente despues de la palabra abstract: 

abstract public function nombreMetodo();
Asi es como la parte de Mascota puede lucir:

    abstract class Mascota {
        protected $_name;
        abstract public function obtenerNombre();
    }


Entonces Gato contendria:


class Gato extends Mascota {
        function obtenerNombre(){
            return $this->_name;
        }
    }

Tenga en cuenta que la implementacion del metodo abstracto en el de la clase extendida Gato::obtenerNombre(); por ejemplo- tiene que cumplir con la misma visibilidad o mas debil. Si la funcion abstracta es publica, la version extendida tambien debe ser publica. Si la funcion de abstracta esta protegida, a continuacion, la version extendida solo puede ser protegida o publica. Nunca crearia un metodo abstracto privado, ya que un metodo privado no se puede heredar. 

En todos los casos, la version implementada del metodo tambien debe tener el mismo numero de argumentos que la definicion abstracta (es decir, la misma firma).


Tenga en cuenta que si una clase tiene incluso un metodo abstracto, la clase en si debe ser abstracta.
Sin embargo una clase abstracta, puede tener metodos no abstractos, asi como atributos, todos los cuales tambien seran heredados por la clase derivada. 
Para poner esto en accion, vamos a volver a los ejemplos de geometria, como Rectangulo. 
Esa clase podria ser una extension de una clase de forma mas generica. Vamos a instruir la clase abstracta Figura y una clase Hijo, Triangulo.


La clase abstracta Figura puede ser el padre de muchos tipos de Figuras (bidimensionales).



 <?php
 /*
 * Esta pagina define la clase abstracta Figura
 * Esta clase no contiene atributos
 * Esta clase  contiene dos metodos abstractos:
 * - obtenerArea()
 * - obtenerPerimetro()
 */

 abstract class Figura {
  // Sin atributos declarados
  // Sin constructores o destructores definidos aqui

  // Metodo para calcular y devolver el area
  abstract protected function obtenerArea();

  // Metodo para calcular y devolver el perimetro.
  abstract protected function obtenerPerimetro();
 } // Terminamos la clase Figura.

 
?>

1.- Definimos la clase figura: abstract class Figura {
2.- Definimos el primer metodo abstracto: abstract protected function obtenerArea();
Esta linea dice que cualquier clase que se extiende de la clase Figura necesita definir un metodo obtenerArea(). Ademas, este metodo no deberia tener ningun argumento y la visibilidad tiene que ser publica o protegida (la misma visibilidad o mas debil).
3.- Definimos el segundo metodo abstracto: abstract protected function obtenerPerimetro();
4.- Completamos la clase: } 


 <?php
 /*
 * Esta pagina define la clase Triangulo
 * Esta clase contiene dos atributos:
 * - private $_lados (array)
 * - private $_perimetro (number)
 * La clase contiene 3 metodos:
 * - __construct()
 * - obtenerArea()
 * - obtenerPerimetro()
 */

 class Triangulo extends Figura {
  // Declaramos los atributos:
  private $_lados = array();
  private $_perimetro = NULL;

  // Constructor:
  function __construct($s0 = 0, $s1 = 0, $s2 = 0){
   // Almacenamos los valores en un Array:
   $this->_lados[] = $s0;
   $this->_lados[] = $s1;
   $this->_lados[] = $s2;

  // Calculamos el perimetro:
  $this->_perimetro = array_sum($this->_lados);
  } // Fin del constructor

  // Metodo para calcular y devolver el area:
  public function obtenerArea(){
   // Calculamos y devolvemos el area:
   return (sqrt(
   ($this->_perimetro/2) *
   (($this->_perimetro/2) - $this->_lados[0]) *
   (($this->_perimetro/2) - $this->_lados[1]) *
   (($this->_perimetro/2) - $this->_lados[2]) 
   ));
  } // Fin del metodo obtenerArea().

  // Metodo para devolver el perimetro:
  public function obtenerPerimetro(){
   return $this->_perimetro;
  } // Fin del metodo obtenerPerimetro().
 } // Fin de la clase Triangulo.
?>


1.- Comenzamos declarando la clase Triangulo: class Triangulo extends Figura {

2.- Declaramos los atributos:
private $_lados = array();
private $_perimetro = NULL;
 

El primer atributo almacenara el tamaño de los tres lados (como alternativa puede hacer tres variables independientes), la segunda variable almacenara el perimetro. 
Solo estoy añadiendo este atributo por que el perimetro sera utilizado en el calculo del area, asi que es bueno tener una variable en lugar de recuperarla a traves de un metodo. Todos los atributos son privados, ya que no deben ser accedidos fuera de cualquier clase y no puedo imaginar como una clase Triangulo sera heredada (en cuyo caso pueden estar protegidos).


3.- Definimos el constructor:
function __construct($s0 = 0, $s1 = 0, $s2 = 0){
            // Almacenamos los valores en un Array:
            $this->_lados[] = $s0;
            $this->_lados[] = $s1;
            $this->_lados[] = $s2;

            // Calculamos el perimetro:
            $this->_perimetro = array_sum($this->_lados);
        } // Fin del constructor




El constructor toma tres argumentos para los tres lados del triangulo. Esos valores se colocan en el array $_lados, y luego se calcula el perimetro. La funcion array_sum() suma todos los elementos del array.


4.- Creamos un metodo obtenerArea():

public function obtenerArea(){
            // Calculamos y devolvemos el area:
            return (sqrt(
            ($this->_perimetro/2) *
            (($this->_perimetro/2) - $this->_lados[0]) *
            (($this->_perimetro/2) - $this->_lados[1]) *
            (($this->_perimetro/2) - $this->_lados[2])
            ))
        } // Fin del metodo obtenerArea().


Si recuerda la geometria, sabe que el area del triangulo es igual a la mitad de la base por altura. 




Por supuesto, para hacer este calculo la base tendria que determinar la base (el lado mas largo, no es un problema) y la altura (que requiere trigonometria). En lugar de eso utilizaremos la Formula de Heron's. Para implementarlo en el codigo PHP.


Ejemplo: ¿Cuál es el área de un triángulo en el que cada lado es de 5?

Paso 1: s = (5 + 5 + 5) / 2 = 7,5 (perimetro)
Paso 2: A = (7,5 × 2,5 × 2,5 × 2,5) = (117,1875) = 10.825 ...


5.- Creamos el metodo obtenerPerimetro():
 
public function obtenerPerimetro(){
            return $this->_perimetro;
        } // Fin del metodo obtenerPerimetro().


Este es el segundo metodo abstracto en Figura que debe ser implementado aqui. Para este ejemplo, simplemente retorna el atributo perimetro. No tiene que crear el atributo perimetro, este metodo fue devuelto por $this->_perimetro = array_sum($this->_lados);

6.- Completamos la clase: } // Fin de la clase Triangulo.   

Para usar la clase Triangulo:


 <!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Triangulo</title>
</head>
<body>
<?php
 // Leemos las definiciones de las clases:
 require('triangulo.php');

 // Establecemos los lados del triangulo:
 $s1 = 5;
 $s2 = 10;
 $s3 = 13;

 // Imprimimos una introduccion y creamos un nuevo triangulo
 echo "<h2>Con los lados de $s1, $s2, y $s3...</h2>";
 $t = new Triangulo($s1,$s2,$s3);

 // Imprimimos el area:
 echo '<p>El area de un triangulo es '. $t->obtenerArea().'</p>';
 // Imprimimos el perimetro:
 echo '<p>El perimetro de el triangulo es '.$t->obtenerPerimetro().'</p>';

 // Completamos la pagina:
 unset($t);
?>
</body>
</html>
 
 


En UML, una clase se marca como abstracto ya sea por su nombre en cursiva, o colocando llaves junto a su nombre {abstracta}.

El metodo __toString()

Si se define un metodo __toString() en una clase, PHP invoca ese metodo automaticamente, cuando un objeto de este tipo es usado como cadena. Por ejemplo, seria llamado si trato de hacer esto:


$a = new AlgunaClase();
echo $a;


Los objetos son por definicion, tipos de variables complejos, no destinados a ser usados como si fueran variables escalares. Sin embargo, es bastante comun para los desarrolladores agregar metodos __toString(), por lo menos como una herramienta sencilla y util de depuracion. 

Como desarrollador, mediante la definicion de un metodo __toString() para una clase, tiene que decidir exactamente lo que sucede podria un objeto de esa clase ser utilizado como Cadena, en lugar de limitarse a que PHP genere un error. 




Interfaces

Una interfaz es similar a la clase abstracta. Interfaces, como clases abstractas, identifican la funcionalidad (es decir, metodos) que debe ser definido por una clase especifica. Para crear una interfaz utilice la palabra clave interface.

Luego dentro de las llaves, se definen las firmas de metodos, no su implementacion real:

interface iAlgo {
        public function algunaFuncion($var);
    }


(Convencionalmente, los nombres de interfaces a menudo comienzan con i minuscula, pero no es requerido). Tenga en cuenta que los metodos de una interfaz deben ser publicos. Ademas, las interfaces solo identifican metodos; nunca incluyen atributos. Para asociar una clase con una interfaz, utilice el operador implements en la definicion de la clase.

class AlgunaClase implements iAlgo{}

La clase entonces debe definir todos los metodos que aparecen en la interfaz o un error fatal se producira:



El error fatal es creado por tener una clase implementada de una interfaz sin implementar todos los metodos.


Clase abstracta vs Interfaz

La diferencia entre una interfaz y una clase abstracta puede parecer sutil. Recuerde que una clase abstracta tiene la intencion de ser extendida por una clase mas especifica, de la que probablemente se cree una instancia de objeto. Como ya has visto, una clase abstracta puede definir un objeto generico, como una Figura. A la inversa, la interfaz no es heredado por una clase, por lo que no se debe pensar en una interfaz como una forma de definir libremente un objeto completo. En cambio, una interfaz establece un contrato para la funcionalidad que una clase debe tener, independientemente del tipo de clase. 
Otra forma de distinguir entre clases abstractas e interfaces es que las clases abstractas siguen teniendo "es una" relacion con la clase derivada. Las interfaces no tienen "es una" relacion con clases derivadas, aunque se podria decir que la clase derivada tiene un "tiene los mismos comportamientos como" relacion con una interfaz.
En el siguiente ejemplo, vamos a crear una interfaz para la funcionalidad CRUD estandar. La sigla CRUD se refiere a la capacidad de CREAR, LEER, ACTUALIZAR y ELIMINAR datos - las cuatro acciones basicas que se requieren para muchos tipos diferentes de contenido utilizado en sitios y aplicaciones.





 <!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Interface</title>
</head>
<body>
<?php
 // Esta pagina define y usa la interface iCrud
 
 /*
 * La interface iCrud
 * La interface identifica cuatro metodos:
 * - crear()
 * - leer()
 * - actualizar()
 * - eliminar()
 */

 interface iCrud {
  public function crear($datos);
  public function leer();
  public function actualizar($datos);
  public function eliminar();
 }

 /* El usuario implementa la clase interface iCrud.
 * La clase contiene dos atributos:
 * - private $_usuarioId;
 * - private $_usuario;
 * La clase contiene los cuatro metodos de la interface, mas un constructor.
 */

 class Usuario implements iCrud {

  private $_usuarioId;
  private $_usuario;

  // El constructor toma un array de datos:
  function __construct($datos){
   $this->_usuarioId = uniqid();
   $this->_usuario = $datos['usuario'];
  } // Fin del constructor

  // Este metodo tambien toma un array de datos:
  function crear($datos){
   self::__construct($datos);
  }

  // Esta funcion retorna la informacion acerca del objeto actual:
  function leer(){
   return array('usuarioId' => $this->_usuarioId, 'usuario' => $this->_usuario);
  }

  // Funcion para actualizar el objeto actual:
  function actualizar($datos){
   $this->_usuario = $datos['usuario'];
  }

  // Funcion para deshacerse del objeto actual:
  public function eliminar(){
   $this->_usuario = NULL;
   $this->_usuarioId = NULL;
  } 
 } // Terminamos la clase Usuario


 // Identificamos la informacion del usuario:
 $usuario = array('usuario' => 'arthusu');

 // Imprimimos una pequeña introduccion:
 echo "<h2>Creando un nuevo usuario</h2>";

 // Crear un nuevo usuario:
 $yo = new Usuario($usuario);

 // Obtenermos el ID de usuario:
 $info = $yo->leer();
 echo "<p>El ID de usuario es {$info['usuarioId']}.</p>";

 // Cambiamos el nombre de usuario:
 $yo->actualizar(array('usuario'=>'arthusuxD'));

 // Confirmamos que el usuario ha sido actualizado:
 $info = $yo->leer();
 echo "<p>El nombre de usuario ahora es {$info['usuario']}.</p>";

 // Eliminamos los registros:
 $yo->eliminar();

 // Eliminamos el objeto:
 unset($yo);
?>
</body>
</html>


1.- Declaramos la interface iCrud:
interface iCrud {
        public function crear($datos);
        public function leer();
        public function actualizar($datos);
        public function eliminar();
    }
La interface iCrud identifica cuatro metodos necesarios. Dos de los metodos esperan tomar los datos como argumento. Los otros dos metodos sin argumentos. Usted vera como funciona este sistema en la interface. Todos los metodos requeridos son publicos.

2.-  Definimos la clase Usuario:
class Usuario implements iCrud {

        private $_usuarioId;
        private $_usuario;
La clase Usuario implementa iCrud, significa que debe definir los cuatro metodos identificados en la interface. Para demostrar este concepto, sin abrumar con codigo, voy a definir dos atributos de la clase, ambos seran privados.


3.- Definimos el constructor:

function __construct($datos){
            $this->_usuarioId = uniqid();
            $this->_usuario = $datos['usuario'];
        } // Fin del constructor


El constructor se va tomar un array de datos como su unico argumento. Utilizara estos datos para asignar valores a las variables privadas internas. Logicamente tambien se puede añadir alguna validacion para los datos que se proveen. El constructor tambien crea un valor usuarioId unico invocando la funcion uniqid()
En una aplicacion real, el constructor podria crear un nuevo usuario registrarlo en la base de datos y asignar una clave primaria generando automaticamente el valor al atributo interno.

4.- Definimos el metodo crear():

function crear($datos){
            self::__construct($datos);
        }


Mediante la implementacion de la interface iCrud, esta clase esta obligada a tener un metodo crear() con un solo argumento. Sin embargo, el constructor ya lo hace lo requerido para crear un nuevo objeto de este tipo, entonces este metodo puede llamar al constructor (utilizando la palabra clave self, que se refiere a la clase actual, añadiendo el operador de resolucion), pasamos la cantidad de datos que le provimos. 
Este metodo podria ser usado en situaciones cuando un nuevo objeto Usuario es creado (quizas de este modo creamos un nuevo registro en la base de datos).

5.- Definimos el metodo leer():

function leer(){
            return array('usuarioId' => $this->_usuarioId, 'usuario' => $this->_usuario);
        }


El metodo leer() no toma ningun argumento y devuelve un array de informacion. En un ejemplo hipotetico, la informacion es representada por las variables internas.
En una aplicacion real, el metodo leer() deberia poder asociar la informacion con la base de datos, usando el interno, valor ID que conocemos para devolver.

6.- Definimos el metodo actualizar():

function actualizar($datos){
            $this->_usuario = $datos['usuario'];
        }



Presumiblemente, el Id de usuario no puede ser actualizado, entonces el metodo actualizar() solo escribe en el atributo interno. De nuevo utilizas una validacion apropiada $datos['usuario'] si existe.

7.- Definimos el metodo eliminar() y completamos la clase:

 public function eliminar(){
            $this->_usuario = NULL;
            $this->_usuarioId = NULL;
        }
    } // Terminamos la clase Usuario


El metodo eliminar() limpia los atributos. En el mundo real, se borraria su correspondiente usuario de la base de datos. 

8.- Creamos un nuevo objeto Usuario:

$usuario = array('usuario' => 'arthusu');

    // Imprimimos una pequeña introduccion:
    echo "<h2>Creando un nuevo usuario</h2>";

    // Crear un nuevo usuario:
    $yo = new Usuario($usuario);



Para crear una interface mas util, hay dos metodos que esperan recibir un solo argumento, con lo que podria ser un array de datos. Si la clase Usuario tambien almacena un correo y contraseña, estos pueden ser representados en el array tambien.

9.- Obtenemos el ID de Usuario:

$info = $yo->leer();
echo "<p>El ID de usuario es {$info['usuarioId']}.</p>";

10.- Cambiamos el nombre de usuario y lo leemos:

$yo->actualizar(array('usuario'=>'arthusuxD'));

    // Confirmamos que el usuario ha sido actualizado:
    $info = $yo->leer();
    echo "<p>El nombre de usuario ahora es {$info['usuario']}.</p>";


11.- Eliminamos el registro:

$yo->eliminar();

Nota que esta linea no elimina el objeto Usuario. Esta solo limpia los valores de guardados internamente.

12.- completamos la pagina:

unset($yo);
?>
</body>
</html>



Interface es una palabra con multiples significados. Genericamente interface se refiere a los tipos de informacion sobre las clases y metodos reflejado por un diagrama UML. En otras palabras una interface, explica como una clase o metodo se utiliza. Alternativamente, la interface puede referirse a un contrato que se puede enlazar con una clase. 

Otro beneficio que tiene utilizar interfaces ante las clases abstractas y herencia es que las clases en PHP no se extienden desde multiples padres. Las clases, sin embargo, pueden ser implementadas multiples interfaces separando por coma:

class AlgunaClase implements iA, iB{  

El operador instanceof tambien puede ser utilizado para probar si una clase implementa una interface.



En UML, una interface es indicado por llevar <<interface>> en su nombre (en este caso interface).

En UML, para indicar que una clase implementa una interface, se utiliza una flecha discontinua desde la clase a la interface (de modo que la flecha esta apuntando a la interface).

Traits

Nuevo en PHP 5.4 es el soporte para traits. Traits son usados para resolver problemas en lenguajes de programacion orientado a objetos como PHP que solo permiten herencia simple. Por ejemplo, digamos que estas diseñando un sitio web que tiene varias clases: Usuario, Pagina, Formulario de contacto, etc. Mientras estas desarrollando el sitio web, ayudaria tener una herramienta de depuracion que pueda imprimir la informacion acerca de un objeto dado, independientemente de su tipo.

function arrojarObjeto(){
        // Imprimir la informacion
    }


Se podria añadir esta definicion para cada clase, pero eso seria innecesariamente redundante (y un obstaculo el cual superar en caso de cambiar la definicion). Normalmente, cuando se tiene un metodo que seria necesario en multiples clases, la herencia es la solucion. 
Sin embargo, en PHP cada clase solo se puede heredar desde una sola clase padre, y no hay una clase padre en comun para cada una de ellas. La solucion es, entonces, los traits. Traits le permiten agregar esta funcionalidad para la clase sin usar herencia.

Para crear un trait, usamos la palabra clave trait, seguido por el nombre y la definicion:

    trait tAlgunTrait {
        // Atributos
        function algunaFuncion(){
            // Hacer algo
        }
    }


(Estilisticamente, usted podria iniciar su Trait con "t" minuscula, pero esto no es necesario).

Al igual que una clase abstracta y una interface, los traits no pueden ser instanciados (es decir, no se puede crear un objeto desde un trait). En su lugar tu agregas un Trait a traves de una clase usando la palabra clave use dentro de la definicion de la clase:

    class AlgunaClase {
        use tAlgunTrait;
        // Resto de la clase.
    }


Asi como incluyes un script PHP externo puedes crear un script con codigo usable en el mismo, añadiendo la declaracion use NombredelTrait el codigo estara disponible en la clase.

Ahora, cuando tu creas un objeto de una AlgunaClase escrita, este metodo tiene una metodo algunaFuncion():

    $obj = new AlgunaClase();
    $obj->algunaFuncion();


En el siguiente ejemplo, vamos a implementar la depuracion del ejemplo trait y utilizarlo con alguna clase. Al hacer esto, voy a utilizar tres funciones de PHP no mencionadas anteriormente pero son practicamente autoexplicativas.

 

 <?php
 // Esta pagina define el trait tDebug.

 /* El trait tDebug.
 * El trait define un metodo:
 * - arrojarObjeto():
 */

 trait tDebug {
  // Este metodo arroja todos los datos acerca del objeto actual:

  public function arrojarObjeto(){

   // Obtenemos el nombre de la clase:
   $clase = get_class($this);

   // Obtenemos todos los atributos:
   $atributos = get_object_vars($this);

   // Obtenemos los metodos:
   $metodos = get_class_methods($this);

   // Imprimimos una cabecera:
   echo "<h2>Informacion acerca del objeto $clase</h2>";

   // Imprimimos los atributos:
   echo '<h3>Atributos</h3><ul>';
   foreach($atributos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</ul>';

   // Imprimimos los metodos:
   echo '<h3>Metodos</h3><ul>';
   foreach($metodos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</ul>';
  } // Fin del metodo arrojarObjeto().
 } // Fin del trait tDebug.
?>


Clase Rectangulo modificada usando el trait tDebug:



 <?php
 // Esta pagina define el trait tDebug.

 /* El trait tDebug.
 * El trait define un metodo:
 * - arrojarObjeto():
 */

 trait tDebug {
  // Este metodo arroja todos los datos acerca del objeto actual:

  public function arrojarObjeto(){

   // Obtenemos el nombre de la clase:
   $clase = get_class($this);

   // Obtenemos todos los atributos:
   $atributos = get_object_vars($this);

   // Obtenemos los metodos:
   $metodos = get_class_methods($this);

   // Imprimimos una cabecera:
   echo "<h2>Informacion acerca del objeto $clase</h2>";

   // Imprimimos los atributos:
   echo '<h3>Atributos</h3><ul>';
   foreach($atributos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</li></ul>';

   // Imprimimos los metodos:
   echo '<h3>Metodos</h3><ul>';
   foreach($metodos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</li></ul>';
  } // Fin del metodo arrojarObjeto().
 } // Fin del trait tDebug.
?>

Usando el trait tDebug:


 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Trait</title>
</head>
<body>
<?php
 // Esta pagina usa el trait tDebug a traves del objeto Rectangulo.

 // Incluimos la definion del trait:
 require('tDebug.php');

 // Incluimos la definicion de la clase:
 require('Rectangulo.php');

 // Creamos un nuevo objeto:
 $r = new Rectangulo(42,37);

 // Arrojamos la informacion:
 $r->arrojarObjeto();

 // Eliminamos el objeto:
 unset($r);
?>
</body>
</html>
 
 
Esta es la salida generada por el metodo arrojarObjeto().

Si obtiene un error de analisis cuando ejecuta el script, podria ser por que no esta usando la version de PHP 5.4 o una superior o no reconoce la palabra clave trait.

Para incorporar multiples traits en una clase, debemos separar cada trait por una coma:

use tTrait1, tTrait2;

Los traits pueden tener metodos abstractos que luego debe ser implementado por cualquier clase que utilice el trait.

Una vez mas, usted podria añadir tDebug dentro de la clase Rectangulo, en lugar de un script principal para el mismo.

Copiando y clonando objetos

En PHP cuando se crea una copia de un objeto, PHP en realidad crea una nueva referencia a ese objeto, no enteramente un nuevo objeto. En otras palabras, ambas variables apuntaran a la misma cosa, y los cambios realizados a traves de un objeto puede ser reflejado por otro:

    $a = new AlgunaClase();
    $a->val = 1;
    $b = $a;
    $b->val = 2;
    echo $a->val; // 2


Mas formalmente puesto, esto significa que PHP asigna objetos por referencia, no por valor. PHP hace esto por razones de rendimiento, como tener multiples copias de objetos enteros, cuando no es necesario, es caro.

Si usted realmente quiere dos separados, objetos individuales, es necesario crear un clon:

    $a = new AlgunaClase();
    $a->val = 1;
    $b = clone $a; // Objetos separados!
    $b->val = 2;
    echo $a->val; // 1


Cuando el operador clone es usado, PHP llevara a cabo lo que se llama una "superficial copia". Si quieres cambiar la manera en que un objeto clon esta hecho, se puede definir un metodo __clone() dentro de la clase. Este metodo se llama siempre que un clon se hace, y se ocuparia de la clonacion como mejor le parezca. Para mas detalles consultar el manual de PHP.


Precedencia de un trait

Si un trait es utilizado como un metodo en una clase que tiene un metodo con el mismo nombre dentro de la clase, PHP decide que metodo toma precedencia (es decir, cual sera ejecutado cuando el metodo sea llamado). Si el metodo es definido en la clase, esta version tendra precedencia sobre el metodo del trait. Si el metodo definido en la clase es actualmente heredado de otra clase, entonces el trait tomara precedencia.


Type Hinting

Type hinting es en la programacion un acto de indicar que tipo de valor es esperado. Por ejemplo, que tipo de valor una funcion espera recibir como parametro. Type hinting no juega un gran papel en el codigo PHP procedural por que no se puede sugerir tipos simples (ejemplo: enteros o cadenas). Pero usted puede hacer alusion a los objetos, que es mas util. Para llevar a cabo el type hinting, debemos anteponer al nombre de la variable el tipo de clase esperada:

class AlgunaClase {
        function hacerEsto(OtraClase $var){
           
        }
    }



Si el argumento pasado a los metodos hacerEsto() no es de tipo OtraClase, o de una clase subderivada. PHP generara un error Fatal.

    class OtraClase{ }
    $algo = new OtraClase();
    $otra = new OtraClase();
    $algo->hacerEsto($otra);
    $algo->hacerEsto($algo);


Para ver un ejemplo de type hinting, este proximo script definira una clase Departamento con un metodo agregarEmpleado(). Ese metodo se utiliza para añadir un nuevo empleado a la lista de los empleados del departamento, el metodo solo acepta un parametro de tipo Empleado.



 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Type Hinting</title>
</head>
<body>
<?php
 // Esta pagina define el uso de las clases Departamento y Empleado.

 # *** CLASES *** #
 
 /* Clase Departamento.
 *  Esta clase contiene dos atributos:
 * nombre y empleados[].
 * Esta clase contiene dos metodos:
 * - __construct()
 * - agregarEmpleado()
 */

 class Departamento {
  private $_nombre;
  private $_empleados;

  function __construct($nombre){
   $this->_nombre = $nombre;
   $this->_empleados = array();
  }

  function agregarEmpleado(Empleado $e) {
   $this->_empleados[] = $e;
   echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
  }
 } // Fin de la clase Departamento

 /* Clase Empleado
 * La clase contiene un atributo:
 * nombre
 * La clase contiene dos metodos:
 * - __construct()
 * - obtenerNombre()
 */

 class Empleado {
  private $_nombre;

  function __construct($nombre) {
   $this->_nombre = $nombre;
  } 

  function obtenerNombre(){
   return $this->_nombre;
  }
 } // Fin de la clase Empleado.

 # *** Fin de las clases *** #

 // Creamos un departamento:
 $rh = new Departamento('Recursos Humanos');

 // Creamos empleados:
 $e1 = new Empleado('Jane Doe');
 $e2 = new Empleado('John Doe');

 // Agregamos empleados al departamento:
 $rh->agregarEmpleado($e1);
 $rh->agregarEmpleado($e2);

 // Eliminamos los objetos:
 unset($rh, $e1, $e2);

?>
</body>
</html>

1.- Declaramos la clase Departamento:

class Departamento {
        private $_nombre;
        private $_empleados;

        function __construct($nombre){
            $this->_nombre = $nombre;
            $this->_empleados = array();
        }



La clase Departamento tiene dos atributos privados: uno almacena el nombre del departamento y el otro almacena un array de empleados en el departamento. El constructor asigna el valor a la variable privada $_nombre y crea $_empleados como un array vacio.


2.- Añadimos el metodo AgregarEmpleado() para completar la clase:

function agregarEmpleado(Empleado $e) {
            $this->_empleados[] = $e;
            echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
        }
    } // Fin de la clase Departamento


Este metodo debe ser llamado para agregar un empleado al departamento del objeto actual. Este toma un argumento, que gracias al type hinting, debe ser Empleado. Si un argumento apropiado es recibido, puede ser agregado internamente, al array $_empleados. Para propositos de confirmacion (y para tener que el script finalmente arroje algo), la verificacion del empleado agregado es mostrada.

Nota que $e debe ser un objeto de tipo Empleado, para poder invocar cualquier tipo de metodos de empleados, tal como obtenerNombre().

3.- Definimos la clase Empleado:

class Empleado {
        private $_nombre;

        function __construct($nombre) {
            $this->_nombre = $nombre;
        }

        function obtenerNombre(){
            return $this->_nombre;
        }
    } // Fin de la clase Empleado.


Estamos definiendo una clase Empleado de una manera minima. El constructor toma un nombre de empleado como argumento, y este es asignado a un atributo interno. El metodo obtenerNombre() es publico, y es la forma correcta de buscar el nombre del empleado.

Los errores se arrojan cuando el tipo de argumento no son enviados. Son excepciones lo que significa que pueden ser "atrapados", es una manera mas elegante de manejar este problema.

Type hinting puede ser utilizado en funciones tambien (es decir, en los no metodos, las funciones que estan fuera de una clase).

Namespaces

Agregados en la version de PHP 5.3 tiene soporte para namespaces. Los namespaces proporcionan una solucion para un problema comun en POO: como comenzar a utilizar mas y mas clases, incluidas las definidas por otros desarrolladores y en las bibliotecas de terceros, los conflictos pueden ocurrir si multiples clases tienen el mismo nombre. Los namespaces previenen estos conflictos por lo que permite organizar el codigo en grupos. Esto tiene el efecto para permitir que para utilizar con seguridad nombres descriptivos sin preocupacion por conflictos.

Una analogia adecuada (que el manual de PHP tambien usa) es comparar los namespaces para la creacion de una estructura de directorios en su computadora. No pueden colocar dos archivos llamadas funciones.php en la misma carpeta. Sin embargo, usted puede colocar uno en la carpeta misUtilidades/ y otro en la carpeta tusUtilidades/, con lo que ambas versiones de funciones.php estan disponibles.
Hay limites en lo que se puede colocar en un namespace, en concreto:

* Clases
* Interfaces
* Funciones
* Constantes

No se podria, por ejemplo, crear un namespace solo para mantener algunas variables. Para definir un namespace, querras crear un nuevo archivo que solo almacenara el codigo de namespace. Esto es un tanto virtual requisito de PHP y una mejor practica de diseño. Dentro de ese archivo, se crea un namespace utilizando la palabra clave namespace, seguido por su identificador:

namespace AlgunNamespace;

Tenga en cuenta que esta debe ser la primera linea de un codigo PHP en un archivo, y que el archivo no puede incluso tener algun codigo HTML antes de el codigo (aunque, desde una perspectiva de diseño, usted no quiere mezclar HTML y namespaces de todas maneras). Usted puede tener codigo PHP comentado antes de esa linea. Sin embargo, cualquier codigo que siga de esa linea sera automaticamente colocado dentro de ese namespace:

    namespace AlgunNamespace;

    class AlgunaClase { }


Los namespaces pueden tener subespacios, al igual que usted tendria niveles de directorios en su ordenador. Para ello, indicamos un subespacio de nombre utilizando la barra invertida:

    namespace MisUtilidades\ManejoUsuario;

    class Login { }


Una vez que haya definido un namespace, puede hacer referencia a ella mediante el uso de barra invertida de nuevo. Primero, sin embargo, tienes necesidad de incluir el archivo que define el namespace. 

require('AlgunNameSpace.php');

A continuacion, utilizar barras invertidas para indicar que un namespace se esta utilizando:

$obj = new \AlgunNamespace\AlgunaClase();

O:

require('MisUtilidades\ManejoUsuario\Usuario.php');
$obj = new \MisUtilidades\ManejoUsuario\Login();

Como una opcion estilistica, una sugerencia es utilizar su nombre u organizacion como nivel superior en el namespace:

namespace MiNombre\Utilerias\Usuario;

Otra sugerencia es organizar los archivos de la misma forma que colocas los namespaces.

Como ejemplo practico de esto, vamos a colocar las clases Departamento y Empleado dentro del namespace MiNamespace\Compania.



 <?php
 // Este script define un namespace Compañia, con dos clases.

 // declaramos el namespace:
 namespace MiNamespace\Compania;

  # *** CLASES *** #
 
 /* Clase Departamento.
 *  Esta clase contiene dos atributos:
 * nombre y empleados[].
 * Esta clase contiene dos metodos:
 * - __construct()
 * - agregarEmpleado()
 */

 class Departamento {
  private $_nombre;
  private $_empleados;

  function __construct($nombre){
   $this->_nombre = $nombre;
   $this->_empleados = array();
  }

  function agregarEmpleado(Empleado $e) {
   $this->_empleados[] = $e;
   echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
  }
 } // Fin de la clase Departamento

 /* Clase Empleado
 * La clase contiene un atributo:
 * nombre
 * La clase contiene dos metodos:
 * - __construct()
 * - obtenerNombre()
 */

 class Empleado {
  private $_nombre;

  function __construct($nombre) {
   $this->_nombre = $nombre;
  } 

  function obtenerNombre(){
   return $this->_nombre;
  }
 } // Fin de la clase Empleado.

 # *** Fin de las clases *** #

?>


1.- Declaramos el namespace MiNamespace\Compania:

namespace MiNamespace\Compania;

La premisa aqui es que todo su codigo reusable entraria al namespace Minamespace, Cuyo nombre lo haria mas unico. Dentro de toda esa biblioteca, todas las clases y codigos relacionados con la creacion de proyectos basados en la empresa iria en el namespace de la empresa, declarado aqui.

2.- Definimos la clase Departamento:

class Departamento {
        private $_nombre;
        private $_empleados;

        function __construct($nombre){
            $this->_nombre = $nombre;
            $this->_empleados = array();
        }

        function agregarEmpleado(Empleado $e) {
            $this->_empleados[] = $e;
            echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
        }
    } // Fin de la clase Departamento



Este es el mismo codigo que ya se explico anteriormente en type hinting.

3.- Declaramos la clase Empleado:

class Empleado {
        private $_nombre;

        function __construct($nombre) {
            $this->_nombre = $nombre;
        }

        function obtenerNombre(){
            return $this->_nombre;
        }
    } // Fin de la clase Empleado.


De nuevo, nada cambia aqui.

4.- Guardamos el archivo, y luego lo colocamos en el directorio web dentro de MiNamespace\Compania.

Usar la clase namespace:


 
 <!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <title>Namespace</title>
</head>
<body>
<?php
 // Esta pagina define el uso de las clases Departamento y Empleado.

 // Incluimos los script PHP:
 require('compania/company.php');

 // Creamos un departamento:
 $rh = new MiNamespace\Compania\Departamento('Contabilidad');

 // Creamos empleados:
 $e1 = new MiNamespace\Compania\Empleado('Holden Caulfield');
 $e2 = new MiNamespace\Compania\Empleado('Jane Gallagher');

 // Agregamos los empleados al departamento:
 $rh->agregarEmpleado($e1);
 $rh->agregarEmpleado($e2);

 // Eliminar los objetos:
 unset($rh, $e1, $e2);
 
?>
</body>
</html>


1.- Incluimos el archivo de namespace:

require('compania/company.php');


2.- Creamos el objeto Departamento:


$rh = new MiNamespace\Compania\Departamento('Contabilidad');


Este es el mismo codigo usado anteriormente, solo que en este caso estamos utilizando namespace.


3.- Creamos dos empleados:

$e1 = new MiNamespace\Compania\Empleado('Holden Caulfield');
$e2 = new MiNamespace\Compania\Empleado('Jane Gallagher');


4.- Agregamos los empleados al departamento:


$rh->agregarEmpleado($e1);
$rh->agregarEmpleado($e2);

5.- Completamos la pagina:


unset($rh, $e1, $e2);



La salida es mas o menos la misma que el script anterior, aunque el codigo subyacente ahora esta mejor organizado gracias a namespaces.


La constante __NAMESPACE__ representa el namespace actual.

Fin de esta parte de POO en PHP.

No hay comentarios:

Publicar un comentario en la entrada