Jak zavolat přesměrování mimo presenter
- fordo.pytlik
- Člen | 26
Ahoj!
řeším takový zapeklitý problém! Rád bych věděl názory někoho dalšího.
Mám nějakou entitu (například objednávku) a na základě stavu entity potřebuju uživatele přesměrovat na správný presenter (typicky nezaplacená objednávka na presenter, kde je platební brána). Běžně to může vypadat takle:
<?php
class FooPresenter extends BasePresenter
{
public function renderDefault(int $id)
{
$order = $this->orderRepository->getOrder($id);
$order->doSomeMagic();
if ($order->isPaid() {
$this->redirect('Payment:'); // platebbní brána, info o platbě apod.
} else {
$this->redirect('Success:'); // poděkování za objednávku apod.
}
}
}
?>
Ale jak se nám aplikace rozvíjí, tak podobných metod, jako je výše
zmíněná renderDefault()
máme v aplikaci několik zároveň ani
ta logika tam není takle hezky jednoduchá.
Když nad tím přemýšlím a zvažuji třeba nějakou větší refaktorizaci, tak mi ale toto rozdělení na presentery dává největší smysl. Na různých místech v aplikaci (tj. v různých prezenterech) může nastat změna entity a může znamenat přesměrování na jiný (různý) prezentery. Myslím si, že to je ta cesta, která vede k přehledné a udržitelné aplikaci. Představoval bych si nějakou takovou třídu, která dostane objednávku a vrátí, co se má uživateli teďka zobrazit.
Jeden z prvních nápadů mě napadlo, že bych této třídě předal presenter.
<?php
class OrderStatusRedirectServis1
{
private $presenter;
public function __construct(Nette\Application\UI\Presenter $presenter)
{
$this->persenter;
}
public function redirect(Order $order)
{
if ($order->isPaid()) {
$this->presenter->redirect('Paid:');
}
$this->presenter->redirect('success');
}
}
?>
Ale třída presenter není překvapivě zaregistrovaná v DI kontejneru. A (pokud tomu správně rozumím) já nechci nějaký konkrétní presenter, stačí mi ten, kde právě jsem… Nebo bych si mohl vytvořit jeden presenter přes který budu všechny requesty přesměrovávat, ale nepřijde mi to OK. (nebo se pletu?)
Asi bych mohl metodě redirect v třídě
OrderStatusRedirectServis1
předat přímo presenter:
<?php
class OrderStatusRedirect2
{
public function redirect(Order $order, Presenter $presenter)
{
if ($order->isPaid()) {
$presenter->redirect('Paid:');
}
$presenter->redirect('success');
}
}
?>
Ale to mi přijde taky ošklivý, předávat jako závislost Presenter. (na druhou stranu je to funkční…)
Taky mě napadlo, že by daná třída mohla vracet „něco“, co by pak
jen vzal presenter (na kterém se právě nacházím) a přesměroval na
základě toho, co třída vrátila. Takle jsem nějakou dobu fungoval s tím,
že třída vracela string
– to mi docela stačilo, ale nyní
mají různé stavy i různé parametry, takže je dobrý to nějak
rozšířit. – no vypadalo to asi takle:
<?php
class FooPresenter extends Presenter
{
public function renderDefault()
{
$redirectString = $orderStatusRedirect3->redirect($order); // vrátí string ve tvaru 'Paid:' nebo 'Success'
$this->redirect($redirectString, [
'id' => $order->getId(),
]);
}
}
?>
Takže by bylo vhodné, aby třída OrderStatusRedirect
vracela
např. Nette\Application\Request
nebo
Nette\Application\IResponse
a tohle je přesně to místo, kde moje
znalosti tak nějak končí a potřeboval bych trochu naťuknout.
Nette\Application\Request
Mám pocit, že by třída OrderStatusRedirect
měla vracet
právě Nette\Application\Request
– hlavně kůli tomu, že
vím, na co mám přesměrovat ve formátu module:presenter:action
+ parametry ['id' => $id, 'message' => ...]
. Takže ji
přesně můžu vytvořit. Ale by se App\Request vrátil do presenteru, tak jak
v presenteru přesmerovat na daný request?
Nette\Application\IResponse
Z druhého úhlu mi dává smysl právě App\IResponse, protože pokud by
třída vracela právě IResponse (ať už ForwardResponse, nebo
RedirectResponse) tak přesně vím, co s tím má presenter udělat –
$this->sendResponse($response)
a to je jasný. Ale jak zase
vytvořit RedirectResponse z tvaru module:presenter:action
?
Vypadá to, že jsem se docela rozepsal :-) nad problémem už nějakou dobu přemýšlím a rád bych vybral dobré řešení. Děkuji, že jsi to dočetl(a) až sem :-)
(to s tou objednávkou je jen příklad, ve skutečnosti to řeším na jiné entitě, ale příklad je závislý k mé doménové logice, objednávky známe asi tak nějak všichni)
Editoval fordo.pytlik (15. 1. 2021 10:33)
- dakur
- Člen | 493
Na různých místech v aplikaci (tj. v různých prezenterech) může nastat změna entity a může znamenat přesměrování na jiný (různý) prezentery.
Moc tomu nerozumím… Pokud v různých presenterech potřebuješ
přesměrovat na různé presentery, pak vůbec nepotřebuješ řešit sdílený
kód, ne? Prostě na tom kterém místě napíšeš
$this->redirect('Target:default')
a je to.
Pokud ti jde o tu sdílenou logiku před redirectem samotným, dá se vyčlenit do helperu a redirect už bude specifický pro každý presenter. Např.:
// presenter:
$params = CommonRedirectParams::generate($order);
$this->redirect('Target:default', $params);
Příp. tam můžeš strčit i ten redirect, ale to mi nepřijde čisté:
// presenter:
CommonRedirectHandler::redirect($order, $presenter);
// CommonRedirectHandler:
public static function redirect(Order $order, Presenter $presenter): void
{
$presenter->redirect($targetGeneratedFromOrderProbably, [
// ...
]);
}
Editoval dakur (18. 1. 2021 16:14)
- Lukes
- Silver Partner | 68
Osobně si myslím, že cokoliv spojovat s modelovými třidami tak, aby
řešili přesměrovaní, není vhodné. Ještě bych byl maximálně pro to,
aby vracela ten string "Presenter:akce"
, ale zase to zbytečně
model zadrátuje na nějaký presenter.
Myslím, že když už využíváme Nette, tak je vhodné využít eventy/události
ze SmartObjectu
.
use Nette;
/**
* @method void onSuccess(array $entity)
* @method void onError(array $entity)
*/
class Model
{
use Nette\SmartObject;
/** @var array<callable> */
public array $onSuccess = [];
/** @var array<callable> */
public array $onError = [];
public function methodXyz(): void
{
/** ... */
if ($success)
{
$this->onSuccess(['id' => '150']);
}
else
{
$this->onError(['id' => '150']);
}
}
}
Následně pak v Presnteru.
class XyzPresenter extends Nette\Application\UI\Presnter
{
/** @inject */
public Model $model;
public function actionDefault(): void
{
$this->model->onSuccess[] = fn(array $entity) => $this->redirect('Success:', $entity);
$this->model->onError[] = fn(array $entity) => $this->redirect('Error:', $entity);
$this->model->methodXyz();
}
}
Takle necháš logiku přesněrování mimo model a můžes si na to pak napojit i jiné věci, jako je třeba email/sms, flash message…
Mimochodem do těch callbacků na modelu jde zapisovat i přes DI kontejner v konfiguraci.
Editoval Lukes (19. 1. 2021 7:47)