nette/application 3.2: atribut #[Requires]
- David Grudl
- Nette Core | 8218
Přidal jsem do Nette Application 3.2 nový atribut
#[Requires]
. (Zatím v 3.2.x-dev větvi).
Pomocí tohoto atributu lze například povolit pro přístup k presenteru jen určité HTTP metody:
#[Requires(methods: 'POST')]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
#[Requires(methods: ['GET', 'POST'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
(Toto nastavení obchází a nahrazuje pole $allowedMethods
v presenteru.)
Lze vyžadovat AJAXový požadavek`:
#[Requires(ajax: true)]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Lze vyžadovat jen přístup ze stejného domény:
#[Requires(sameOrigin: true)]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Lze vyžadovat, že na presenter lze přistoupit jen nepřímo přes
forward()
:
#[Requires(forward: true)]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Lze povolit přístup jen k některým akcím:
#[Requires(actions: 'default')]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
#[Requires(actions: ['add', 'edit'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Všechny tyto hodnoty lze kombinovat.
Atributy u metod
Využití se stává mnohem zajímavější díky tomu, že lze atribut lepit nejen na třídu, ale také na tyto metody:
action<Name>()
render<Name>()
handle<Name>()
createComponent<Name>()
Poslední dvě metody se týkají také všech komponent!
Takže třeba lze kontrolovat, že akce je proveditelná jen AJAXovým POST požadavkem:
#[Requires(methods: 'POST', ajax: true)]
public function actionDelete(int $id)
{
...
}
Nebo že render-metoda bude dostupná jen tehdy, pokud se na ni přistoupí
nepřímo (forward nebo setView()
v action metodě):
#[Requires(forward: true)]
public function renderNotFound()
{
...
}
Nebo třeba kontrolovat, že komponenta bude dostupná jen v určitých akcích:
#[Requires(actions: ['add', 'edit'])]
public function createComponentPostForm()
{
...
}
U handle metod #[Requires(sameOrigin: false)]
nahrazuje atribut
#[CrossOrigin]
.
Alternativní zápis
Atributy lze zapisovat i tímto způsobem:
#[Requires(methods: 'POST')]
#[Requires(ajax: true)]
public function actionDelete(int $id)
{
...
}
Vlastní kombinace
Atribut můžete také podědit a pod jedním názvem mít konkrétní konfiguraci:
#[\Attribute]
class AllowAllMethods extends Nette\Application\Attributes\Requires
{
public function __construct()
{
parent::__construct(methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']);
}
}
#[AllowAllMethods]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Budu rád, když to otestujete a napíšete připomínky a podněty.
Aktualizace: finální popis fungování najdete na https://doc.nette.org/…ute-requires
- Miellap
- Člen | 12
Bylo by skvělé, kdyby se toto chování rozšířilo ještě o omezení komponent v akcích presenteru. Něco jako:
class MyPresenter extends Nette\Application\UI\Presenter
{
#[Requires(components: ['postForm'])]
public function actionPost()
{
...
}
}
Tímto by se omezilo to, že akce post může tvořit pouze komponentu postForm. V rámci mého kódu nedává smysl, aby se o toto omezení starala komponenta (jaké akce ji můžou tvořit), protože komponentu využívám v různých Presenterech a různých akcích a tím pádem by toto omezení postrádalo význam. (metody jako createComponentPostForm mám v traitách, abych zbytečně nepsala duplicitní kód)
Mimochodem, lze tento atribut využít i jinde než v Presenterech? Například handle metodách u komponent?
Editoval Miellap (16. 4. 22:21)
- nightfish
- Člen | 516
ViPErCZ napsal(a):
#[Requires(actions: ('add', 'edit'])]
Nevybalancované závorky → syntax error.
vs
#[Requires(actions: ('add', 'edit') )]
('add', 'edit')
není platný zápis hodnoty v PHP → syntax
error. Možná by fungovalo actions: array('add', 'edit')
, ale to
je fujfuj.
nebo
#[Requires(actions: ['add', 'edit'] )]
Tohle vypadá jako správná syntaxe, jen bych za uzavírací hranatou závorkou nedělal mezeru.
Kdyz pouziju
#[Requires(methods: ‚POST‘, ajax: true)]a poslu to non ajaxem, tak to hodi 5-ti kilo? Nebo 404-ku?
403 Forbidden
- ViPEr*CZ*
- Člen | 817
@nightfish samozrejme vim, jak to je spravne… ostatne pokus omylem
to taky jde zjistit :-D stejne jako 403-ka ;-)
Ale diky, aspon to maji pripadne juniori rovnou na dlani. ;-)
Btw…
#[Requires(methods: ‚POST‘, ajax: true)]
public function actionDelete(int $id)
{
…
}
tady me napada, ze by v tom Required mohlo byt neco jak ma Symfony
v Route
requirements={„id“=„\d+“}
a to by mohlo hodit 404-ku pokud to neuvedu v url.
Husty by bylo, kdyby to rovnou hazelo ActiveRow a v pripade, ze to ma wrong id,
tak 404-ku.
Takhle to hodi TypeError jestli se nepletu, kdyz v URL nebude uvedeno id.
- m.brecher
- Generous Backer | 863
@ViPEr*CZ*
Takhle to hodi TypeError jestli se nepletu, kdyz v URL nebude uvedeno id.
Ve starších verzích Nette presenter TypeError vyhazoval v případě, že v metodě akci bylo požadováno int $id a v url id nebylo. To se ale změnilo cca před 2 lety a dnes presentery v této situaci vyhazují 404.
- David Grudl
- Nette Core | 8218
Pokud nejsou splněné podmínky, chybí povinné parametry, parametry nejsou požadovaného typu (například int), tak to hodí 40× chybu, nikdy ne 500.
Je možné, že tam někdy byla krátce nějaká chyba a že to hodilo 500, ale to samozřejmě nebyl záměr.
Regulární výrazy se dají psát v routách, tam je to hodně užitečné. U action/render/handle metod se dají používat jen datové typy (pro zajímavost už od první verze Nette, dřív než to mělo PHP :-) ). Nikdy jsem v praxi nenarazil na situaci, že by se mi hodilo argument kontrolovat podle regulárního výrazu, proto to v Nette není.
U číselných hodnot může být užitečné přidat omezení na kladná, nezáporná apod. Ale zase, v praxi nenarážím na situace, že by to bylo skutečně užitečné. Protože ta kontrola proběhne stejně následně, například při pokusu čtení ID z databáze, takže by to fungovalo stejně s případnou anotací jako bez ní.
- David Grudl
- Nette Core | 8218
Miellap napsal(a):
Bylo by skvělé, kdyby se toto chování rozšířilo ještě o omezení komponent v akcích presenteru. Něco jako:
#[Requires(components: ['postForm'])] public function actionPost() { ... }
Rozumím kam míříš, ale tohle chce lépe promyslet. Jednak by to asi chtělo jinak pojmenovaný atribut, protože nechceme říct, že akce vyžaduje komponentu, ale naopak, že komponenta je povolena pouze v této akci. Tím vlastně by se měla stát automaticky zakázaná v jakékoliv jiné, i takové, která nemá žádnou metodu render/view.
Mimochodem, lze tento atribut využít i jinde než v Presenterech? Například handle metodách u komponent?
Jo, u komponent lze atributovat tyto metody:
- handle<Name>()
- createComponent<Name>()
- Marek Bartoš
- Nette Blogger | 1260
Co třeba #[ForAction(['default'])]
nad
createComponent*()
? Komponent může být v presenteru hodně a
lépe se mi bude kontrolovat nad komponentou pro jakou je akci, než naopak.
Pro obecné komponenty, které jsou v base presenteru nebo pro komponenty
přidávané dynamicky (přetížením createComponent()
) by
explicitní seznam všech povolených komponent byl dost nepoužitelný.
Striktně pro všechny createComponent*()
může atribut vynutit
jednoduché phpstan pravidlo. A aby takové pravidlo nebylo příliš
omezující, tak by mohlo fungovat #[ForAction(all: true)]
pro
povolení všech akcí. To by efektivně fungovalo stejně, jako by atribut
vůbec nebyl přidán.
Editoval Marek Bartoš (17. 4. 17:38)
- David Grudl
- Nette Core | 8218
@MarekBartoš od toho je právě
#[Requires(actions: 'default')]
nad
createComponent*()
.
Pokud si přetěžuju createComponent()
, tak myslím, že si
i snadno ohlídám aktuální akci.
- Miellap
- Člen | 12
David Grudl napsal(a):
Miellap napsal(a):
Bylo by skvělé, kdyby se toto chování rozšířilo ještě o omezení komponent v akcích presenteru. Něco jako:
#[Requires(components: ['postForm'])] public function actionPost() { ... }
Rozumím kam míříš, ale tohle chce lépe promyslet. Jednak by to asi chtělo jinak pojmenovaný atribut, protože nechceme říct, že akce vyžaduje komponentu, ale naopak, že komponenta je povolena pouze v této akci. Tím vlastně by se měla stát automaticky zakázaná v jakékoliv jiné, i takové, která nemá žádnou metodu render/view.
Mimochodem, lze tento atribut využít i jinde než v Presenterech? Například handle metodách u komponent?
Jo, u komponent lze atributovat tyto metody:
- handle<Name>()
- createComponent<Name>()
Je pravda, že ten název může být zavádějící (uvažovala jsem nad tím tak, že akce potřebuje zmíněné komponenty aby se načetla správně). Vyřešila jsem to dočasně vlastním atributem s názvem Allow, který lze volat buď nad presenterem nebo zmíněnou akcí. Vypadá to nějak takto:
#[Allow(action: 'post', components: 'postForm')]
#[Allow(components: ['otherForm', 'otherComponent'], handles: 'delete')]
class SettingPresenter extends Presenter
{
#[Allow(components: 'gridComponent')]
public function actionGrid(): void
{
}
}
Pokud neuvedu u atributu Allow u presenteru akci jsou uvedené komponenty dostupné všem akcím presenteru (v routeru mám vyřešeno, že nelze přistoupit k neexistujícím akcím) Pokud je atribut u akce, je dostupný jen pro ni. Obdobně mi to umožňuje povolit volat Handle metody jen z určitých akci.
Editoval Miellap (19. 4. 9:29)
- Miellap
- Člen | 12
David Grudl napsal(a):
Teď zbývá využít tento atribut pro vyžadování přihlášeného uživatele.
To už taky mám vyřešeno, pokud se ten atribut Allow zadá s parametrem resource, případně privilege, tak to ověřuje vůči autorizátoru a povolím tvoření dané komponenty z dané akce jen na základě tohoto omezení.
- David Grudl
- Nette Core | 8218
Jeden z možných příkladů využití. Na webech, zejména v administracích, by správně neměly být akce měnící stav serveru prováděny HTTP metodou GET. Koneckonců proto se jmenuje GET. Takže třeba pro mazání záznamů v tabulce bych měl použít odkazy s metodou POST (ideální by byla metoda DELETE, ale takový požadavek lze vytvořit jen JavaScriptem, takže se historicky používá POST).
Někde na začátku šablony si vytvořím pomocný formulář s id
postForm
:
@layout.latte
<form method="post" id="postForm"></form>
A ten pak budu využívat pro mazací odkazy. Místo elementu
<a>
použiju <button>
, který lze ale
klidně nastylovat jako klasický odkaz, třeba Bootstrap CSS k tomu má
třídy btn btn-link
.
admin.latte
<table>
<tr n:foreach="$posts as $post">
<td>{$post->title}</td>
<td>
<button class="btn btn-link" form="deleteForm" formaction="{link delete $post->id}">delete</button>
<!-- instead of <a n:href="delete $post->id">delete</a> -->
</td>
</tr>
</table>
Pro mazání se volá akce delete
. A tady právě využiju
atribut #[Requires]
abych zajistil, že jinak než POST metodou a
navíc jen ze stejné domény (obrana před CSRF) požadavek přijít
nemůže:
#[Requires(methods: 'POST', sameOrigin: true)]
public function actionDelete(int $id): void
{
$this->facade->deletePost($id);
$this->redirect('default');
}
Pokud by mazání realizoval signál, tak není potřeba uvádět
sameOrigin: true
, protože to mají všechny signály
defaultně:
#[Requires(methods: 'POST')]
public function handleDelete(int $id): void
{
$this->facade->deletePost($id);
$this->redirect('this');
}
- Miellap
- Člen | 12
@DavidGrudl
Zkouším teď použít atribut Requires ve své aplikaci a narazila jsem na
menší problém, kdy se v error message vypisuje celá cesta a tím se ukazuje
adresářová struktura aplikace. Bylo by možné v error message ponechat jen
"Method $method is not allowed"
?
Případně přidat do atributu nepovinný parametr
$errorMessage
?
Editoval Miellap (24. 4. 21:28)
- David Grudl
- Nette Core | 8218
@Miellap Mělo by tam být jen to co píšeš https://github.com/…ssPolicy.php#L120
- Miellap
- Člen | 12
David Grudl napsal(a):
@Miellap Mělo by tam být jen to co píšeš https://github.com/…ssPolicy.php#L120
$this->presenter->error(
"Method $method is not allowed by " . Reflection::toString($this->element),
Nette\Http\IResponse::S405_MethodNotAllowed,
);
Ta cesta se zobrazuje právě v té části
Reflection::toString($this->element).
. Nechci uživatelům
ukazovat ani částečně strukturu aplikace, proto bych ocenila možnost
vlastní hlášky.
- Marek Znojil
- Člen | 84
Tohle bych spíše viděl na práci error prezenteru, aby mi zobrazil hlášku jak chci dle kódu či typu výjimky.
- Miellap
- Člen | 12
Marek Znojil napsal(a):
Tohle bych spíše viděl na práci error prezenteru, aby mi zobrazil hlášku jak chci dle kódu či typu výjimky.
Vím kam míříš, ale pokud bych chtěla pro jednotlivé errory různé, konkrétnější hlášky, podle toho co daný error způsobilo (například u error 404 chci různou hlášku podle toho, zda je chybná url nebo nebyl nalezen uživatel), dělalo by se toto rozlišení v error presenter obtížně, proto si raději nastavuji „správné“ chybové hlášky už v rámci aplikace. Hlavně při tvorbě API chci mít error hlášku přesnou, aby bylo hned jasné, co se pokazilo.
Samozřejmě si můžu vyhazovat vlastní výjimku, kterou v error presenteru rozpoznám a odchytím, ale to mi znemožňuje využití Requires atributu.
- David Grudl
- Nette Core | 8218
@Miellap ověřoval jsem to, a nic z toho co popisuješ se v Nette neděje, pokud se někde zobrazují cesty k souborům, musí to být ve tvém kódu.
Otevři kdyžtak nové vlákno, protože tohle opravdu s atributem Requires nesouvisí.