gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
The HTML content of our email will use this template... which is still totally static. For example, see this link going to #homepage
? That's just a placeholder. Normally in a template, we would use the {{ path() }}
function to generate a URL to the homepage route. The name of that route is... check out ArticleController
... there it is: the homepage route name is app_homepage
. So we would normally say path('app_homepage')
.
The problem is that this will generate a relative URL - it will literally generate href="/"
. But for an email, all paths must be absolute. To force that, change path()
to url()
.
<html lang="en"> | |
... lines 3 - 55 | |
<body> | |
<div class="body"> | |
<div class="container"> | |
<div class="header text-center"> | |
<a href="{{ url('app_homepage') }}"> | |
... line 61 | |
</a> | |
</div> | |
... lines 64 - 93 | |
</div> | |
</div> | |
</body> | |
</html> |
That's it! Symfony will detect the domain name - localhost:8000
while we're coding locally - and use that to prefix the URL.
Let's fix a few other URLs: for the link to create a new article, replace the hardcoded string with url()
and the name of that route, which if you looked in the app, is admin_article_new
.
<html lang="en"> | |
... lines 3 - 55 | |
<body> | |
<div class="body"> | |
<div class="container"> | |
... lines 59 - 63 | |
<div class="content"> | |
... lines 65 - 69 | |
<p class="block text-center"> | |
<a href="{{ url('admin_article_new') }}" class="btn">Get writing!</a> | |
</p> | |
... lines 73 - 83 | |
</div> | |
... lines 85 - 93 | |
</div> | |
</div> | |
</body> | |
</html> |
At the bottom, there's one more link to the homepage. Say {{ url('app_homepage') }}
.
<html lang="en"> | |
... lines 3 - 55 | |
<body> | |
<div class="body"> | |
<div class="container"> | |
... lines 59 - 63 | |
<div class="content"> | |
... lines 65 - 75 | |
<p class="block text-center"> | |
<a href="{{ url('app_homepage') }}" class="btn">Get reading!</a> | |
</p> | |
... lines 79 - 83 | |
</div> | |
... lines 85 - 93 | |
</div> | |
</div> | |
</body> | |
</html> |
Links, done! But there's one other path we need to fix: the path to this image. But... forget about emails for a minute. This project uses Webpack Encore to compile its assets: I have an assets/
directory at the root, an images
directory inside that, and an email/logo.png
file that I want to reference. You don't need to run Encore, but if you did, I've configured it to copy that file into a public/build/images/
directory. There it is: public/build/images/email/logo.66125a81.png
.
If you downloaded the starting code for the tutorial, you don't need to worry about running Encore... only because we ran it for you and included the final, built public/build
directory. I mean, you can run Encore if you want - you just don't need to because the built files are already there.
The point is, whether you're using Encore or not, the end goal is to generate an absolute URL to a file that lives somewhere in your public/
directory. To do that in Twig, we use the {{ asset() }}
function. Pass this build/images/email/logo.png
. Because we're using Encore, we don't need to include the version hash that's part of the real file: the asset function will add that automatically. Go team!
If you're not using Encore, it's the same process: just use asset()
then include the actual path to the physical file, relative to the public/
directory.
But... this leaves us with the same problem we had for the generated URLs! By default, the asset()
function generates relative URLs: they don't contain the domain name. To fix that, wrap this in another function: absolute_url()
.
<html lang="en"> | |
... lines 3 - 55 | |
<body> | |
<div class="body"> | |
<div class="container"> | |
<div class="header text-center"> | |
<a href="{{ url('app_homepage') }}"> | |
<img src="{{ absolute_url(asset('build/images/email/logo.png')) }}" class="logo" alt="SpaceBar Logo"> | |
</a> | |
</div> | |
... lines 64 - 93 | |
</div> | |
</div> | |
</body> | |
</html> |
And... done! Ready to try this? Move over to the site, go back, change the email address again... we're going to do this a lot... type a new password, wave a magic wand and... hit enter. Ok... no errors... a good sign!
Over in Mailtrap, it's already there! Oh, it looks so much better: we even have a working image and, if we hover over a link, the URL does contain our domain: localhost:8000
. This is even more obvious in the HTML source: everything has a full URL.
Woh, and... our email also has a text part! How did that happen? In the controller, we only called htmlTemplate()
- we removed our call to the text()
method. Well... thank you Mailer. If you set the HTML on an email but do not explicitly set the text, Symfony automatically adds it for you by calling strip_tags()
on your HTML. That's awesome.
Well... awesome... but not totally perfect: it included all the styles on top! Don't worry: we'll fix that soon... kinda on accident. But the bottom looks pretty great... with zero effort.
Next, the URLs and image paths in our email are now dynamic... but nothing else is! Any self-respecting email must have real data, like the name of the user... or their favorite color. Let's make the email truly dynamic by passing in variables. We'll also find out what other information is available for free from inside an email template.
Hey Marco!
Excellent question! We tackle this a bit later in the tutorial: https://symfonycasts.com/sc...
And also, the config needed for this has been slightly simplified: https://symfony.com/blog/ne... - you can use our approach in the tutorial (it works fine!) or adapt to this new config.
Cheers!
Hi,
some images like the logo in the email and the Article images are not loaded and I don't understand why.
All other images work.
I ran:
composer install
yarn install
yarn build
I'm using webpack encore for my other projects too, so I don't think it's related to my yarn/npm installation.
This is how the html looks like in the email:
<img src="https://127.0.0.1:8000/build/images/email.logo.png" class="logo" alt="SpaceBar Logo">
The exception is : No route found for "GET /build/images/email.logo.png"
My public folder contains the image tho I checked it : public/build/images/email/logo.66125a81.png
edit: the email logo was my fault a typo but the articles are still broken mybe its the twigextention/function?
Hey @Ecludio!
Hmm. Well first, good job debugging so far :).
For the article images that aren’t working, what does the final HTML look like? And what error do you get if you try those URLs directly on your browser?
Btw, there is some special logic to get these files set up correctly. When you load the fixtures, the ArticleFixtures.php file (in src/DataFixtures) does a, sort of, “fake” upload where it copies the image files into the public/. I’m not sure if this will help, but I wanted to highlight it.
Anyways, let me know what you find out!
Cheers!
Hi,
just to warn you that chapter 5 is not public, but chapter 6 is.
Thank you again for your work !
Hey John,
Hm, most probably the problem was in a different thing, maybe temporary video outage... depends on the error you saw. Anyway, glad to hear it works for you now.
Cheers!
Hey AymDev,
Ah, you're right! We meant "strip_tags()" but said "strip_slashes()". I fixed it in https://github.com/knpunive...
Thank you for reporting it!
Cheers!
Love these twig tips. What is the best method to retrieve the root directory from within my command class? I could use DI to inject the KernelInterface and use `$this->kernel->getRootDir()`, or I can alternatively inject ParameterBagInterface and use `$this->parameterInterface->get('kernel.project_dir')`. I'm guessing these are pretty equivalent, but I don't know if there is a preferred or better way??
Hey Ernest H.
Yes there is another way, and we think it's best way to do such things. You can autowire using DI, in services.yaml
under bind
section you can define something like project_dir: '%kernel.project_dir%'
and then feel free to use it in any constructor like:
public function __construct(string $project_dir) {
$this->projectDir = $project_dir;
}
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"aws/aws-sdk-php": "^3.87", // 3.110.11
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-snappy-bundle": "^1.6", // v1.6.0
"knplabs/knp-time-bundle": "^1.8", // v1.9.1
"league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
"league/flysystem-cached-adapter": "^1.0", // 1.0.9
"league/html-to-markdown": "^4.8", // 4.8.2
"liip/imagine-bundle": "^2.1", // 2.1.0
"nexylan/slack-bundle": "^2.1,<2.2.0", // v2.1.0
"oneup/flysystem-bundle": "^3.0", // 3.1.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.4.1
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.3.4
"symfony/console": "^4.0", // v4.3.4
"symfony/flex": "^1.9", // v1.17.6
"symfony/form": "^4.0", // v4.3.4
"symfony/framework-bundle": "^4.0", // v4.3.4
"symfony/mailer": "4.3.*", // v4.3.4
"symfony/messenger": "4.3.*", // v4.3.4
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/security-bundle": "^4.0", // v4.3.4
"symfony/sendgrid-mailer": "4.3.*", // v4.3.4
"symfony/serializer-pack": "^1.0", // v1.0.2
"symfony/twig-bundle": "^4.0", // v4.3.4
"symfony/twig-pack": "^1.0", // v1.0.0
"symfony/validator": "^4.0", // v4.3.4
"symfony/web-server-bundle": "^4.0", // v4.3.4
"symfony/webpack-encore-bundle": "^1.4", // v1.6.2
"symfony/yaml": "^4.0", // v4.3.4
"twig/cssinliner-extra": "^2.12", // v2.12.0
"twig/extensions": "^1.5", // v1.5.4
"twig/inky-extra": "^2.12" // v2.12.0
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.2.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/browser-kit": "4.3.*", // v4.3.5
"symfony/debug-bundle": "^3.3|^4.0", // v4.3.4
"symfony/dotenv": "^4.0", // v4.3.4
"symfony/maker-bundle": "^1.0", // v1.13.0
"symfony/monolog-bundle": "^3.0", // v3.4.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.4
"symfony/profiler-pack": "^1.0", // v1.0.4
"symfony/var-dumper": "^3.3|^4.0" // v4.3.4
}
}
Hello there ,
I not understand this part, "That's it! Symfony will detect the domain name". What if I'm sending email using command line? Symfony will not detect the domain name, how to configure that in my .env.local? I think, maybe this part is missing in this article. For all the rest, excellent article! Regards.