Automatická registrace komponenty
- David Zadražil
- Člen | 62
Dobrý den,
začíná se mi kupit v BasePresenteru spousta kódu kvůli komponentám. Rád bych nějak vyřešil automatické registrování komponent, ale nevím jak. Našel jsem následující kód (který jsem umístil do BasePresenteru, ale ten mi z nějakého důvodu nefunguje (komponentu to nezaregistruje).
protected function createComponent($name)
{
$component = parent::createComponent($name);
if ($component === NULL && class_exists($name)) {
$component = new $name;
}
return $component;
}
Díky za jakékoli nakopnutí.
Editoval David Zadražil (3. 8. 2013 12:02)
- Jan Tvrdík
- Nette guru | 2595
@David Zadražil: Blahopřeji, pravě jsi v aplikaci vyrobil bezpečnostní chybu.
- pepakriz
- Člen | 246
Používám toto řešení: https://github.com/…resenter.php#L175
mám službu widgetManager
, do které přes neon registruji
továrničky na komponenty. Když v presenteru neexistuje továrnička na
komponentu, vyzkouším ještě, jestli náhodou neexistuje ve
widgetManageru
.
- David Zadražil
- Člen | 62
@Jan Tvrdík: Díky, později mi to došlo že to nebude nejvhodnější řešení.
@pepakriz: Vyzkouším, díky.
- Šaman
- Člen | 2666
Požívám podobné řešení jako popisuje Pepa, jen na to nemám žádný manager a jen tu továrničku hledám v configu. Tohle je v BaseComponent, obdobná metoda je i v BasePresenteru:
<?php
/**
* Supertovárnička na komponenty
* Pokud neexistuje volaná komponenta, zkusí se zavolat továrnička z configu
*
* @param string $name třida komponenty
* @return IComponent
*/
protected function createComponent($name)
{
$controlName = ucfirst($name);
if (method_exists($this, "createComponent" . $controlName))
{
return parent::createComponent($name);
}
else
{
return $this->presenter->context->createService($name);
}
}
?>
V configu pak stačí:
services:
registrationForm: App\Controls\RegistrationForm
lostPasswordForm: App\Controls\LostPasswordForm
Editoval Šaman (3. 8. 2013 14:46)
- Jan Tvrdík
- Nette guru | 2595
@Šaman: Blahopřeji, pravě jsi v aplikaci vyrobil bezpečnostní chybu.
Viz také https://forum.nette.org/…s-komponenty
Koukám, že Vojta ti to vysvětloval už před 11 měsíci a pak znovu před 3 měsíci. Takže teď to zkusím já.
Ta tvoje „supertovárnička“ je špatně ze dvou důvodů
- Nerespektuje API definované třídou
UI\Presenter
, od které dědí. Součástí toho API je totiž i požadavek, že v případě, že metodacreateComponent
není schopna požadovanou komponentu vytvořit, tak by měla vrátitNULL
, nikoliv vyhodit výjimku. Dále pak existuje požadavek, že když už vracíš hodnotu, který neníNULL
, tak se nutně musí jednat o instanciIComponent
. To taky porušuješ. - Z bezpečnostního hlediska – za žádných okolností bys uživateli neměl dát možnost vytvářet instance libovolné třídy nebo služby. Uvědom si, že tím, že se vytvoří služba se kaskádově vytvoří (nejsou-li již vytvořeny) i všechny její závislosti. Tedy dáváš uživateli možnost zavolat obrovské množství konstruktorů, injekt metod, setterů a vůbec všeho, co v configu máš. Viz také dva roky staré vysvětlení od Honzy Marka.
Editoval Jan Tvrdík (3. 8. 2013 15:25)
- Šaman
- Člen | 2666
Kdybych toho tam měl skutečně velké množství, vytvořil bych extenzi,
kde by byly jen volatelné komponenty. Porušení API mi nedošlo, opravím
to.
Ještě jsi zapomněl zmínit, že volám context a tím skrývám závislost
komponenty (presenteru) na jiné komponentě.
Na druhou stranu množství (i když ne obrovské) továrniček v nějakém
BasePresenteru (pro společné komponenty typicky vykreslované v layoutu),
nebo opakujících se továrniček po různých presenterech mi vadilo. Tohle
tento problém zcela eliminovalo za cenu skrytí závislosti a možnosti volat
kteroukoliv komponenty odevšad. (To API půjde spravit.)
Takže pokud vím, že nějaká komponenta nemá být volatelná odkudkoliv,
nedávám jí do configu (mám je i oddělené do zvláštního souboru, aby
toto řešení bylo co nejvíce přehledné).
Ideálním řešením by asi bylo mít nějakého toho manažera komponent a
toho předat jako závislost komponentám/presenterům. A ten by měl nějak
definováno co kdo a za jakých podmínek může komponentu vytvořit. Ale pro
klasické, často jen informační komponenty, které používám mi toto stále
nebezpečné nepřijde. Vojta už mi to dvakrát vysvětloval na příkladu,
který je mimo můj způsob práce. Například já kontroluji práva na
akci/signál ve všech presenterech přes ACL. Pokud uživatel nemá právo na
metodu delete!
komponenty userDataGrid
, tak mu
vypíšu nedostatečná oprávnění. Zůstává mu jen možnost zobrazit
komponentu kdekoliv, nicméně komponenta by si sama měla kontrolovat, jestli
nevypisuje něco, co nesmí. To už je ale na jinou diskuzi.
Takže bezpečnostní chyba tím vzniknout může za
určitých podmínek, ale také nemusí, pokud si rizika rohoto řešení
eliminuji. Mimochodem pokud dám továrničku
createComponentUserDataGrid()
do Basepresenteru, tak mám stejnou
bezpečnostní díru bez velkého humbuku.
Editoval Šaman (3. 8. 2013 19:34)
- Filip Procházka
- Moderator | 4668
Ideální řešení je takové, kde ty továrničky napíšeš ručně. A ideálně pouze v presenterech, kde je reálně používáš.
- Jan Tvrdík
- Nette guru | 2595
Ještě jsi zapomněl zmínit, že volám context a tím skrývám závislost komponenty (presenteru) na jiné komponentě.
Protože to není obecně problém. DI není svatý grál a tedy nelze obecně tvrdit, že nepoužívání DI je špatně.
Na druhou stranu množství (…) továrniček v nějakém BasePresenteru (…), nebo opakujících se továrniček po různých presenterech mi vadilo.
Problém jsi viděl správně, řešení jsi nicméně vymyslel špatně.
Takže pokud vím, že nějaká komponenta nemá být volatelná odkudkoliv, nedávám jí do configu
Jenomže ty to vůbec nemáš omezené na komponenty, ale umožňuješ
přístup úplně ke všem službám. Kdyby jsi ty komponenty, které chceš
mít globálně dostupné, měl označené nějakým tagem a umožnil takto
získat jen komponenty s daným tagem, tak by situace byla výrazně lepší.
Stejně tak by bylo v pořádku, kdybys nad každým presenterem měl anotaci
@allowedComponents
, kde bys měl vyjmenované komponenty, které se
mohou nad daným presenterem vytvořit.
Takže bezpečnostní chyba tím vzniknout může za určitých podmínek, ale také nemusí, pokud si rizika rohoto řešení eliminuji.
Mýlíš se. Bezpečnostní chyba, kterou nejde (momentálně) zneužít, je pořád bezpečnostní chyba. Pokud máš v aplikaci SQL injection zranitelnost, kterou nejde nijak zneužít, tak to nic nemění na faktu, že ta SQL injection je bezpečnostní chyba. Nehledě na to, že je velmi náročné hlavou propočítat / promyslet skutečně do hloubky, že danou bezpečnostní chybu nejde nijak zneužít. Spoléhat se na tuto nejistotu mi přijde velmi naivní. Je nesmírně náročně garantovat do budoucna, že se při nějakém refactoringu (který navíc nemusíš dělat ty) nestane ta bezpečnostní chyba zneužitelná.
- Šaman
- Člen | 2666
Dík za analýzu, asi to přes ten tag, nebo rozšíření udělám. I ta
anotace – nebo možná virtuální metoda
createComponentFoo()
– je dobrý nápad. Navíc tím budou
závislosti někde vypsané, takže nebudou skryté. (DI se snažím dodržovat,
mám ho jen ve výše popsané metodě. Ono se to fakt pak líp ladí, když
vím, de se mi co vzalo.)
Někde jsem četl, že se plánuje přidat do konfigurace i nastavování presenterů. Pak by se daly ty závislosti na komponentách zapsat přímo tam.