Sunteți pe pagina 1din 15

Mtodos Mgicos

Los mtodos mgicos mgicos los provee PHP y nos permiten realizar
ciertas tareas orientadas a objetos.
Los mtodos mgicos identifican por el uso de dos guiones bajos __ como
prefijo, y funcionan como interceptores que se llaman automticamente
cuando ocurre una condicin.
__clone()
En PHP 4 se poda copiar un objeto simplemente asignndolo de una variable a otra. Y de esta
forma se poda 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 ms 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 automticamente al


mtodo __clone() del nuevo objeto creado. Siempre y cuando __clone() este
definido. Recuerda que los mtodos mgicos son invocados
automticamente por PHP si estos estn 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 variacin 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) {
$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", "prueba1@ejemplo.com");
$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 mtodos, si se intenta acceder a un atributo como
si este fuera pblico, PHP llamara automticamente a __get(). Y si
asignamos un valor a un atributo, llamar a __set().
Evidentemente, __set() necesita dos parmetros 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 {
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", "prueba1@ejemplo.com");
$p = clone $obj;
echo $p->id;//2
$p->nombre = "nombre cambiado";
echo $p->nombre;//nombre cambiado

Hay que tener en cuenta que estos mtodo solo se llamarn desde fuera del
objeto. Dentro utilizaremos la referencia al objeto $this.
__toString
Este mtodo es quizs uno de los mtodos mgicos 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", "prueba1@ejemplo.com");
$p = clone $obj;
echo $p; //esto es una prueba

Si no se hubiera definido __toString, al intentar imprimir $p dara un error


indicando que no se puede convertir el objeto a un string. Adems dentro de
este mtodo no se pueden utilizar excepciones ya que tambin ocasionaran

un error.
En PHP 5.2 y versiones superiores, el mtodo __toString ser invocado
automticamente cuando se utilice las funciones de
impresin echo(), print() y printf(). Esta ltima nicamente cuando se utiliza
el modificador %s.
_call
Este mtodo mgico es llamado automticamente cuando se intenta llamar
a un mtodo que no esta definido en la clase o es inaccesible dentro del
objeto (por ejemplo un mtodo privado).
Este mtodo recibe dos parmetros, uno es el nombre del mtodo al que se
intenta invocar, y el otro los parmetros que le hemos pasado al mtodo 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 mtodo $metodo() con los argumentos
$args";
}
}
$obj = new Objeto(1, "objeto1", "prueba1@ejemplo.com");
$p = clone $obj;
echo $p;
$p->noExiste();

Visto este ejemplo nos puede parecer que este mtodo solo sirve para
mostrar un mensaje evitando el error que se debera mostrar por intentar
acceder a algo que no existe. Pero tambin se puede utilizar para crear
'setter' y 'getters' dinmicos sin tener que utilizar los mtodos mgicos
__set() y __get(). Ya que es posible que la utilizacin de estos ltimos no nos
guste (los atributos se acceden como si fueran pblicos para que se
invoquen los mtodos __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;
}
public function __toString() {
return "esto es una prueba";
}
public function __call($metodo, $params = null) {
// todo en minsculas 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 'Mtodo no definido <br/>';
}
}
}
$obj = new Objeto(1, "objeto1", "prueba1@ejemplo.com");
$p = clone $obj;
$p->noExiste(); //Mtodo 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 funcinget_class() que nos devuelve el nombre de la clase del
objeto que se le pase como parmetro.
string get_class ([ object $object = NULL ] )

Estando dentro de la clase, si se omite el parmetro, devolver el nombre


de la clase. Pero si dicha clase hereda de otra, tendremos que usar $this
como parmetro para obtener el nombre de la clase actual y no el de la
padre.

class Objeto {
//...
public function __call($metodo, $params = null) {
// todo en minsculas 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 'Mtodo no definido <br/>';
}
}
}
class ClaseHijo extends Objeto{
protected $apellido = "apellido";
}
$obj = new ClaseHijo(1, "objeto1", "prueba1@ejemplo.com");
$p = clone $obj;
$p->noExiste();//Mtodo no definido
$p->setId(2);
echo $p->getId() . "<br/>";//2
echo $p->getApellido();//apellido
__callStatic
El mtodo anterior __call era lanzado al intentar llamar a un mtodo inaccesible en el contexto
del objeto. Pero no est pensado si el mtodo es inaccesible en un contexto esttico. Osea que

si llamamos a un mtodo esttico que no existe, no se llamar automticamente a __call


aunque est definido (__call).
En PHP 5.3 aparece un nuevo mtodo mgico llamado __callStatic que soluciona este
problema.

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 "Mtodo $metodo ha sido llamado!!!<br/>";
echo "Con los siguientes argumentos: " . implode(", ", $args) .
"<br/>";
}
public function __call($metodo, $params = null) {
// todo en minsculas 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 'Mtodo no definido <br/>';
}
}
}
class ClaseHijo extends Objeto {
protected $apellido = "apellido";
}
$obj = new ClaseHijo(1, "objeto1", "prueba1@ejemplo.com");
$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 estn definidos estos mtodo mgicos son llamados automticamente 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 comprobacin se realice sobre atributos de un objeto tendremos que
definir los mtodo mgicos.
El mtodo mgico __isset() recibe un argumento nombre del atributo a comprobar si existe o
no.
El mtodo mgico __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 funcin empty() (comprobar si una variable es


considerada vaca) sobre un atributo inaccesible tambin se invoca
automticamente a __isset().

__sleep y __wakeUp
Los objetos pueden ser representados en cadenas para poder ser
almacenados. Esto se hace mediante la funcin serialize(). Sin embargo la
cadena resultante en el proceso de serializacin muestra todos los atributos
de objeto, independientemente de que sean publico o privados. Y esto no es
conveniente sobre todo si queremos compartir dicha serializacin.
PHP 5 introdujo dos mtodos mgicos que se invocarn automticamente
durante el proceso de serializacin y de esta forma podemos, por ejemplo,
elegir los atributos que queremos que se muestren en la representacin del
objeto (serializacin) . Para ello, cuando las funciones serialize() y
unserialize() sean llamadas, PHP buscar si los mtodos mgicos __sleep y
__wakeUp(), respectivamente, estn 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 ejecucin 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 mtodo __wakeUp() no se ha utilizado ya que en este ejemplo no tiene


utilidad. Donde si tendra utilidad sera en una clase que al serializar se
desconecta de una base de datos y al deserializar tiene que volver a
conectarse. O tambin 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 conexin: ' . $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 mtod mgico __invoke que es llamado
automticamente por PHP cuando se intenta llamar a un objeto como si se
tratase de una funcin. Por lo tanto sirve para controlar el comportamiento
de nuestro objeto en dicho caso. Si no definimos el mtodo __invoke y
usamos un mtodo como si se tratase de una funcin 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 funcin annima 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 annimas similares una y otra vez. Evidentemente
es ms corto definir una funcin annima que tener que definir una clase
con el mtodo __invoke(). Sin embargo definiendo la clase tenemos la
ventaja de la programacin orientada a objetos.
Vamos a ver en el siguiente ejemplo como es equivalente la definicin de
una funcin annima o 'closure' y una clase con el mtodo __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 funcin annima. Esta vez importando variables externas y


encapsulacin gracias a la programacin 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 mtodo mgico nacido en PHP 5.1 es llamado automticamente
cuando una instancia de una clase es pasada a la funcin var_export. Esta
funcin devuelve informacin 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 cdigo vlido 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 mtodo__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 mtodo mgico tiene que ser esttico.

Pero entonces, si var_export solo lo usamos para mostrar informacin, para


que queremos definir__set_state()? Como hemos dicho antes, la virtud
de var_export es que muestra cdigo ejecutable por PHP. Imagnate que
queremos compartir la informacin 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 mtodo de
creacin. Una especie de constructor copia.
Evidentemente si ejecutamos eval() sobre var_export($p), en el ejemplo
anterior, dar error. Ya que an no hemos definido el mtodo mgico
esttico. 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'
objeto p2 creado con eval:
object(Prueba)#5 (2) { ["nombre"]=>
int(30) }
objeto p:
object(Prueba)#4 (2) { ["nombre"]=>
int(30) }
objeto p2 modificado:
object(Prueba)#5 (2) { ["nombre"]=>
["edad"]=> int(30) }

=> 'Jose', 'edad' => 30, ))


string(4) "Jose" ["edad"]=>

string(4) "Jose" ["edad"]=>

string(16) "Jose- modificado"

Como se puede apreciar, __set_state() se ha convertido en un constructor al


ejecuarse la funcinvar_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 mtodo llamado __construct() (doble guin bajo), que se llama
automticamente cuando se crea un nuevo objeto. Este mtodo forma parte
de los denominados "mtodos mgicos" que PHP posee para hacer ms
simple el uso de los objetos.
Para continuar con el ejemplo anterior, supongamos que queremos
establecer la propiedad $propietario en "Sin dueo" al momento de crear un
objeto.

class Auto
{
private $ruedas;
private $propietario;
public function __construct()
{
$this->propietario = "Sin dueo";
$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

S-ar putea să vă placă și