Funkce pro vyhodnocení PHP kódu

Školení, která pořádám

Jedna z technik, které je lepší se obloukem vyhnout, je vyhodnocení řetězce jako PHP kódu funkcí eval. Důvod je ten, že pokud řetězec vytváříme dynamicky nebo ho např. načítáme z databáze, musíme důkladně ošetřit, aby neobsahoval něco nepatřičného. Osobně jsem se zatím nesetkal se situací, kdy by se použití této funkce nedalo obejít bez ztráty elegantnosti kódu. Pokud se bez funkce obejít nedá, obvykle je někde něco špatně:

<?php
// špatně
eval("zpracovat_$znacka('$atributy');");

// o něco lépe
call_user_func("zpracovat_$znacka", $atributy);

// správně
zpracovat($znacka, $atributy);
?>

Kromě funkce eval je možné dynamický kód vytvořit také funkcí create_function. Čas od času ji používám pro vytvoření callback funkcí (např. pro preg_replace_callback, array_map nebo register_shutdown_function), pokud se mi nechce kvůli triviálnímu kódu vytvářet regulérní funkci. Zásadně ale kód nevytvářím dynamicky skládáním proměnných, ale uvádím ho do apostrofů.

PHP kód se vykoná i při použití modifikátoru e ve funkci preg_replace. Díky funkci preg_replace_callback se bez něj dá obejít, ale někdy dovoluje vytvořit přehlednější a kratší kód.

Jakub Vrána, Seznámení s oblastí, 10.8.2005, diskuse: 27 (nové: 0)

Diskuse

ikona Radek Hulán:

Říci, že eval() je špatně, je špatné, dokonce bych řekl, že eval() je jedna z mála věcí, které u mě dávají PHP milost, dají se s tím dělat psí kusy, skládat kód za pochodu, a to je dobré! :-)

ikona spaze:

"If eval() is the answer, you're almost certainly asking the wrong question."

"Eval seems to be a hot topic of discussion lately, especially in light of the recent vBulletin exploits and past exploits in common applications such as phpMyAdmin. Eval is one of the functions in PHP which can execute arbitrary code. Generally eval is used either by inexperienced programmers for a variety of misguided reasons, or by people attempting to push the boundries of PHP."
-- http://www.sitepoint.com/blog-post-view?id=238381


-- http://phpxmlrpc.sourceforge.net/#security

ikona Radek Hulán:

ano, "or by people attempting to push the boundries of PHP" ;-)

ikona spaze:

ano, "attempting", ne "pushing" ;)

Zdeněk Merta:

Docela by mě zajímala ukázka nějakého opodstatněného použití eval()
Podle mě to spíš zavání programátorovou neschopností řešit problém elegantněji.

ikona dgx:

třeba:

<?php

if (PHP_VERSION < 5) eval('
   function &clone($obj) { return $obj; }
'
);

?>

ale samozřejmě je to jinak prasárna, nebrat!

Zdeněk Merta:

Což by samozřejmě šlo elegantněji vyřešit includnutím souboru, kde by byly potřebné funkce deklarovane.

ikona dgx:

Samozřejmě. Obě metody jsem srovnával, a zjistil jsem, že eval je nesmírně rychlejší. Na druhou stranu se nedají využít výhody různých bytecode cache a optimalizátorů (např. Zend Optimizer atd.), takže pak je lepší inkludovat.

Jan Tichý:

Ale proč toto? Proč ne třeba:

<?php

if (PHP_VERSION < 5) {
    function &clone($obj)
    {
        return $obj;
    }
}

?>

ikona Jakub Vrána OpenID:

Také jsem si to říkal, málem jsem si to chtěl vyzkoušet, ale pak mi došlo, že clone je v PHP 5 klíčové slovo, takže pokud je ve zdrojáku na nevhodném místě, způsobí parse error.

Jakub Podhorský:

já jsem musel jednou eval použít kdy jsem měl několik tříd v PHP5 s konstantami se stejnýmm názvem ale každá byla v jiné třídě a dynamicky jsem je volal a bohužel mi nefungovala konstrukce $this->nejakaPromena::KONSTANTA tudíž zde nebylo jiné východisko něž eval() použít

jinak osobně se eval() nebráním a nepovažuji to za programátorskou neschopnost ale občas i za neschopnost PHP

Zdeněk Merta:

No nějak si neumím představit, co myslíš, ale řekl bycxh, že konstanty ve třídách se nevolají přes $this->nejakaPromenna::KONSTANTA

Jakub Podhorský:

normálně ne ale já jí potřeboval volat dynamicky

$this->nejakaPromena totiz obsahovala název třídy z které jsem tutokonstantu načítal ale tuhle konstantu jsem měl ve více třídách které jsem načítal parametrem z metody a tak jsem dopředu nemohl vědět o jakou třídu se jednalo

dám radši příklad :)

<?php

class HlavniTrida {
    private $nazevTridy = '';
    private $object;
    private $hodnota = '';

    public function __construct($nazevTridy) {
        $this->nazevTridy = $nazevTridy;
        $this->object = new $nazevTridy();

        //A teď ta část s tím eval
        eval('$this->hodnota = '.$nazevTridy.'::MOJE_KONSTANTA');
    }
}

class
Trida1 {
    const MOJE_KONSTANTA = 'nejaka hodnota';
}
class
Trida2 {
    const MOJE_KONSTANTA = 'nejaka dalsi hodnota';
}

?>

a bohužel se to v PHP nijak jinak nedá udělat :( teda pokud vím

Zdeněk Merta:

No určitě se to dá vyřešit mnohem elegantněji, ale záleží na konrétním případě.

Každopádně v tomto případě by se použití eval() dalo jednoduše vyvarovat třeba takto:

<?php
class Trida1 {
    const MOJE_KONSTANTA = 'nejaka hodnota';

    public function getMojeKonstanta() {
        return self::MOJE_KONSTANTA;
    }
}
?>

No a v třídě HlavniTrida už můžeš hodnotu konstanty zjišťovat přímo voláním metody instance.

No není to úplně košéř OO řešení, ale chtělo by to vidět konkrétní problém. Takhle je to ale určitě mnohem elegantnější než používat eval()

Jakub Podhorský:

no tohle řešní se mi právěže vůbec nelíbí :) a taky mě to už napadlo, ale prostě se mi nelíbí jelikož konstanta má být nezávislá na instanci objektu a v mém konkrétním řešení jí volám ještě před instancí

neříkám že eval() je taky nejčistčím řešením ale v tomhle případě jediným možným....a ten příklad co jsem zde uvedl je v podstatě ten co mám ve své třídě  akorát je to zbavený o další kód a daný do jednoduchý formy...

Zdeněk Merta:

Hmm, uznávám, že zde ti asi většina dá za pravdu, že je jednodušší použít eval() - pokud tedy neexistuje nějaká konstrukce, která by umožňovala získat obsah konstanty, tak jak je to potřeba v tomto případě a ani jeden ji neznáme.

Alternativu bych viděl v použití Reflection

<?php
class Test {
    const KONSTANTA = 'nejaka hodnota';
}

$class = new ReflectionClass('Test');

echo
$class->getConstant('KONSTANTA');
?>

Nicméně je to o něco pracnější a v podstatě jen pro zajímavost, jak by se to dalo také řešit.

Jakub Podhorský:

jojo tohle mě taky napadlo když jsem psal svůj poslední příspěvek

ale spíšse mi zdá že reflection není na tohle zrovna to pravé ořechové a ke všemu je to zbytečný vytváření objektu, který vůbec nepotřebuju

btw: Reflection spíš používám na kontrolu tříd jestli implementují a dědí nebo obsahují správné prvky což se dá skvělelpoužít u pluginovacích systémů :)

v6ak:

A když parsuju tokenizerem, jak z "'řetězec'" (bez dvojitých uvozovek) udělám "řetězec" (bez dvojitých uvozovek)?

sNop:

zatat jedine naco eval pouzivam(a asi ho nikdy na nic jine pouzivat nebudu) je zakodovani zdrojaku v jednoduchsich programech, kde to potrebuji alebo zakodovani jen nejake funkce viz. priklad http://madnet.name/eng/files/1/10.html zrojove kody c99madshell.php

Václav Holuša:

Já třeba eval() používám pro sestavování layoutu. Mám to v databázi uložené jako [trida], [metoda], [parametr] a uživatel si u každé stránky nadefinuje jaké moduly kde chce a já je pak jenom cyklem vypíšu
$metoda = $pole['trida']."::".$pole['metoda']." ".$pole['parametr'].");";
eval($metoda);

... na lepší způsob jsem bohužel nepřišel...

ikona Jakub Vrána OpenID:

Od PHP 5.3 lze zavolat $trida::$metoda($parametr).

Michal:

Jo jednou jsem byl donucenej pouzit eval .. bylo to parsovani LUA pole do php pole .. (mělo to skoro 2 mega)

Wena:

A jak jinak než pomocí f-ce eval() dokázat něco takového:
<?php
   
for($i=1;$i < 11; $i++) {
        eval('$barva = $p->Barva_'.$i.';');
        echo $barva;
    }
?>

neboli načítat hodnotu z $p->Barva_1, $p->Barva_2, $p->Barva_3 atd..   pomocí cyklu (skládat název elementu a načítat z něj hodnotu) ?

ikona Jakub Vrána OpenID:

V první řadě je tento kód špatně navržen – místo $p->Barva_1, ... by bylo lepší mít pole $p->barvy, kde by barvy byly pěkně pohromadě.

Nicméně i s takto špatně navrženou strukturou se dá pracovat bez evalu:

<?php
for($i=1;$i < 11; $i++) {
    $barva = "Barva_$i";
    echo $p->$barva;
}
?>

Případně rovnou $p->{"Barva_$i"}.

Robert Máslo:

php_check_syntax - 5.0.5 This function was removed from PHP.

Ne každej má právo pouštět něco jako:
<?php
exec
("php -l $file",$error,$code);
?>

Pak je asi jedinym řešením eval:

Bool informace o tom zda kód scriptu je ok:
<?php
$code1
= 'return true; ?>'.file_get_contents('script1.php');
$ok = eval($code1);
?>

Nebo zjištění kde je chyba:
<?php
$code
= 'return true; ?>'.$code;
ob_start();
eval(
$code);
$err = ob_get_contents();ob_end_clean();
if (
$err!='')
  {
  $err = str_replace('<br />','',$err);
  $btv = TextBetween(' in ',' on ',$err);
  echo str_replace(' in '.$btv,'',$err);
  }
?>

Tento check_syntax narozdíl od půvpdního nespouští vlastní kód (což vidím jako velkou výhodu). Samozřejmě funkce a classy se zavedou - pokud by to někomu vadilo tak by se to dalo ošetřit pomocí namespace.

Robert Máslo:

A nebo pokud nechci aby se nezaváděly funkce a classy (a nechci si hrát s namespace) je možno celý script zabalit do funkce.
Tam trochu nastává problém s tím že .php soubor může ale nemusí být ukončen pomocí tagu na ukončení php. Řešení je jednoduché ... ukončovací tag přidat ... pokud je tam potom celkem 2x tam to ničemu nevadí - ten druhý je jakoby v HTML kontextu.

<?php
$code
= 'return true; function xxx___xxx___xxx() { ?>'.$code.'?><?php };';
ob_start();
eval(
$code);
.....
?>

Miloš Brecher:

Funkci eval() používám. Budu se tedy muset zamyslet nad svými programátorskými schopnostmi. Používám ji pro zpracování šablon, kde za běhu oddělím blok head šablony od bloku body - kód HTML + PHP mám potom v proměnné a když ho nechci zbytečně ukládat na disk, tak jsem nenašel jiný rozumný způsob jak kód vykonat než eval(). Jinde eval() nepoužívám. Je ještě potřeba fixnout číslo řádku u chybové hlášky - aby chyby PHP z eval měly číslo řádku odpovídající zdrojovému souboru.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.