Monkey patching v PHP
PHP přistupuje ke třídám způsobem známým ze staticky typovaných jazyků a neumožňuje monkey patching, tedy měnit za běhu metody tříd, kopírovat je mezi instancemi a podobně.
Abyste porozuměli, co mám na mysli, vytvořme třídu Greeting
s metodou say()
:
class Greeting
{
function __construct($name)
{
$this->name = $name;
}
function say($message)
{
echo "$message $this->name.";
}
}
$g = new Greeting('John');
$g->say('Hello'); // Hello John.
V PHP neočekáváme, že by bylo možné metodu třeba uložit do proměnné či jiného atributu a poté zase zavolat:
$method = $g->say;
$g->greet = $method;
$g->greet('Hello');
Nebo dokonce za chodu přidávat metody nové:
$g->shout = function($message) {
echo "$message $this->name!!!";
};
$g->shout('Hello'); // Hello John!!!
Na jedné straně je mi líto, že tohle PHP neumí, na straně druhé vidím ve statickém pojetí tříd podstatné výhody. A na straně třetí: v PHP lze tohle chování snadno emulovat.
Emulace dynamiky
Vytvořit funkci jako je výše uvedená shout()
a vložit ji do
proměnné objektu PHP už umí od verze 5.3. Ale abychom ji mohli zavolat
běžným zápisem, musíme si vypomoci magickou metodou
__call()
:
class Greeting
{
function __call($name, $args)
{
if (!isset($this->$name) || !$this->$name instanceof Closure) {
throw new Exception("Method $name not found.");
}
return call_user_func_array($this->$name->bindTo($this, $this), $args);
}
...
}
A nyní už bude příklad s metodou shout()
fungovat.
Abychom mohli stejně nakládat i se statickými metodami, jako byla třeba
výše uvedená say()
, doplníme ještě __get()
:
class Greeting
{
function __get($name)
{
if (!method_exists($this, $name)) {
throw new Exception("Property $name not found.");
}
return function() use ($name) {
return call_user_func_array(array($this, $name), func_get_args());
};
}
...
}
A nyní bude fungovat i první příklad s přiřazením
$method = $g->say
a následným voláním.
Pro verzi 5.3
Uvedené příklady vyžadují PHP 5.4. Ve verzi 5.3 jsou closures
ořezané a nesmí se v nich používat $this
. Řešení by
vypadalo trošičku jinak:
// for PHP 5.3
class Greeting
{
function __call($name, $args)
{
if (!isset($this->$name) || !$this->$name instanceof Closure) {
throw new Exception("Method $name not found.");
}
array_unshift($args, $this);
return call_user_func_array($this->$name, $args);
}
function __get($name)
{
if (!method_exists($this, $name)) {
throw new Exception("Property $name not found.");
}
return function() use ($name) {
$args = func_get_args();
return call_user_func_array(array(array_shift($args), $name), $args);
};
}
...
}
A namísto $this
bychom uvnitř closure použili první
argument, pojmenovaný třeba $self
.
$g->shout = function($self, $message) {
echo "$message $self->name!!!";
};
Nicméně narozdíl od 5.4 varianty má nyní funkce přístup jen k veřejným proměnným třídy.
Komentáře
midlan #1
Přišel jsem ještě na dva způsoby přidání funkcí/metod přímo bez overloadingu (nevím jestli tímto způsobem půjde použít $this, používám PHP 5.3):
midlan #2
#1 midlane, druhá možnost někam zmizela, tak tady je:
$obj->pole_funkci[smazáno] = function() {
echo(‚čau
<br>
‘);};
$obj->pole_funkci[smazáno](); // funkční
nediik #3
tady trosicku nesouhlasim, v pripade pridavani metod za behu by v programu byl desnej gulas. asi by se v nem spatne orientovalo. navic by to porusilo i funkce napr. u netbeans, kdy se krasne preklikavam z jedne tridy do druhe pouhym poklikanim na urcite volani metody.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.