Skip to content

Instantly share code, notes, and snippets.

@pavarnos
Last active May 24, 2016 23:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pavarnos/3b984d45427b6bf13990e17103949e0f to your computer and use it in GitHub Desktop.
Save pavarnos/3b984d45427b6bf13990e17103949e0f to your computer and use it in GitHub Desktop.
<?php
use ISV\Service\Cache\CacheInterface;
use ISV\Service\Cache\FileCache;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Overrides the symfony event dispatcher so that we don't call addListener() 400 times for every request when 90%
* of requests never dispatch an event.
*
* When dispatch() is called, we addListener() first and then dispatch().
*
* Stole some ideas from ContainerAwareEventDispatcher
*/
class LazyEventDispatcher extends EventDispatcher
{
const CACHE_KEY = 'LazyEventDispatcher';
// array index in $listeners
const SERVICE = 0;
const METHOD = 1;
const PRIORITY = 2;
/** @var CacheInterface */
private $cache;
/** @var array eventName => array of ['service', 'method', 'priority'] one per listener */
private $listeners = [];
/** @var ContainerInterface */
private $container;
/**
* LazyEventDispatcher constructor.
* @param ContainerInterface $container
* @param CacheInterface $cache for unit testing: so we can inject a different cache if needed
*/
public function __construct(ContainerInterface $container, CacheInterface $cache = null)
{
$this->container = $container;
$this->cache = $cache;
}
/**
* @return array
*/
public function getLazyListeners()
{
if (empty($this->listeners)) {
$this->listeners = $this->getCache()->get(self::CACHE_KEY);
if (empty($this->listeners)) {
$this->listeners = [];
}
}
return $this->listeners;
}
/**
* save them to cache so we can load them later.
* called at compile time.
*/
public function saveLazyListeners()
{
ksort($this->listeners);
$this->getCache()->set(self::CACHE_KEY, $this->listeners);
}
/**
* Adds an event listener that listens on the specified events. Called by a container compiler class at build time.
*
* @param string $eventName The event to listen for
* @param string $serviceName usually a class name
* @param string $methodName eg onPersonDelete
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addLazyListener($eventName, $serviceName, $methodName, $priority = 0)
{
$this->listeners[$eventName][] = [
self::SERVICE => $serviceName,
self::METHOD => $methodName,
self::PRIORITY => $priority,
];
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
if ($this->hasListeners($eventName)) {
return parent::dispatch($eventName, $event);
}
$allListeners = $this->getLazyListeners();
if (!isset($allListeners[$eventName])) {
return parent::dispatch($eventName, $event);
}
// add them then dispatch
foreach ($allListeners[$eventName] as $handler) {
$service = $this->container->get($handler[self::SERVICE]);
$this->addListener($eventName, [$service, $handler[self::METHOD]], $handler[self::PRIORITY]);
}
return parent::dispatch($eventName, $event);
}
/**
* no need to clear the cache explicitly: it will be automatically rebuilt when the container is rebuilt
* @return CacheInterface
*/
private function getCache()
{
if (empty($this->cache)) {
$this->cache = new FileCache(CACHE_PATH);
}
return $this->cache;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment