Login to bookmark this video
15.

Email Twig Layout

|

Share this awesome video!

|

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

This Chapter isn't quite ready...

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com

New feature time! I want to send a reminder email to customers 1 week before their booked trip. T minus 1 week to lift off people!

Symfony CLI Worker Issue

First though, we have a little problem with our Symfony CLI worker. Open .symfony.local.yaml. Our messenger worker is watching the vendor directory for changes. At least on some systems, there's just too many files in here to monitor and some weird things happen. No big deal: remove vendor/:

9 lines | .symfony.local.yaml
workers:
// ... lines 2 - 5
messenger:
// ... line 7
watch: ['config', 'src', 'templates']

And since we changed the config, jump to your terminal and restart the webserver:

symfony server:stop

And:

symfony serve -d

Email Layout

Our new booking reminder email will have a template very similar to the booking confirmation one. To reduce duplication, and keep our snazzy emails consistent, in templates/email/, create a new layout.html.twig template that all our emails will extend.

Copy the contents of booking_confirmation.html.twig and paste here. Now, remove the booking-confirmation-specific content and create an empty content block. I think it's fine to keep our signature here.

{% apply inky_to_html|inline_css(source('@styles/foundation-emails.css'), source('@styles/email.css')) %}
<container>
{% block content %}{% endblock %}
<row>
<columns>
<p>We can't wait to see you there,</p>
<p>Your friends at Universal Travel</p>
</columns>
</row>
</container>
{% endapply %}

In booking_confirmation.html.twig, up top here, extend this new layout and add the content block. Down below, copy the email-specific content and paste it inside that block. Remove everything else.

{% extends 'email/layout.html.twig' %}
{% block content %}
<row>
<columns>
<spacer size="40"></spacer>
<p class="accent-title">Get Ready for your trip to</p>
<h1 class="trip-name">{{ trip.name }}</h1>
<img
class="trip-image float-center"
src="{{ email.image('@images/%s.png'|format(trip.slug)) }}"
alt="{{ trip.name }}">
</columns>
</row>
<row>
<columns>
<p class="accent-title">Departure: {{ booking.date|date('Y-m-d') }}</p>
</columns>
</row>
<row>
<columns>
<button class="expanded rounded center" href="{{ url('booking_show', {uid: booking.uid}) }}">
Manage Booking
</button>
<button class="expanded rounded center secondary" href="{{ url('bookings', {uid: customer.uid}) }}">
My Account
</button>
</columns>
</row>
{% endblock %}

Let's make sure the booking confirmation email still works - and we have tests for that! Back in the terminal, run them with:

bin/phpunit

Green! That's a good sign. Let's be doubly sure by checking it in Mailtrap. In the app, book a trip... and check Mailtrap. I still looks fantastic!

Time to bang out the reminder email!

Booking Reminder Flag

After an email reminder is sent, we need to mark the booking so that we don't annoy the customer with multiple reminders. Let's add a new flag for this to the Booking entity.

In your terminal, run:

symfony make:entity Booking

Oops!

symfony console make:entity Booking

Add a new field called reminderSentAt, type datetime_immutable, nullable? Yes. This is a common pattern I use for these type of flag fields instead of a simple boolean. null means false and a date means true. It works the same, but gives us a bit more info.

Hit enter to exit the command.

In the Booking entity... here's our new property, and down here, the getter and setter.

Finding Bookings to Remind

Next, we need a way to find all bookings that need a reminder sent. Perfect job for BookingRepository! Add a new method called findBookingsToRemind(), return type: array. Add a docblock to show it returns an array of Booking objects:

68 lines | src/Repository/BookingRepository.php
// ... lines 1 - 12
class BookingRepository extends ServiceEntityRepository
{
// ... lines 15 - 51
/**
* @return Booking[]
*/
public function findBookingsToRemind(): array
{
// ... lines 57 - 65
}
}

Inside, return $this->createQueryBuilder(), alias b. Chain ->andWhere('b.reminderSentAt IS NULL'), ->andWhere('b.date <= :future'), ->andWhere('b.date > :now') filling in the placeholders with ->setParameter('future', new \DateTimeImmutable('+7 days')) and ->setParameter('now', new \DateTimeImmutable('now')). Finish with ->getQuery()->getResult():

68 lines | src/Repository/BookingRepository.php
// ... lines 1 - 12
class BookingRepository extends ServiceEntityRepository
{
// ... lines 15 - 54
public function findBookingsToRemind(): array
{
return $this->createQueryBuilder('b')
->andWhere('b.reminderSentAt IS NULL')
->andWhere('b.date <= :future')
->andWhere('b.date > :now')
->setParameter('future', new \DateTimeImmutable('+7 days'))
->setParameter('now', new \DateTimeImmutable('now'))
->getQuery()
->getResult()
;
}
}

Pending Reminder Booking Fixture

In AppFixtures, down here, we create some fake bookings. Add one that will for sure trigger a reminder email to be sent: BookingFactory::createOne(), inside, 'trip' => $arrakis, 'customer' => $clark and, this is the important part, 'date' => new \DateTimeImmutable('+6 days'):

95 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 10
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// ... lines 15 - 87
BookingFactory::createOne([
'trip' => $arrakis,
'customer' => $clark,
'date' => new \DateTimeImmutable('+6 days'),
]);
}
}

Clearly between now and 7 days from now.

"Migration"

We made changes to the structure of our database. Normally, we should be creating a migration... but, we aren't using migrations. So, we'll just force update the schema. In your terminal, run:

symfony console doctrine:schema:update --force

Then, reload the fixtures:

symfony console doctrine:fixture:load

That all worked, great!

Next, we'll create a new reminder email and a CLI command to send them!