Link generation in emails with Nette 2.3

10 years ago by David Grudl  

Since Nette 2.3, there is a LinkGenerator tool, which allows you to generate links without the need to use presenters and still in a very comfy way. How to use it?

Let’s show that on an example of a class, which sends emails. Email code can look like this:

<title>Subject of message</title>

<p>Hello {$name} <a n:href="Homepage:">click here</a></p>

I will inject LinkGenerator to the class and assign it to $this->linkGenerator property.

class MailSender
{
	/** @var Nette\Application\LinkGenerator */
	private $linkGenerator;

	function __construct(Nette\Application\LinkGenerator $generator)
	{
		$this->linkGenerator = $generator;
	}

Actual sending of the email will be done by sendEmail method, which we create the Latte object:

function sendEmail()
{
	$latte = new Latte\Engine;
	$latte->setTempDirectory(...);
	...
}

It’s appropriate to set a temporary directory for Latte to prevent repeated compilation of the template. But it’s not necessary if you are sending a few emails a day, or lots of emails in one request (meant by one Latte object), because the compilation is very fast. Where to get the path to the temp directory? We can inject it, but there is a smarter way – inject object (called factory), which will be able to create already configured Latte:

/** @var Nette\Bridges\ApplicationLatte\ILatteFactory */
private $latteFactory;

// $latteFactory is injected through the constructor

function sendEmail()
{
	$latte = $this->latteFactory->create();

	// install macros {link} and n:href to $latte
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());

	// and generate HTML email
	$html = $latte->renderToString(__DIR__ . '/email.latte', [
		'name' => $order->getName(), // variables to the template
		....
	]);

	// and send it, see below
}

But there is one more alternative way – we can create object $template known from presenter, which will contain variables like $basePath etc. and also configured Latte. To do that, I will change latteFactory to templateFactory:

/** @var Nette\Application\UI\ITemplateFactory */
private $templateFactory;

// inject $templateFactory through constructor

function sendEmail()
{
	$template = $this->templateFactory->createTemplate();
	$template->name = $order->getName();
	$template->setFile(__DIR__ . '/email.latte');
	...
}

It’s similar to using $this->createTemplate() inside of a presenter or component. The rest of sendEmail method will look like this:

	...
	$mail = new Nette\Mail\Message;
	$mail->addTo($order->email);
	$mail->setHtmlBody($html); // or setHtmlBody($template)

	$mailer->send($mail);   // $mailer is injected through constructor
}

Now, the last step! Involve LinkGenerator into this. It’s easy:

So if you use latteFactory:

		$params = array(
			'name' => $order->getName(),
			...
		);
		$latte->addProvider('uiControl', $this->linkGenerator);
		$html = $latte->renderToString(__DIR__ . '/email.latte', $params);

or

		$template->getLatte()->addProvider('uiControl', $this->linkGenerator);

Now, you can use {link} or n:href macros.

LinkGenerator generates all links as absolute URLs, including http://example.com. It also understands modules absolutely, so do not start the link with : or //, it is not necessary and it will not even understand it. If the router uses relative paths, and this is very often, the generator takes the domain from the current HTTP request. But there is no HTTP request in CLI, of course. So we can simulate it in the bootstrap:

$configurator->addServices([
	'http.request' => new Nette\Http\Request(new Nette\Http\UrlScript('http://example.com')),
]);

(Thanks to Jiří Zralý for translation.)

Comments

  1. There is potential catch in this tutorial as sometimes there is need to add service not on configurator but on container (for example when the URL is based on the parameters in config).

    Using $container->removeService and $container->addService seems sufficient.

    8 years ago
  2. In code snippet, where you are injecting “linkGenerator”, there are missing underscores just in front of “construct” method. This could make newbies like me a bit confused :)

    8 years ago

Sign in to submit a comment