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.

Episodes

# Title Duration
1 An Introduction to Symfony Events 06:54
2 Keep Constant, and Dispatch Events from Services 03:32
3 Symfony Event Subscriber Tutorial 07:28
4 Symfony Event Subscriber in a JSON API Example 03:20
5 Symfony Events - The Gotchas 08:07