sábado, 17 de agosto de 2013

PHP orientado a objetos - Métodos mágicos - Parte 2

En la primera parte del tutorial presentamos los método mágicos __clone(), __set, __get, __toString , __call y __callStatic. Pero aún faltan varios método que vamos a ver a continuación.

__isset y __unset

Si están definidos estos método mágicos son llamados automáticamente cuando se utilizan las funciones isset() y unset() sobre un atributo no definido o inaccesible en el objeto. Recordemos que isset() sirve para comprobar la existencia de una variable y unset() sirve para destruir una variable. Pero para que la comprobación se realice sobre atributos de un objeto tendremos que definir los método mágicos.

El método mágico __isset() recibe un argumento – nombre del atributo a comprobar si existe o no.
El método mágico __unset() recibe un argumento – nombre del atributo que queremos  destruir.

 class Objeto {  
   protected $id;  
   protected $nombre;  
   protected $email;  
   function __construct($id, $nombre, $email) {  
     $this->id = $id;  
     $this->nombre = $nombre;  
     $this->email = $email;  
   }  
   function __isset($atributo) {  
     return isset($this->$atributo);  
   }  
   function __unset($atributo) {  
     if(isset($this->$atributo))  
       unset($this->$atributo);  
   }  
 }  
 $obj2 = new Objeto(1, "objeto1", "prueba1@ejemplo.com");  
 echo isset($obj2->nombre);  

Nota: si se utiliza la función empty() (comprobar si una variable es considerada vacía) sobre un atributo inaccesible también se invoca automáticamente a __isset().

__sleep y __wakeUp

Los objetos pueden ser representados en cadenas para poder ser almacenados. Esto se hace mediante la función serialize(). Sin embargo la cadena resultante en el proceso de serialización muestra todos los atributos de objeto, independientemente de que sean publico o privados. Y esto no es conveniente sobre todo si queremos compartir dicha serialización.
PHP 5 introdujo dos métodos mágicos que se invocarán automáticamente durante el proceso de serialización y de esta forma podemos, por ejemplo, elegir los atributos que queremos que se muestren en la representación del objeto (serialización) . Para ello, cuando las funciones serialize() y unserialize() sean llamadas, PHP buscará si los métodos mágicos __sleep y __wakeUp(), respectivamente, están definidos.

En el siguiente ejemplo se va a ver como mediante __sleep() se elije lo que queremos que se serialize.

 class Usuario {  
   public $nombreUsuario;  
   public $email;  
   private $contrasenya;  
   private $dinero;  
   public function Usuario($nombre, $pwd, $email,$dinero) {  
     $this->nombreUsuario = $nombre;  
     $this->contrasenya = $pwd;  
     $this->email = $email;  
     $this->dinero = $dinero;  
   }  
   public function ingresar($saldo) {  
     $this->dinero += $saldo;  
   }  
   public function __sleep() {  
     return array("nombreUsuario", "email");  
   }  
 }  
 $usuario = new Usuario("Pedro", "1234", "prueba@ejemplo.com",100);  
 $usuario->ingresar(50);  
 echo "Estado del usuario: <br/>";  
 print_r($usuario);  
 echo "<br/>";  
 $usuarioSerialize = serialize($usuario);  
 echo "El usuario serializado: <br/>";  
 echo $usuarioSerialize;  
 echo "<br/>";  
 echo "Usuario deseralizado: <br/>";  
 $obj = unserialize($usuarioSerialize);  
 print_r($obj);  

El resultado de la anterior ejecución es este:

 Estado del usuario:   
 Usuario Object ( [nombreUsuario] => Pedro [email] => prueba@ejemplo.com [contrasenya:Usuario:private] => 1234 [dinero:Usuario:private] => 150 )   
 El usuario serializado:   
 O:7:"Usuario":2:{s:13:"nombreUsuario";s:5:"Pedro";s:5:"email";s:18:"prueba@ejemplo.com";}  
 Usuario deseralizado:   
 Usuario Object ( [nombreUsuario] => Pedro [email] => prueba@ejemplo.com [contrasenya:Usuario:private] => [dinero:Usuario:private] => )  

El método __wakeUp() no se ha utilizado ya que en este ejemplo no tiene utilidad. Donde si tendría utilidad sería en una clase que al serializar se desconecta de una base de datos y al deserializar tiene que volver a conectarse. O también se puede utilizar en una clase que escribe logs en un fichero y al serializar guarda y cierra el manejador del fichero y al desearializar vuelve a abrir el fichero.

 class DatabaseConn {  
   protected $link;  
   private $server, $username, $password, $dbname;  
   public function __construct($server, $username, $password, $dbmame) {  
     $this->server = $server;  
     $this->username = $username;  
     $this->password = $password;  
     $this->dbname = $dbmame;  
     $this->connect();  
   }  
   private function connect() {      
     $dsn = 'mysql:dbname=' . $this->dbname . ';host=' . $this->server;  
     try {  
       $this->link = new PDO($dsn, $this->username, $this->password);  
     } catch (PDOException $e) {  
       echo 'Falló la conexión: ' . $e->getMessage();  
     }  
   }  
   public function __sleep() {  
     unset($this->link);
     return array('server', 'username', 'password', 'db');  
   }  
   public function __wakeup() {  
     $this->connect();  
   }  
 }  

__invoke

En PHP 5.3 aparece el métod mágico __invoke que es llamado automáticamente por PHP cuando se intenta llamar a un objeto como si se tratase de una función. Por lo tanto sirve para controlar el comportamiento de nuestro objeto en dicho caso. Si no definimos el método __invoke y usamos un método como si se tratase de una función obtendremos un error.

 class Animal {  
  public function __invoke() {  
   echo "Soy un animal";  
  }  
 }  
 $animal = new Animal();  
 $animal();  

Podemos conseguir que la clase se comporte como una función anónima o 'clousure'. La idea de crear clases que se comporten como las 'closures', explicadas en un tutorial anterior, es para evitar tener que estar reescribiendo funciones anónimas similares una y otra vez. Evidentemente es más corto definir una función  anónima que tener que definir una clase con el método __invoke(). Sin embargo definiendo la clase tenemos la ventaja de la programación orientada a objetos.

Vamos a ver en el siguiente ejemplo como es equivalente la definición de una función anónima o 'closure' y una clase con el método __invoke().

 $cubic = function($valor) {  
   return $valor * $valor * $valor;  
 };  
 var_dump($cubic(5));  
 class CubicCallback  
 {  
   public function __invoke($valor)  
   {  
     return $valor * $valor * $valor;  
   }  
 }  
 $cubicObject = new CubicCallback();  
 var_dump($cubicObject(5));  
 $array = array(1, 2, 3);  
 var_dump(array_map($cubic, $array));  
 var_dump(array_map($cubicObject, $array));  

Otro ejemplo de función anónima. Esta vez importando variables externas y encapsulación gracias a la programación orientada a objetos. El resultado es el mismo.

 $toMultiply = 5;  
 $multiplier = function($value) use ($toMultiply) {  
   return $value * $toMultiply;  
 };  
 var_dump($multiplier(3));  
 class MultiplierCallback   
 {  
   private $_toMultiply;  
   public function __construct($numberToMultiply)  
   {  
     $this->_toMultiply = $numberToMultiply;  
   }  
   public function __invoke($value)  
   {  
     return $value * $this->_toMultiply;  
   }  
 };  
 $multiplierObj = new MultiplierCallback(5);   
 var_dump($multiplierObj(3)) ;  

__set_state

Este método mágico nacido en PHP 5.1 es llamado automáticamente cuando una instancia de una clase es pasada a la función var_export. Esta función devuelve información de la variable pasada como argumento. su uso es similar a print_r o var_dump. Pero con la diferencia de que la salida de var_export es código válido y por lo tanto evaluable por PHP.

Vamos a ver un ejemplo de var_export()

 class Prueba{  
   public $nombre;  
   public $edad;  
   function __construct($nombre, $edad){  
     $this->edad = $edad;  
     $this->nombre = $nombre;  
   }  
 }  
 $p = new Prueba('Jose',30);  
 var_export($p);//Prueba::__set_state(array( 'nombre' => 'Jose', 'edad' => 30, ))  

Como se puede observar, var_export() nos da una pistas de lo que deberá mostrar el método __set_state(), si lo definimos:
- Deberá mostrar un array con pares clave-valor: array('atributo' => valor, ...). Podemos mostrar lo que queramos mientras cumpla la anterior sintaxis.
- El método mágico tiene que ser estático.

Pero entonces, ¿si var_export solo lo usamos para mostrar información, para que queremos definir __set_state()? Como hemos dicho antes, la virtud de var_export es que muestra código ejecutable por PHP. Imagínate que queremos compartir la información proporcionada por var_export y que luego inmediatamente el receptor la pueda ejecutar, creando un nuevo objeto. Por lo tanto podemos convertir a __set_state() en método de creación. Una especie de constructor copia.

Evidentemente si ejecutamos  eval() sobre var_export($p), en el ejemplo anterior, dará error. Ya que aún no hemos definido el método mágico estático. Por lo que vamos a ver otro ejemplo y vamos a definirlo.

 class Prueba{  
   public $nombre;  
   public $edad;  
   function __construct($nombre, $edad){  
     $this->edad = $edad;  
     $this->nombre = $nombre;  
   }  
   public static function __set_state(array $array) {  
     return new self($array['nombre'], $array['edad']);  
   }  
 }  
 $p = new Prueba('Jose',30);  
 var_export($p);//Prueba::__set_state(array( 'nombre' => 'Jose', 'edad' => 30, ))  
 eval('$p2=' . var_export($p,true). ';');  
 echo "<br/> objeto p2 creado con eval: <br/>";  
 var_dump($p2);  
 $p2->nombre.='- modificado';  
 echo "<br/> objeto p: <br/>";  
 var_dump($p);  
 echo "<br/> objeto p2 modificado: <br/>";  
 var_dump($p2);  
Nota: con new self() estamos creando una instancia de la clase Prueba.

Y la salida es la siguiente:

 Prueba::__set_state(array( 'nombre' => 'Jose', 'edad' => 30, ))  
 objeto p2 creado con eval:   
 object(Prueba)#5 (2) { ["nombre"]=> string(4) "Jose" ["edad"]=> int(30) }   
 objeto p:   
 object(Prueba)#4 (2) { ["nombre"]=> string(4) "Jose" ["edad"]=> int(30) }   
 objeto p2 modificado:   
 object(Prueba)#5 (2) { ["nombre"]=> string(16) "Jose- modificado" ["edad"]=> int(30) }  

Como se puede apreciar, __set_state() se ha convertido en un constructor al ejecuarse la función var_export. Y por lo tanto, mediante eval() se ha creado un nuevo objeto $p2.


Entradas relacionadas

PHP orientado a objetos - Métodos mágicos - Parte 1
Funciones anónimas en PHP (closures)

No hay comentarios:

Publicar un comentario