ITemplate nevyžaduje __toString() ani renderToString()

petr.pavel
Člen | 504
+
+1
-

Objevil jsem koncepční problém v renderování šablony do stringu.

V dokumentaci je příklad:

<?php
class MailSender
{
...
	/** @var Nette\Application\UI\ITemplateFactory */
	private $templateFactory;

	public function sendEmail(): void
	{
		$template = $this->createTemplate();
...
		$mail = new Message;
		$mail->setHtmlBody($template);
	}

	protected function createTemplate(): Nette\Application\UI\ITemplate
	{
		$template = $this->templateFactory->createTemplate();
...
		return $template;
	}
}
?>

Vyžadujeme ITemplateFactory, které vrací instanci ITemplate. Tuto instanci předáváme metodě, která vyžaduje string. Tohle za určitých okolností funguje:

  1. používáme pouze TemplateFactory z Nette a nekonfigurujeme ji, aby nám vracela jinou třídu
  2. proto vrací \Nette\Bridges\ApplicationLatte\Template, která implementuje __toString
  3. nedeklarujeme strict_types=1

Nic moc. Jaké je ale řešení?
Problém (3) vyřeší ruční přetypování $mail->setHtmlBody((string) $template); ale tím nekončíme.

Varianta 1: nepoužívat interface, ale konkrétní třídy
Tím jdu ale proti SOLID a navíc závislost na TemplateFactory stejně nezajistí, že vrací Template. Nepovažuju za správné řešení.

Varianta 2: nadefinuju si vlastní interface a obálkové třídy, které nakonec vrátí interface, který vyžaduje implementaci __toString
IMyTemplateFactory extends ITemplateFactory, MyTemplateFactory extends TemplateFactory implements IMyTemplateFactory { public createTemplate(): IMyTemplate {...}}, IMyTemplate extends ITemplate { public function __toString(); }
To tady mám pro úplnost, taky nepovažuju za správné řešení, příliš mnoho „zbytečného“ psaní.

Varianta 3: vykašlu se na __toString a volám renderToString
Jenže to je to samé v bledě modrém – ITemplate také nevyžaduje existenci této metody

Varianta 4: do MailSender zkopíruju vnitřek Template::__toString

<?php
		try {
			return $this->latte->renderToString($this->file, $this->params);
...
?>

Předpokládám, že šablona má vlastnost Latte $latte, což v ITemplate opět není. Takže zase v bledě modrém to samé.

Varianta 5: změnit ITemplate, aby vyžadovala __toString

To mně omezuje, pokud v aplikaci nikdy renderování do stringu nepotřebuju. Asi nejmenší zlo, ne? Vždycky můžu zaplácnout stubem.

Co myslíte? Něco mi uniklo?

David Grudl
Nette Core | 7417
+
0
-

Ještě je možnost vyrenderovat do stringu pomocí

$str = Nette\Utils\Helpers::capture(function () use ($template) { $template->render(); });
petr.pavel
Člen | 504
+
0
-

David Grudl napsal(a):

Ještě je možnost vyrenderovat do stringu pomocí $str = Nette\Utils\Helpers::capture(function () use ($template) { $template->render(); })

Zajímavá technika, díky.
Je lepší než přidat renderToString anebo __toString do ITemplate?

Zkouším si představit ten příklad v dokumentaci:

<?php
		$mail = new Message;
		$renderedTemplate = Nette\Utils\Helpers::capture(function () use ($template) { $template->render(); })
		$mail->setHtmlBody($renderedTemplate);
?>
David Grudl
Nette Core | 7417
+
+1
-

Čistě technicky, do PHP 7.4 je nevýhoda __toString v tom, že v nich nelze vyhodit výjimky, zabily by tím aplikaci. Zase od 7.4 se dá to zachytávání napsat víc elegantně Nette\Utils\Helpers::capture(fn() => $template->render()).

Když by se přidávala metoda do rozhraní, připadalo by mi srozumitelnější $mail->setHtmlBody($template->renderToString); než $mail->setHtmlBody((string) $template);

Na druhou stranu, v PHP 8.0 se objeví rozhraní Stringable a union typy, takže bude možné upravit Mail, aby přijímal Stringable|string a mohlo by fungovat $mail->setHtmlBody($template);

Felix
Nette Core | 1072
+
0
-

David Grudl napsal(a):

Na druhou stranu, v PHP 8.0 se objeví rozhraní Stringable a union typy, takže bude možné upravit Mail, aby přijímal Stringable|string a mohlo by fungovat $mail->setHtmlBody($template);

To bude uz v prosinci 2020, to bychom mohli vydrzet. ;-)