Skip to content

Notify post author about new comments by sending email #421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 12, 2017
3 changes: 1 addition & 2 deletions app/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public function registerBundles()
new CodeExplorerBundle\CodeExplorerBundle(),
new AppBundle\AppBundle(),
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), // used for initial population of non-SQLite databases in production envs
// uncomment the following line if your application sends emails
// new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
];

// Some bundles are only used while developing the application or during
Expand Down
9 changes: 9 additions & 0 deletions app/Resources/translations/messages.en.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,15 @@
<target>Post deleted successfully!</target>
</trans-unit>

<trans-unit id="notification.comment_created">
<source>notification.comment_created</source>
<target>Your post received a comment!</target>
</trans-unit>
<trans-unit id="notification.comment_created.description">
<source>notification.comment_created.description</source>
<target><![CDATA[Your post "%title%" has received a new comment. You can read the comment by following <a href="%link%">this link</a>]]></target>
</trans-unit>

<trans-unit id="help.app_description">
<source>help.app_description</source>
<target><![CDATA[This is a <strong>demo application</strong> built in the Symfony Framework to illustrate the recommended way of developing Symfony applications.]]></target>
Expand Down
1 change: 1 addition & 0 deletions app/Resources/views/blog/post_show.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

{% for comment in post.comments %}
<div class="row post-comment">
<a name="comment_{{ comment.id }}"></a>
<h4 class="col-sm-3">
<strong>{{ comment.authorEmail }}</strong> {{ 'post.commented_on'|trans }}
{# it's not mandatory to set the timezone in localizeddate(). This is done to
Expand Down
12 changes: 6 additions & 6 deletions app/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ doctrine:
# stores options that change the application behavior and parameters.yml
# stores options that change from one server to another
#
# swiftmailer:
# transport: "%mailer_transport%"
# host: "%mailer_host%"
# username: "%mailer_user%"
# password: "%mailer_password%"
# spool: { type: memory }
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
4 changes: 2 additions & 2 deletions app/config/config_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ monolog:
# type: chromephp
# level: info

#swiftmailer:
# delivery_address: me@example.com
swiftmailer:
disable_delivery: true
11 changes: 4 additions & 7 deletions app/config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,9 @@ parameters:
# $ php bin/console doctrine:schema:create
# $ php bin/console doctrine:fixtures:load

# Uncomment these parameters if your application sends emails:
#
# mailer_transport: smtp
# mailer_host: 127.0.0.1
# mailer_user: ~
# mailer_password: ~
#
# If you don't use a real mail server, you can send emails via your Gmail account.
# see http://symfony.com/doc/current/cookbook/email/gmail.html
mailer_transport: smtp
mailer_host: 127.0.0.1
mailer_user: ~
mailer_password: ~
14 changes: 11 additions & 3 deletions app/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ services:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

# Event subscribers are similar to event listeners but they don't need to add
# a separate tag for each listened event. Instead, the PHP class of the event
# subscriber includes a method that returns the list of listened events.
app.comment_notification:
class: AppBundle\EventListener\CommentNotificationListener
arguments: ['@mailer', '@router', '@translator', '%app.notifications.email_sender%']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the app.notifications.email_sender parameter initialized ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, it is already part of the codebase although not used for notifications today

# The "method" attribute of this tag is optional and defaults to "on + camelCasedEventName"
# If the event is "comment.created" the method executed by default is "onCommentCreated()".
tags:
- { name: kernel.event_listener, event: comment.created, method: onCommentCreated }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have two questions about the onCommentCreated method name:

  1. When the method name is on + camelCasedEventName, the method parameter is optional. Should we add it anyway?
  2. Some people like method names that describe the listened event (e.g. on + EventName) and others prefer semantic method names that describe their purposes (e.g. notifyCommentAdded()). Which is your preference?


# Event subscribers are similar to event listeners but they don't need service tags.
# Instead, the PHP class of the event subscriber includes a method that returns
# the list of events listened by that class.
# See http://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber
app.console_subscriber:
class: AppBundle\EventListener\ConsoleEventSubscriber
Expand Down
16 changes: 16 additions & 0 deletions src/AppBundle/Controller/BlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@

use AppBundle\Entity\Comment;
use AppBundle\Entity\Post;
use AppBundle\Events;
use AppBundle\Form\CommentType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

Expand Down Expand Up @@ -96,6 +98,20 @@ public function commentNewAction(Request $request, Post $post)
$entityManager->persist($comment);
$entityManager->flush();

// When triggering an event, you can optionally pass some information.
// For simple applications, use the GenericEvent object provided by Symfony
// to pass some PHP variables. For more complex applications, define your
// own event object classes.
// See http://symfony.com/doc/current/components/event_dispatcher/generic_event.html
$event = new GenericEvent($comment);

// When an event is dispatched, Symfony notifies it to all the listeners
// and subscribers registered to it. Listeners can modify the information
// passed in the event and they can even modify the execution flow, so
// there's no guarantee that the rest of this controller will be executed.
// See http://symfony.com/doc/current/components/event_dispatcher.html
$this->get('event_dispatcher')->dispatch(Events::COMMENT_CREATED, $event);

return $this->redirectToRoute('blog_post', ['slug' => $post->getSlug()]);
}

Expand Down
96 changes: 96 additions & 0 deletions src/AppBundle/EventListener/CommentNotificationListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace AppBundle\EventListener;

use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
* Notifies post's author about new comments.
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
class CommentNotificationListener
{
/**
* @var \Swift_Mailer
*/
private $mailer;

/**
* @var TranslatorInterface
*/
private $translator;

/**
* @var UrlGeneratorInterface
*/
private $urlGenerator;

/**
* @var string
*/
private $sender;

/**
* Constructor.
*
* @param \Swift_Mailer $mailer
* @param UrlGeneratorInterface $urlGenerator
* @param TranslatorInterface $translator
* @param string $sender
*/
public function __construct(\Swift_Mailer $mailer, UrlGeneratorInterface $urlGenerator, TranslatorInterface $translator, $sender)
{
$this->mailer = $mailer;
$this->urlGenerator = $urlGenerator;
$this->translator = $translator;
$this->sender = $sender;
}

/**
* @param GenericEvent $event
*/
public function onCommentCreated(GenericEvent $event)
{
$comment = $event->getSubject();
$post = $comment->getPost();

$linkToPost = $this->urlGenerator->generate('blog_post', [
'slug' => $post->getSlug(),
'_fragment' => 'comment_'.$comment->getId(),
], UrlGeneratorInterface::ABSOLUTE_URL);

$subject = $this->translator->trans('notification.comment_created');
$body = $this->translator->trans('notification.comment_created.description', [
'%title%' => $post->getTitle(),
'%link%' => $linkToPost,
]);

// Symfony uses a library called SwiftMailer to send emails. That's why
// email messages are created instantiating a Swift_Message class.
// See http://symfony.com/doc/current/email.html#sending-emails
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setTo($post->getAuthorEmail())
->setFrom($this->sender)
->setBody($body, 'text/html')
;

// In app/config/config_dev.yml the 'disable_delivery' option is set to 'true'.
// That's why in the development environment you won't actually receive any email.
// However, you can inspect the contents of those unsent emails using the debug toolbar.
// See http://symfony.com/doc/current/email/dev_environment.html#viewing-from-the-web-debug-toolbar
$this->mailer->send($message);
}
}
32 changes: 32 additions & 0 deletions src/AppBundle/Events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace AppBundle;

/**
* This class defines the names of all the events dispatched in
* the Symfony Demo application. It's not mandatory to create a
* class like this, but it's considered a good practice.
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
final class Events
{
/**
* For the event naming conventions, see:
* http://symfony.com/doc/current/components/event_dispatcher.html#naming-conventions.
*
* @Event("Symfony\Component\EventDispatcher\GenericEvent")
*
* @var string
*/
const COMMENT_CREATED = 'comment.created';
}