Funkce pro vyhodnocení PHP kódu
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.
Diskuse
Ří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é! :-)
10.8.2005 12:17:45
"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/#security10.8.2005 13:48:46
ano, "or by people attempting to push the boundries of PHP" ;-)
10.8.2005 14:14:12
ano, "attempting", ne "pushing" ;)
10.8.2005 17:57:15
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.
10.8.2005 14:25:19
třeba:
<?php
if (PHP_VERSION < 5) eval('
function &clone($obj) { return $obj; }
');
?>
ale samozřejmě je to jinak prasárna, nebrat!
10.8.2005 16:40:05
Zdeněk Merta:
Což by samozřejmě šlo elegantněji vyřešit includnutím souboru, kde by byly potřebné funkce deklarovane.
10.8.2005 16:56:11
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.
10.8.2005 22:50:00
Jan Tichý:
Ale proč toto? Proč ne třeba:
<?php
if (PHP_VERSION < 5) {
function &clone($obj)
{
return $obj;
}
}
?> 6.9.2005 11:15:48
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.
6.9.2005 11:21:29
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
10.8.2005 17:41:36
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
10.8.2005 17:49:15
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
10.8.2005 20:40:45
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()
10.8.2005 21:55:06
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...
11.8.2005 00:54:25
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.
11.8.2005 09:59:14
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ů :)
11.8.2005 13:24:39
v6ak:
A když parsuju tokenizerem, jak z "'řetězec'" (bez dvojitých uvozovek) udělám "řetězec" (bez dvojitých uvozovek)?
16.12.2007 12:35:56
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
24.7.2008 06:17:17
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...
21.9.2010 12:15:49
Od PHP 5.3 lze zavolat $trida::$metoda($parametr).
21.9.2010 14:46:37
Michal:
Jo jednou jsem byl donucenej pouzit eval .. bylo to parsovani LUA pole do php pole .. (mělo to skoro 2 mega)
12.5.2011 22:48:44
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) ?
28.2.2012 20:52:07
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"}.
29.2.2012 06:52:35
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.
14.7.2012 09:02:32
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);
.....
?>
14.7.2012 12:22:58
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.
1.3.2017 17:08:02