Link generation in emails with Nette 2.3
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
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.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 :)
Sign in to submit a comment