PHP a Python: volání objektu známého až za běhu

PHP

Přestože je PHP často zatracováno a mluví se o něm jako o jazyku bastličů, obsahuje řadů zajímavých vlastností, které velmi usnadňují práci. Jedna z těchto vlastností je možnost snadno volat třídu/metodu/funkci, aniž bychom před spuštěním skriptu znali její jméno.

Díky této technice nám například stačí jeden kód pro ukládání záznamů do libovolné tabulky:

$class = 'ActiveRecord_' . $_POST['table'];
$instance = new $class();
$instance->loadFromPost();
$instance->save();

Stejně tak můžeme přistupovat k metodám/instančním proměnným, jejichž jméno zjistíme až za běhu:

echo $instance->{'id_' . $_GET['table']};

Python

Protože je Python (narozdíl od PHP) silně typový, nemůžeme přetypovat řetězec na odkaz na objekt. Tento odkaz ale můžeme získat pomocí funkce getattr. Prvním parametrem je objekt, který chceme prohledávat (v tomto případě současný modul), druhým parametrem je název datového atributu (instanční proměnné), atributu třídy (statické proměnné) nebo metody.

class_name = getattr(sys.modules[__name__], 'ActiveRecord_' + table)
instance = class_name()
instance.save()

Hodnocení

Komentáře

2009-02-01 19:40:40

Zrovna ten příklad s neznámým jménem třídy se mi vůbec nelíbí a osobně to považuju za... hodně nečistou věc, které se osobně vyhýbám jako čert kříži.

Na tento komentář odpověděl [2] Dundee
Na tento komentář odpověděl [3] v6ak
[2] Dundee
2009-02-01 20:08:36

#1 Jan Tichý: Zajímavý názor. Mohl bys to trochu přiblížit? Používám to relativně dost často (při vytváření zmíněných ORM objektů, vytváření view a vlastně i při výběru controlleru) a nenapadá mne, jak to udělat lépe.

Na tento komentář odpověděl [6] Jiří Pospíšil
[3] v6ak
2009-02-01 20:52:09

Celkem souhlasím s #1 Jan Tichý: . Ještě by se to dalo použít, pokud víš, že ta třída určitě existuje (pozor na http://v6ak.profitux.cz/clanky/include-na-prani-zneuziti-a-obrana.php a to i u autoloadu). Jsem zastánce whitelistu. Ale celkově jsem od těchto způsobů dávno upustil, prostě to IMHO není dostatečně univerzální řešení.
Metoda loadFromPost taky není nic čistého. Přejdu globální proměnné, ale na bezpečnost bacha. Viděl jsem takový hezký příklad v manuálu symphony, který názorně vysvětloval, proč takto ne. Moment.

Na tento komentář odpověděl [4] v6ak
[4] v6ak
2009-02-01 21:20:29

#3 v6ak: Tak ten příklad je tady: http://www.symfony-project.org/book/forms/1_1/en/02-Form-Validation#chapter_02_validators_security . Myslím, že u loadFromPost je problém velmi podobný.
Za předchozí duplicitu komentáře se omlouvám, byl jsem přesměrován na RSS (možná chyba Opery mini) a myslel jsem, že se to nevložilo.

[5] Dundee
2009-02-01 22:19:30

Máte pravdu, že pokud vstupy neprojdou řádnou kontrolou a je neopatrně použit include, může být něco takového použito pro PHP injection. Ale, že by celá tato technika měla být špatná, to mi přijde přehnané.

2009-02-01 22:26:05

#2 Dundee: Konkrétně v tomto případě nemáš zaručeno, že ti někdo nepošle v $_POST['table'] úplně jinou tabulku. Slušelo by se také zkontrolovat, jestli $_POST['table'] neobsahuje nějaké nepovolené znaky. Auto loading se ti postará o kontrolu existence souboru a tím vlastně i o existenci třídy (podle toho "_" předpokládám, že používáš zendovský styl pojmenovávání), takže tam by neměl být problém.

Jinak na samotné technice nevidím nic špatného. Teď si nevzpomenu na jiný způsob implementace třeba factory patternu, pokud nechceme vyjmenovávat všechny adaptéry (třeba je ani neznáme).

Na tento komentář odpověděl [7] Dundee
[7] Dundee
2009-02-01 23:01:32

#6 Jiří Pospíšil: Samozřejmě. Je to jen příklad ukazující základní použití techniky. Nesnaží se postihnout bezpečnostní otázky okolo. Kdybych do toho příkladu doplnil i kontrolu vstupů, použití té techniky by se tam úplně ztratilo.

[8] LLook
2009-02-01 23:53:00

Slíbil jsem si, že nebudu nic psát pod vlivem, ale nemůžu si pomoct...

Ad příklad č. 1: Který jiný živý jazyk to neumožňuje? Tohle umí i prudérní Java a .NET...

/---code cs
var className = "ActiveRecord_" + Request.Form["table"];
var instance = (System.Type.GetType(className)).GetConstructor(System.Type.EmptyTypes).Invoke(null);
// ...
\---

Akorát pro ten přístup k metodám `loadFromPost()` a `save()` by bylo třeba přetypovat na předem známý typ (typicky předka nebo rozhranní), protože na rozdíl od PHP a Pythonu nám nebude fungovat kód (ani se nepřeloží), který může volat metodu, která "možná existuje a možná ne".

Teď koukám, že můj příklad v C# spíš koresponduje s tvým příkladem v Pythonu. Stejně jako tam totiž třída samotná je objektem a to jiného typu, než řetězec. V příkladu s Pythonem má tudíž proměnná "class_name" zavádějící název, protože ve skutečnosti nenese pouhý název třídy, ale třídu samotnou.

Na tento komentář odpověděl [9] Dundee
[9] Dundee
2009-02-02 00:56:06

#8 LLook: Umí to celkem jistě většina jazyků, já jsem chtěl pouze poukázat na to, že v Pythonu a zvlášť PHP to lze opravdu velmi snadno a elegantně. Přecejen srovnej kód pro Javu a PHP.

[10] petr
2009-02-28 12:09:57

jen připomenu pro php mnohem přehlednější funkci `call_user_func()`

Komentáře již nelze přidávat