Symfony Event Subscriber in a JSON API Example
In the previous video we covered how to create an Event Subscriber, and configure it to listen for (or ahem, subscribe to) our own event. We saw how an Event Subscriber gives us a lot of freedom compared to an Event Listener.
In this video we will cover one way I use an Event Dispatcher with Custom Events.
We start with a controller action.
In this case we receive some JSON via a POST
request, and use this raw data to create Widgets in our code.
// src/AppBundle/Controller/WidgetController.php
/**
* Add a new Widget
*
* @Annotations\Post(path="/widget")
*
* @param Request $request
* @param EntityManagerInterface $em
* @param EventDispatcherInterface $eventDispatcher
* @param WidgetFactory $widgetFactory
*
* @return View|\Symfony\Component\Form\Form
*/
public function postAction(
Request $request,
EntityManagerInterface $em,
EventDispatcherInterface $eventDispatcher,
WidgetFactory $widgetFactory
)
{
$form = $this->createForm(
WidgetType::class,
$widgetFactory->create(),
[
'csrf_protection' => false,
]
);
$form->submit($request->request->all());
if (!$form->isValid()) {
return $form;
}
$widget = $form->getData();
$em->persist($widget);
$em->flush();
$eventDispatcher->dispatch(
Events::WIDGET__CREATED,
new WidgetCreatedEvent($widget)
);
$routeOptions = [
'id' => $widget->getId(),
];
return $this->routeRedirectView('get_widget', $routeOptions, Response::HTTP_CREATED);
}
The interesting part here for us is the dispatch
call:
$eventDispatcher->dispatch(
Events::WIDGET__CREATED,
new WidgetCreatedEvent($widget)
);
Note that this dispatch
call happens after the widget has been through the form, validated, and saved / persisted to the database.
As such, it should be a valid object, and should have an ID.
We've seen how to use Constants for our event names.
I tend to follow the same format:
{ENTITY}__{ACTION}
E.g.
WIDGET__UPDATED
WIDGET__DELETED
SPROCKET__CREATED
And have the associated constant strings as the same, but in dot notation, e.g. :
widget.updated
widget.deleted
sprocket.created
Of course do whatever you prefer.
We create a new WidgetCreatedEvent
to contain all the relevant information regarding this particular event.
The only really important thing here is the $widget
variable contents (essentially the Widget object itself) so we pass that in as a mandatory constructor argument.
The event object, otherwise, only has a getter:
<?php
// src/AppBundle/Event/WidgetCreatedEvent.php
namespace AppBundle\Event;
use AppBundle\Entity\Widget;
use Symfony\Component\EventDispatcher\Event;
class WidgetCreatedEvent extends Event
{
/**
* @var Widget
*/
private $widget;
public function __construct(Widget $widget)
{
$this->widget = $widget;
}
/**
* @return Widget
*/
public function getWidget(): Widget
{
return $this->widget;
}
}
This is a Symfony 3.3 project with autowiring and autoconfiguration, so all we need to do next is to create an Event Subscriber and let Symfony take care of the rest:
<?php
// /src/AppBundle/Event/Subscriber/WidgetSubscriber.php
namespace AppBundle\Event\Subscriber;
use AppBundle\Event\Events;
use AppBundle\Event\WidgetCreatedEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class WidgetSubscriber implements EventSubscriberInterface
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(
LoggerInterface $logger
)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return [
Events::WIDGET__CREATED => [
[
'writeLogEntry',
],
[
'indexWithElasticsearch'
]
],
];
}
public function writeLogEntry(WidgetCreatedEvent $event)
{
$this->logger->debug('Write log entry', [
'widget' => [
'id' => $event->getWidget()->getId()
]
]);
}
public function indexWithElasticsearch(WidgetCreatedEvent $event)
{
// for example
}
}
This is a completely made up example simply to show some possibilities.
Inject what you need, do what you want.