Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

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 4 lety | odpovědět | 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 4 lety | odpovědět
  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 3 lety | odpovědět

Zanechat komentář

Text komentáře
Kontakt

(kvůli gravataru)



*kurzíva* **tučné** "odkaz":http://example.com /--php phpkod(); \--