Métodos mágicos são métodos especiais que sobrescrever o comportamento padrão do PHP
quando certas operações são realizadas em um objeto.
Cuidado
Todos os métodos prefixados com __ são reservados pelo PHP.
Portanto, não é recomendado utilizar nomes de métodos com esse prefixo a não ser para
sobrescrever o comportamento do PHP.
Se tipos forem utilizados na declaração de métodos mágicos, eles
precisam ser idênticos às assinaturas previstas aqui.
Senão, um erro fatal é lançado.
Anteriormente ao PHP 8.0.0, nenhum diagnóstico era emitido.
Entretanto, __construct() e
__destruct() não devem declarar um tipo de retorno,
senão um erro fatal é lançado.
serialize() checa se sua classe tem uma função com
o nome mágico __sleep(). Se houver, a função é
executada antes de qualquer serialização. Ela pode limpar o objeto
e deve retornar um array com os nomes de todas as variáveis
do objeto que devem ser serializadas. Se o método não retornar nada,
então null é serializado e um
E_NOTICE disparado.
Nota:
Não é possível que __sleep() retorne nomes de
propriedades privadas da classe pai. Fazer isso causará um erro de nível
E_NOTICE.
Como alternativa, utilize __serialize().
Nota:
Desde o PHP 8.0.0, retornar um valor que não seja um array de __sleep() gera um warning. Anteriormente gerava um aviso.
O intuito do método __sleep() é enviar dados
pendentes ou realizar tarefas de limpeza. Além disso, a função é
útil se tiver objetos muito grandes que não precisem ser completamente salvos.
Ao mesmo tempo, unserialize() checa pela
presença da função com o nome mágico
__wakeup(). Se presente, essa função pode
reconstruir qualquer recurso que o objeto possa ter.
O intuito do método __wakeup() é
reestabelecer qualquer conexão com banco de dados que podem ter sido perdidas
durante a serialização, e realizar outras tarefas de
reinicialização.
Exemplo #1 Sleep e wakeup
<?php class Connection { protected $link; private $dsn, $username, $password;
public function __construct($dsn, $username, $password) { $this->dsn = $dsn; $this->username = $username; $this->password = $password; $this->connect(); }
private function connect() { $this->link = new PDO($this->dsn, $this->username, $this->password); }
public function __sleep() { return array('dsn', 'username', 'password'); }
public function __wakeup() { $this->connect(); } }?>
serialize() verifica se a classe contém uma função
com o nome mágico __serialize(). Se sim, essa função é
executada antes de qualquer serialização. Ela precisa construir e retornar um array associativo de chaves-valores
que representam a forma serializada do objeto. Se o array não for retornado então um erro TypeError
será lançado.
Nota:
Se ambos __serialize() e __sleep()
estiverem definidos no mesmo objeto, somente __serialize() será chamado.
__sleep() será ignorado. Se o objeto implementa a interface Serializable,
o método serialize() da interface será ignorado e o método mágico __serialize()
será utilizado.
O uso pretendido de __serialize() é definir uma representação arbitrária,
amigável, da representação do objeto. Elementos do array podem corresponder a propriedades do objeto diretamente, mas
isso não é obrigatório.
Inversamente, unserialize() verifica a
presença da função mágica
__unserialize(). Se presente, essa função será chamada
com o array retornado de __serialize(). Ela poderá,
então, restaurar as propriedades do objeto a partir do array.
O método __toString() permite que uma classe decida
como se comportar quando convertida para uma string. Por exemplo,
o que echo $obj; irá imprimir.
Aviso
A partir do PHP 8.0.0, o valor de retorno segue as mesma semântica de tipo do PHP,
significando que o valor será convertido para string se possível e se
strict typing
estiver desligado.
Um objeto Stringablenão é aceito para uma declaração string se
strict typing
estiver ativo. Caso esse comportamento seja desejado, a declaração precisa indicar
Stringable e string através de um tipo união.
A partir do PHP 8.0.0, quaisquer classe que contenha o método __toString()
também implementa implicitamente a interface Stringable, e portanto passa
os testes para essa interface. Implementar explicitamente essa interface é o
recomendado.
No PHP 7.4, o valor retornado precisa ser uma
string, senão um erro Error é lançado.
Anteriormente ao PHP 7.4.0, o valor retornado precisa ser uma
string, senão um erro fatal E_RECOVERABLE_ERROR
é emitido.
Aviso
Não era possível lançar uma exception de dentro de um método
__toString()
antes do PHP 7.4.0. Isso gera um erro fatal.
Exemplo #3 Exemplo Simples
<?php // Declara uma classe simples class TestClass { public $foo;
public function __construct($foo) { $this->foo = $foo; }
public function __toString() { return $this->foo; } }
Nota:
Quando exportando um objeto, var_export() não verifica
se __set_state() está
implementado na classe do objeto, de forma que re-importar esses objetos falham com um erro Error
na ausência de __set_state(). Isto afeta particularmente algumas
classes internas.
É responsabilidade do programador verificar se todos os objetos podem
ser re-importados, ou seja, que todas as classes implementem __set_state().
Este método é chamado pela função var_dump() ao despejar um
objeto para obter as propriedades que devem ser exibidas. Se este método não for
definido em um objeto, todos as propriedades públicas, protegidas e provadas
serão exibidas.
The __toString() method is extremely useful for converting class attribute names and values into common string representations of data (of which there are many choices). I mention this as previous references to __toString() refer only to debugging uses.
I have previously used the __toString() method in the following ways:
- representing a data-holding object as: - XML - raw POST data - a GET query string - header name:value pairs
- representing a custom mail object as an actual email (headers then body, all correctly represented)
When creating a class, consider what possible standard string representations are available and, of those, which would be the most relevant with respect to the purpose of the class.
Being able to represent data-holding objects in standardised string forms makes it much easier for your internal representations of data to be shared in an interoperable way with other applications.
Please note that as of PHP 8.2 implementing __serialize() has no control over the output of json_encode(). you still have to implement JsonSerializable.
Be very careful to define __set_state() in classes which inherit from a parent using it, as the static __set_state() call will be called for any children. If you are not careful, you will end up with an object of the wrong type. Here is an example:
<?php
class A
{
public $var1;
public static function __set_state($an_array)
{
$obj = new A;
$obj->var1 = $an_array['var1'];
return $obj;
}
}
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance.
<? class BaseObject { function __sleep() { $vars = (array)$this; foreach ($vars as $key => $val) { if (is_null($val)) { unset($vars[$key]); } } return array_keys($vars); } }; ?>
providing a object as a array index doesn't try to us __toString() method so some volatile object identifier is used to index the array, which is breaking any persistency. Type hinting solves that, but while other than "string" type hinting doesn't work on ob jects, the automatic conversion to string should be very intuitive.
PS: tried to submit bug, but withot patch the bugs are ignored, unfortunately, I don't C coding
<?php
class shop_product_id {
protected $shop_name; protected $product_id;
function __construct($shop_name,$product_id){ $this->shop_name = $shop_name; $this->product_id = $product_id; }
function __toString(){ return $this->shop_name . ':' . $this->product_id; } }
Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection).
One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'. Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous.
Consider the following...
<?php class SomeStupidStorageClass { public function getContents($pos, $len) { ...stuff... } }
class CryptedStorageClass extends SomeStupidStorageClass { private $decrypted_block; public function getContents($pos, $len) { ...decrypt... } } ?>
If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored. Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to.
Considering encapsulation again, no class should have to know how the parent handles its own private data. And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience.
If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable. Like so....
<?php
class BetterClass { private $content;
public function __sleep() { return array('basedata1', 'basedata2'); }
public function getContents() { ...stuff... } }
class BetterDerivedClass extends BetterClass { private $decrypted_block;
public function __sleep() { return parent::__sleep(); }
public function getContents() { ...decrypt... } }
?>
The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be.
If you use the Magical Method '__set()', be shure that the call of <?php $myobject->test['myarray'] = 'data'; ?> will not appear!
For that u have to do it the fine way if you want to use __set Method ;) <?php $myobject->test = array('myarray' => 'data'); ?>
If a Variable is already set, the __set Magic Method already wont appear!
My first solution was to use a Caller Class. With that, i ever knew which Module i currently use! But who needs it... :] There are quiet better solutions for this... Here's the Code:
<?php class Caller { public $caller; public $module;
function __call($funcname, $args = array()) { $this->setModuleInformation();
if (is_object($this->caller) && function_exists('call_user_func_array')) $return = call_user_func_array(array(&$this->caller, $funcname), $args); else trigger_error("Call to Function with call_user_func_array failed", E_USER_ERROR);
function __construct($callerClassName = false, $callerModuleName = 'Webboard') { if ($callerClassName == false) trigger_error('No Classname', E_USER_ERROR);
$this->module = $callerModuleName;
if (class_exists($callerClassName)) $this->caller = new $callerClassName(); else trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR);
if (is_object($this->caller)) { $this->setModuleInformation(); if (method_exists($this->caller, '__init')) $this->caller->__init(); $this->unsetModuleInformation(); } else trigger_error('Caller is no object!', E_USER_ERROR); }
function __destruct() { $this->setModuleInformation(); if (method_exists($this->caller, '__deinit')) $this->caller->__deinit(); $this->unsetModuleInformation(); }
function __isset($isset) { $this->setModuleInformation(); if (is_object($this->caller)) $return = isset($this->caller->{$isset}); else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; }
function __unset($unset) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$unset})) unset($this->caller->{$unset}); } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); }
function __set($set, $val) { $this->setModuleInformation(); if (is_object($this->caller)) $this->caller->{$set} = $val; else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); }
function __get($get) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$get})) $return = $this->caller->{$get}; else $return = false; } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; }
function setModuleInformation() { $this->caller->module = $this->module; }
function unsetModuleInformation() { $this->caller->module = NULL; } }
// Well this can be a Config Class? class Config { public $module;
public $test;
function __construct() { print('Constructor will have no Module Information... Use __init() instead!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); $this->test = '123'; }
function __init() { print('Using of __init()!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); }
function testFunction($test = false) { if ($test != false) $this->test = $test; } }