runkit extension a možné AOP

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
klip
Člen | 11
+
0
-

Možná to tu někoho zaujme. Nenašel jsem tu na fóru téměř žádné zmínky o AOP tak nevím, zda tu jsou nějací příznivci.
V PHP jsem nenašel moc použitelných věcí, často je to zastaralý a nebo provázaný s něčím větším.
(viz AOP v PHP).
Některé přístupy fungují přes preprocesování kódu přímo v IDE, někde se používá proxy objektů.

Narazil jsem ale na extensionu runkit (http://www.php.net/…k.runkit.php), která umí za běhu přejmenovávat, přidávat a mazat funkce, metody, konstanty, předědit a vydědit třídy, a v neposlední řadě v odděleném vlákně sandboxovat vykonávaný kód.

Tak sem zkusil malý test:

<?php

function weave_class_method($class, $method, $cuts)
{
    if (!isset($cuts['before'])) { $cuts['before'] = ""; }
    if (!isset($cuts['after'])) { $cuts['after'] = ""; }

    runkit_method_rename($class, $method, "AOP_{$method}_invoke");
    runkit_method_add($class, $method, '',
        $cuts['before'].
        '; $return = $this->AOP_'.$method.'_invoke();'.
        $cuts['after'].
        '; return $return;',
        RUNKIT_ACC_PUBLIC);
}

class TestClass
{
    public function sayHello()
    {
        echo "Hello\n";
    }
}

function test()
{
    $t = new TestClass();
    echo "sayHello() call start\n";
    $t->sayHello();
    echo "sayHello() call end\n";
    echo "\n";
}

test();
weave_class_method('TestClass', 'sayHello', array(
        'before' => 'echo "BEFORE!!!\n";',
        'after' => 'echo "AFTER!!!\n";',
));
test();

?>

výstupem je:

sayHello() call start
Hello
sayHello() call end

sayHello() call start
BEFORE!!!
Hello
AFTER!!!
sayHello() call end

Sympatické mi je na tom, že by tím šlo weavovat nejen public metody a dokonce přes to lze weavovat i obyčejné funkce (runkit_function_rename a runkit_function_add). Pointcut na vyjímku by šel teoreticky taky udělat jen vložením originální metody do try catch bloku. Tím se pokrývá většina případů AOP a přijde mi, že implementovat to nebude složité.

Problém by bylo definovat pointcut někde uprostřed té původní metody/funkce. Na to by už bylo potřeba načíst definici ze zdrojáku a ten zdroj za běhu přepsat. To ale taky není nemožné.

Ještě nevím, jestli budu AOP v brzké době používat, ale jestli ano, budu si s runkit hrát trochu víc, tak pak možná budu moct poreferovat o tom jestli je to i použitelné (funkční a výkonné) a to bych se pak i podělil s implementací.

Když sem si změřil milión volání test() před úpravou, tak je to 6.6 sekund. Po weavnutí je to 11.1 sekund.

Jedno weavnutí a milión mínus jedno předefinování metody (runkit_method_redefine místo rename a add když už je originální metoda weavnutá) trvalo 32.4 sekundy.

Na to, na co bych to využil já, by to použitelné bylo už po tomhle malém příkladu. Projekt, který dělám má omezený počet uživatelů (stovky) a spíš dělá delší operace, než že by se spouštěl často a hodně uživateli ve stejnou chvíli. Takže to nebude jednoduše poznat.

Jediná velká nevýhoda je závislost na extensione runkit, ale můj projekt je intranetí aplikace, kde si můžu nainstalovat víceméně co chci :)

Filip Procházka
Moderator | 4668
+
0
-

Osobně se mi AOP strašně líbí, ale přijde mi to jako brutální overhead, ještě větší než OOP.

Znáš FLOW3?

klip
Člen | 11
+
0
-

Narazil jsem na FLOW3 právě při zkoumání dostupných AOP řešení, ale přijde mi to zbytečně moc komplexní a univerzální. Na to nemám teď bohužel čas, ale hlavně nedonutím kolegy se to naučit! :)

Pro blog až střední web je AOP určitě overhead, ale pro intranet aplikaci, kde je logiky relativně hodně, mi to přijde jako ideální přístup jak snížit šanci spaghetti kódování a jak centralizovat správu logování aktivit uživatelů, autorizaci, případně i transakční logiku, ale to si ještě rozmyslím :)

Sem si dneska chvíli hrál :) https://gitorious.org/yapaa (interface, implementace a test)

Potřebuju pořádně prozkoušet ještě exceptiony a zbavit se require v kódu a vymyslet jak to propojit třeba s Nette. Bude nutné weavovat funkce a metody až po té, co jsou nahrány přes autoload mechanismus, a to samotné by se dalo řešit přidáním advice po spuštění toho loadovacího mechanismu. Takový lazy-weaving :)

Prozatím nebudu řešit pointcuty někde uprostřed kódu funkce, protože by to bylo malinko náročnější. Správně má funkce dělat jen jednu věc a nemá mít víc jak pár řádků. Jestli budu někdy mít potřebu weavovat funkci někde uprostřed, možný by to mohla být známka, že mám špatnej design a funkci rozdělit :)

Pochopitelně to závisí na nainstalované a zapnuté extensioně runkit.

Pro 3. assert, tj. weavnutí PHP interní funkce je potřeba do php.ini přidat: runkit.internal_override=1 Ale pro běžné použití bych to nedoporučoval :)

Editoval klip (28. 12. 2011 2:04)

Filip Procházka
Moderator | 4668
+
0
-

Nevím jestli se mi to líbí, určitě jsou mi sympatičtější proxy třídy.

klip
Člen | 11
+
0
-

To rozhodně, ale nenaráží náhodou proxy ve chvíli, kdy nemám k třídě interface? A pak proxy neumí obalit obyčejnou funkci.

Je fakt, že jestli budu weavovat, tak budu weavovat svůj kód a to bude psáno proti interface :)

Prostě sem měl náladu to zkusit přes runkit, protože mě baví metaprogramování :D

klip
Člen | 11
+
0
-

Tak sem to celý redesignoval a

  • přidal jsem možnost definovat pointcuty maskou pro hromadný advicování, třeba method(*Presenter,render*) najde všechny render metody ve všech prezenterech,
  • přidal jsem around advice, kde použití Yapaa::proceed(); vyvolá původní funkci a lze ji volat i vícekrát,
  • odchytává se $argc a $argv a před voláním původní funkce se dá modifikovat,
  • stejně tak $return obsahuje odchycený výstup původní funkce a dá se po volání modifikovat a
  • napsal jsem lepší testy.

\Yapaa\Yapaa::Pointcut() je faktory, která si pamatuje všechny vytvořený pointcuty a po zavolání \Yapaa\Yapaa::weaveAllPointcuts() se vše přeweavuje.

Použití například pro weavnutí autoloaderu, aby reweavnul vše po loadnutí nové třídy:

<?php
	$autoloaderPointcut = new \Yapaa\Yapaa::Pointcut('function(autoloader)');
	$autoloaderPointcut
	    ->addAdviceAfter('\Yapaa\Yapaa::weaveAllPointcuts();')
	    ->weave();
?>

Taky jsem sepsal menší dokumentaci: https://gitorious.org/…a/pages/Home

Testy v žádném případě nepokrývají všechny možné kombinace. Zkusim to používat a budu testy postupně přidávat.

Případné TODO, na které se ale moc nechystám, protože prozatimní stav mi vyhovuje a další featury jsou každá tak na tolik času, kolik sem tomu už teďka věnoval:

  • Vytvoření abstraktní Aspect třídy, kde by se metody všech potomků weavovali přes pointcuty definované v @anotacích. Prozatím si budu psát aspekty přes normální statický metody, a navedu si to ručně.
  • Možnost definovat pointcut na jiné joinpointy než je exekuce funkce nebo metody.