Nette\Application\UI\Control::redirect() neumí přesměrovat jako presenter

m.brecher
Generous Backer | 758
+
0
-

Ahoj,

narazil jsem na nejasnost, jak funguje přesměrování komponenty. Testuji přesměrování komponenty na jiný presenter. Komponenta obsahuje formulář s jedním tlačítkem.

class MyComponent extends Nette\Application\UI\Control
{
    public function createComponentButton(): Nette\Application\UI\Form
    {
        $form = new Form;
        $form->addSubmit('button','Přesměrovat')->onClick[] = $this->buttonClick(...);
        return $form;
    }

    public function buttonClick(): void
    {
        $this->redirect(':Homepage:default');   // přesměrovává komponenta - tohle nefunguje

       // $this->presenter->redirect(':Homepage:default');   // přesměrovává presenter - tohle funguje
    }
}

Obdržel jsem chybovou hlášku:

Nette\InvalidArgumentException
Component name must be non-empty alphanumeric string, '' given.

Zkusil jsem přesměrovat presenter a to funguje. Nicméně bych si rád vyjasnil, jak přesměrování komponenty funguje, dokumentace přesměrování komponent nijak nevysvětluje.

Z chybové hlášky to vypadá, že metoda redirect() komponenty očekává jako parametr jméno komponenty (jiné ?). Asi na jiný presenter přesměrovat nejde, ale jde v rámci přesměrování na aktuální presenter a akci poslat signál jiné komponentě?

Děkuji za případné komentáře.

Editoval m.brecher (17. 9. 2022 23:25)

Bulldog
Člen | 110
+
+3
-

Pokud se nepletu, tak link generátor v komponentách nezná rodiče.
A tak by to taky mělo být.

Jeden z principů programování je, že máš programovat třídu k jejímu účelu a ne k účelu nadřízených. Respektive když tvoříš třeba formulář, tak by si jej měl vytvořit tak, aby mu bylo fuk, co se děje okolo, ale zajímal se jen o svou práci.

Pokud by třída věděla o svém nadřízeném (kdo ji používá), tak by s ním byla svázaná a nedala by se použít jinde, než právě v kompozici s touhle nadřazenou třídou a to je špatně.
Je to jako by sis chtěl udělat kalkulačku, která počítá jen v rukou konkrétního Jakuba.

Takže komponenty neví (v rámci odkazování – jinak ví, samozřejmě, presenter je do nich attachnutý, ale nemusí to nutně být jejich nadřízený) o nadřízených nic. Takže na ně logicky nemohou odkazovat.
Komponenty tedy odkazují na signály své/signály svých subkomponent.
Pattern pro psaní odkazů vypadá asi takto:
'(componentName:)*signalName'

Tedy následující odkazy budou směřovat na tyto místa: (pokud předpokládáme, že redirect je použit v komponentě)

/*
 * Aktuální komponenta a její signál page. Musí existovat funkce s názvem `handlePage`
 */
$this->redirect('page');
/*
 * Komponenta search uvnitř této komponenty. Musí být tvořena funkcí `createComponentSearch`
 * a musí obsahovat signál change, který tvoříme funkcí `handleChange`
 */
$this->redirect('search:change');
/*
 * Komponenta filter uvnitř této komponenty. Musí být tvořena funkcí `createComponentFilter`
 * komponenta filter uvnitř sebe musí mít komponentu page tvořenou pomocí `createComponentPage`
 * a konečně komponenta page musí obsahovat signál change, který tvoříme funkcí `handleChange`
 */
$this->redirect('filter:page:change');
// atd...

Z výše uvedeného plyne, že pokud uděláš odkaz který vypadá takto:
':Homepage:default'
tak to vezme první dvojtečku a snaží se ji to resolvnout jako název podkomponenty, podle výše uvedeného patternu, což má za následek to, že to jako název podkomponenty vezme prázdný řetězec, protože před dvojtečkou nic nemáš a vyhodí to tvůj error že název komponenty nemůže být prázdné jméno.

Tvůj druhý odkaz přes presenter samozřejmě funguje, jelikož presenter zná vše a umí správně redirectovat.
Použití je ale zcela nevhodné, jelikož je to přesně to, co jsem psal a to svázání třídy (ve tvém případě komponenty) s presenterem. Takto nemůžeš použít komponentu v aplikaci, kde třeba presentery nepoužíváš, nebo v aplikaci, kde nechceš přesměrovat, ale jen překreslit snippet, udělat nějaké další změny atp., jelikož stihneš v komponentě přesměrovat dřív, než by se další věci vykonaly…

Správné řešení je použití návrhového vzoru Observer, aby se prostě o redirect/překreslení/další side efekty postaral někdo jiný až na něj přijde řada.

Nejjedodušší implementace je použít přímo tu komponentu jako observer a prostě si předat nějaký callback/pole callbacků:

class MyComponent extends Nette\Application\UI\Control
{
	private array $onButtonSubmit = [];	// pole pro callbacky

	// nebo (callable $callback) pro starší PHPčka
	public function addButtonSubmitCallback(\Closure $callback): static
	{
		$this->onButtonSubmit[] = $callback;	// Přidáme callback do pole
		return $this;
	}

    public function createComponentButton(): Nette\Application\UI\Form
    {
        $form = new Form;
        $form->addSubmit('button','Přesměrovat')->onClick[] = $this->buttonClick(...);
        return $form;
    }

    public function buttonClick(): void
    {
		// tohle zavolá všechny callbacky, mezi nimiž může být klidně presenterovský
		// callback s přesměrováním, ale to, co je jejich náplní je komponentě jedno.
		// prostě si zavolá callbacky, které jí někdo dal a od koho jsou, nebo co dělají se nestará
		// důležité jen je, aby ten, kdo jí je předal je předal správně
        Nette\Utils\Arrays::invoke($this->onButtonSubmit);
    }
}

a pak v presenteru:

public function createComponentMyComponent(): MyComponent
{
	$component = /* tvorba komponenty ideálně továrnou */

	// pro starší PHP v tomto případě musí být funkce 'onMyComponentButtonClick' public
	//$component->addButtonSubmitCallback([$this, 'onMyComponentButtonClick']);
	// Od verze PHP 7
	//$component->addButtonSubmitCallback(\Closure::fromCallable([$this, 'onMyComponentButtonClick']));
	// Od PHP 8.1 'First class callable syntax'
	$component->addButtonSubmitCallback($this->onMyComponentButtonClick(...));

	return $component;
}

private function onMyComponentButtonClick(): void
{
	$this->redirect(':Homepage:default');		// tohle už je tvůj redirect

	// samozřejmě tento callback může dělat cokoliv a je to plně odstíněno od komponenty.
}
m.brecher
Generous Backer | 758
+
0
-

@Bulldog Díky moc za skvělý a komplexní komentář.. Ano, komponenta jak jsem ji napsal není dostatečně nezávislá a nelze ji obecně použít kdekoliv.

m.brecher
Generous Backer | 758
+
0
-

@DavidGrudl Ano, kapitolu o odkazech v komponentách v dokumentaci https://doc.nette.org/…eating-links#… jsem četl několikrát i včera, ale nevyvodil jsem z toho, že to platí i pro $component->redirect(). Text v dokumentaci:

„… značka {link} i metody komponent jako je link() a další považují cíl odkazu vždy za název signálu…“

je sice naprosto jasný, ale já vyhledával fulltextem přímo ve stránce „redirect(“ a proto jsem to minul.

Obecně je v dokumentaci lepší jednotlivé položky explicitně vyjmenovat, než obecně popsat, protože nováček v Nette to potřebuje po lopatě. Doplnil bych to do dokumentace i sám, ale budu si muset nastudovat, jak se do dokumentace přispívá.

Editoval m.brecher (18. 9. 2022 17:17)

David Grudl
Nette Core | 8136
+
+3
-

Je to normální git repozitar na https://github.com/nette/doc, asi by to šlo editovat přímo na Githubu

David Grudl
Nette Core | 8136
+
0
-

A zmin tam prosímte rovnou i další související metody.

m.brecher
Generous Backer | 758
+
0
-

@DavidGrudl

A zmin tam prosímte rovnou i další související metody.

jj, prostuduji to, vyzkouším a doplním – česky i anglicky

m.brecher
Generous Backer | 758
+
0
-

David Grudl napsal(a):

Je to normální git repozitar na https://github.com/nette/doc, asi by to šlo editovat přímo na Githubu

Opravuji, je to https://github.com/nette/docs