métodos mágicos parte 1

16
Métodos Mágicos Los métodos mágicos mágicos los provee PHP y nos permiten realizar ciertas tareas orientadas a objetos. Los métodos mágicos identifican por el uso de dos guiones bajos “__” como prefijo, y funcionan como interceptores que se llaman automáticamente cuando ocurre una condición. __clone() En PHP 4 se podía copiar un objeto simplemente asignándolo de una variable a otra. Y de esta forma se podía modificar la copia sin que ello afectara al original. $objecto1->nombre = "nombre 1"; $objecto2 = $objecto1; $objecto2->nombre = "nombre 2"; echo $objecto1->nombre; // nombre 1" Pero en PHP 5 los objetos son asignados por referencia. Lo que significa que si en dos variables asignamos el mismo objeto lo que obtendremos será dos referencias al mismo objeto. Por lo que necesitamos algo más si queremos realizar una copia exacta pero no enlazada de un objeto. Y para ello existe la palabra reservada clone. $objecto1->nombre = "nombre 1"; $objecto2 = clone $objecto1; $objecto2->nombre = "nombre 2"; echo $objecto1->nombre; // nombre 1" Y una vez el clonado se haya realizado, se llamará automáticamente al método __clone() del nuevo objeto creado. Siempre y cuando __clone() este definido. Recuerda que los métodos mágicos son invocados automáticamente por PHP si estos están definidos. Y de esta forma, dentro de __clone() se pueden realizar, por ejemplo, cambios sobre los atributos del nuevo objeto creado. Por si queremos alguna variación respecto el objeto clonado. class Objeto { private $id; private $nombre; private $email; function __construct($id,$nombre, $email) { $this->id = $id;

Upload: jose-idelso-cruz-bueno

Post on 13-Jul-2016

216 views

Category:

Documents


0 download

DESCRIPTION

RERERER

TRANSCRIPT

Page 1: Métodos Mágicos Parte 1

Métodos Mágicos Los métodos mágicos mágicos los provee PHP y nos permiten realizar ciertas tareas orientadas a objetos.

Los métodos mágicos identifican por el uso de dos guiones bajos “__” como prefijo, y funcionan como interceptores que se llaman automáticamente cuando ocurre una condición.

__clone()

En PHP 4 se podía copiar un objeto simplemente asignándolo de una variable a otra. Y de esta forma se podía modificar la copia sin que ello afectara al original. 

$objecto1->nombre = "nombre 1"; $objecto2 = $objecto1; $objecto2->nombre = "nombre 2"; echo $objecto1->nombre; // nombre 1"

Pero en PHP 5 los objetos son asignados por referencia. Lo que significa que si en dos variables asignamos el mismo objeto lo que obtendremos será dos referencias al mismo objeto. Por lo que necesitamos algo más si queremos realizar una copia exacta pero no enlazada de un objeto. Y para ello existe la palabra reservada clone.

$objecto1->nombre = "nombre 1"; $objecto2 = clone $objecto1; $objecto2->nombre = "nombre 2"; echo $objecto1->nombre; // nombre 1"

Y una vez el clonado se haya realizado, se llamará automáticamente al método __clone() del nuevo objeto creado. Siempre y cuando __clone() este definido. Recuerda que los métodos mágicos son invocados automáticamente por PHP si estos están definidos. Y de esta forma, dentro de __clone() se pueden realizar, por ejemplo, cambios sobre los atributos del nuevo objeto creado. Por si queremos alguna variación respecto el objeto clonado.

class Objeto { private $id; private $nombre; private $email; function __construct($id,$nombre, $email) { $this->id = $id; $this->nombre = $nombre; $this->email = $email; } function __clone() { $this->id = ++$this->id; } public function getId() { return $this->id; } public function setId($id) {

Page 2: Métodos Mágicos Parte 1

$this->id = $id; } public function getNombre() { return $this->nombre; } public function setNombre($nombre) { $this->nombre = $nombre; } public function getEmail() { return $this->email; } public function setEmail($email) { $this->email = $email; } } $obj = new Objeto(1, "objeto1", "[email protected]"); $p = clone $obj; echo $p->getId(); //2

Como se puede ver en el ejemplo, cuando clonemos un objeto incrementamos el identificador en 1.

__set y __get

Una vez declarados estos métodos, si se intenta acceder a un atributo como si este fuera público, PHP llamara automáticamente a __get(). Y si asignamos un valor a un atributo, llamará a __set().

Evidentemente, __set() necesita dos parámetros de entrada: nombre del atributo y el valor a asignar. Y __get() solo necesita el nombre del atributo a obtener su valor.

class Objeto { private $id; private $nombre; private $email; function __construct($id, $nombre, $email) { $this->id = $id; $this->nombre = $nombre; $this->email = $email; } function __clone() { $this->id = ++$this->id; } public function __set($var, $valor) { if (property_exists(__CLASS__, $var)) { $this->$var = $valor; } else {

Page 3: Métodos Mágicos Parte 1

echo "No existe el atributo $var."; } } public function __get($var) { if (property_exists(__CLASS__, $var)) { return $this->$var; } return NULL; } } $obj = new Objeto(1, "objeto1", "[email protected]"); $p = clone $obj; echo $p->id;//2 $p->nombre = "nombre cambiado"; echo $p->nombre;//nombre cambiado

Hay que tener en cuenta que estos método solo se llamarán desde fuera del objeto. Dentro utilizaremos la referencia al objeto $this. 

__toString

Este método es quizás uno de los métodos mágicos menos importantes. Permite asignar una cadena (string) al objeto que será mostrada si el objeto es usado como una cadena. Osea el valor que mostrará si se intenta hacer echo $objeto.

class Objeto { private $id; private $nombre; private $email; //... public function __toString(){ return "esto es una prueba"; } } $obj = new Objeto(1, "objeto1", "[email protected]"); $p = clone $obj; echo $p; //esto es una prueba

Si no se hubiera definido __toString, al intentar imprimir $p daría un error indicando que no se puede convertir el objeto a un string. Además dentro de este método no se pueden utilizar excepciones ya que también ocasionarían un error.

En PHP 5.2 y versiones superiores, el método __toString será invocado automáticamente cuando se utilice las funciones de impresión echo(), print() y printf(). Esta última únicamente cuando se utiliza el modificador %s.

Page 4: Métodos Mágicos Parte 1

_call

Este método mágico es llamado automáticamente cuando se intenta llamar a un método que no esta definido en la clase o es inaccesible dentro del objeto (por ejemplo un método privado).Este método recibe dos parámetros, uno es el nombre del método al que se intenta invocar, y el otro los parámetros que le hemos pasado al método al que hemos intentado llamar.

class Objeto { private $id; private $nombre; private $email; //... public function __toString(){ return "esto es una prueba"; } public function __call($metodo, $args) { $args = implode(', ', $args); echo "fallo al llamar al método $metodo() con los argumentos $args"; } } $obj = new Objeto(1, "objeto1", "[email protected]"); $p = clone $obj; echo $p; $p->noExiste();

Visto este ejemplo nos puede parecer que este método solo sirve para mostrar un mensaje evitando el error que se debería mostrar por intentar acceder a algo que no existe. Pero también se puede utilizar para crear 'setter' y 'getters' dinámicos sin tener que utilizar los métodos mágicos __set() y __get(). Ya que es posible que la utilización de estos últimos no nos guste (los atributos se acceden como si fueran públicos para que se invoquen los métodos __get() y __set()).

Vamos a ver un ejemplo.

class Objeto { private $id; private $nombre; private $email; function __construct($id, $nombre, $email) { $this->id = $id; $this->nombre = $nombre; $this->email = $email; } function __clone() { $this->id = ++$this->id;

Page 5: Métodos Mágicos Parte 1

} public function __toString() { return "esto es una prueba"; } public function __call($metodo, $params = null) { // todo en minúsculas para evitar problemas. Por ejemplo setNombre $metodo = strtolower($metodo); $prefijo = substr($metodo, 0, 3); $atributo = substr($metodo, 3); if ($prefijo == 'set' && count($params) == 1) { if (property_exists(__CLASS__, $atributo)) { $valor = $params[0]; $this->$atributo = $valor; } else { echo "No existe el atributo $atributo."; } } elseif ($prefijo == 'get') { if (property_exists(__CLASS__, $atributo)) { return $this->$atributo; } return NULL; } else { echo 'Método no definido <br/>'; } } } $obj = new Objeto(1, "objeto1", "[email protected]"); $p = clone $obj; $p->noExiste(); //Método no definido $p->setId(2); echo $p->getId() . "<br/>"; //2

Vamos a ampliar un poco el ejemplo haciendo uso de la herencia. Esta vez se utiliza la funciónget_class() que nos devuelve el nombre de la clase del objeto que se le pase como parámetro.

string get_class ([ object $object = NULL ] )

Estando dentro de la clase, si se omite el parámetro, devolverá el nombre de la clase. Pero si dicha clase hereda de otra, tendremos que usar $this como parámetro para obtener el nombre de la clase actual y no el de la padre.

Page 6: Métodos Mágicos Parte 1

class Objeto { //... public function __call($metodo, $params = null) { // todo en minúsculas para evitar problemas. P.E. setNombre $metodo = strtolower($metodo); $prefijo = substr($metodo, 0, 3); $atributo = substr($metodo, 3); $nombreClase = get_class($this); if ($prefijo == 'set' && count($params) == 1) { if (property_exists($nombreClase, $atributo)) { $valor = $params[0]; $this->$atributo = $valor; } else { echo "No existe el atributo $atributo."; } } elseif ($prefijo == 'get') { if (property_exists($nombreClase, $atributo)) { return $this->$atributo; } return NULL; } else { echo 'Método no definido <br/>'; } } } class ClaseHijo extends Objeto{ protected $apellido = "apellido"; } $obj = new ClaseHijo(1, "objeto1", "[email protected]"); $p = clone $obj; $p->noExiste();//Método no definido $p->setId(2); echo $p->getId() . "<br/>";//2 echo $p->getApellido();//apellido

__callStatic

El método anterior __call era lanzado al intentar llamar a un método inaccesible en el contexto del objeto. Pero no está pensado si el método es inaccesible en un contexto estático. Osea que si llamamos a un método estático que no existe, no se llamará automáticamente a __call aunque esté definido (__call).En PHP 5.3 aparece un nuevo método mágico llamado __callStatic que soluciona este problema.

Page 7: Métodos Mágicos Parte 1

class Objeto { protected $id; protected $nombre; protected $email; function __construct($id, $nombre, $email) { $this->id = $id; $this->nombre = $nombre; $this->email = $email; } function __clone() { $this->id = ++$this->id; } function who() { return __CLASS__; } public function __toString() { return "esto es una prueba"; } public static function __callStatic($metodo, $args) { echo "Método $metodo ha sido llamado!!!<br/>"; echo "Con los siguientes argumentos: " . implode(", ", $args) . "<br/>"; } public function __call($metodo, $params = null) { // todo en minúsculas para evitar problemas. P.E. setNombre $metodo = strtolower($metodo); $prefijo = substr($metodo, 0, 3); $atributo = substr($metodo, 3); $nombreClase = get_class($this); if ($prefijo == 'set' && count($params) == 1) { if (property_exists($nombreClase, $atributo)) { $valor = $params[0]; $this->$atributo = $valor; } else { echo "No existe el atributo $atributo."; } } elseif ($prefijo == 'get') { if (property_exists($nombreClase, $atributo)) { return $this->$atributo; } return NULL; } else { echo 'Método no definido <br/>'; } } }

Page 8: Métodos Mágicos Parte 1

class ClaseHijo extends Objeto { protected $apellido = "apellido"; } $obj = new ClaseHijo(1, "objeto1", "[email protected]"); $p = clone $obj; $p->noExiste(); $p->setId(2); echo $p->getId() . "<br/>"; //2 echo $p->getApellido(); //apellido Objeto::algunMetodo('con', 'varios', 'argumentos');

__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", "[email protected]"); 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().

Page 9: Métodos Mágicos Parte 1

__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", "[email protected]",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);

Page 10: Métodos Mágicos Parte 1

El resultado de la anterior ejecución es este:

Estado del usuario: Usuario Object ( [nombreUsuario] => Pedro [email] => [email protected] [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:"[email protected]";} Usuario deseralizado: Usuario Object ( [nombreUsuario] => Pedro [email] => [email protected] [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(); } }

Page 11: Métodos Mágicos Parte 1

__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.

Page 12: Métodos Mágicos Parte 1

$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 devar_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.

Page 13: Métodos Mágicos Parte 1

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:

Page 14: Métodos Mágicos Parte 1

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ónvar_export. Y por lo tanto, mediante eval() se ha creado un nuevo objeto $p2.

Constructores

Cuando se instancia un objeto, generalmente es deseable dejar todas las propiedades del mismo en un estado consistente. Para manejar esto, PHP ofrece un método llamado __construct() (doble guión bajo), que se llama automáticamente cuando se crea un nuevo objeto. Este método forma parte de los denominados "métodos mágicos" que PHP posee para hacer más simple el uso de los objetos.Para continuar con el ejemplo anterior, supongamos que queremos establecer la propiedad $propietario en "Sin dueño" al momento de crear un objeto.

class Auto

{ private $ruedas; private $propietario; public function __construct() { $this->propietario = "Sin dueño"; $this->ruedas = 4; } public function getRuedas() { return $this->ruedas; }public function getPropietario(){ return $this->propietario; } public function setPropietario($nombre) { if (is_string($nombre)) { $this->propietario = $nombre; } }}$miAuto = new Auto;echo "Propietario: " . $miAuto->getPropietario() . "<br />";

De esta forma, podemos acceder a la propiedad sin indicar un propietario para ella, estamos seguros que va a poseer un valor que tenga sentido