Na navigaci | Klávesové zkratky

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

  1. 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):

    <?php
    class Object {
      public $jedna_funkce;
      public $pole_funkci;
    }
    $obj = new Object();
    $obj->jedna_funkce = function() {
      echo('ahoj<br />');
    };
    call_user_func($obj->jedna_funkce); //funkční
    $obj->jedna_funkce(); //nefunkční, snaží se zavolat metodu 'jedna_funkce'
    před 12 lety | reagoval [2] midlan
  2. 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í

    před 12 lety
  3. nediik #3

    avatar

    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.

    před 10 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.