Jak zavolat přesměrování mimo presenter

fordo.pytlik
Člen | 26
+
0
-

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
+
0
-

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
+
0
-

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)