curl-easy / event dispatcher lib

This commit is contained in:
kremsy 2014-02-18 18:15:12 +01:00 committed by Steffen Schröder
parent 15f0164287
commit 244428a2fe
29 changed files with 2684 additions and 0 deletions

View File

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@ -0,0 +1,16 @@
CHANGELOG
=========
2.1.0
-----
* added TraceableEventDispatcherInterface
* added ContainerAwareEventDispatcher
* added a reference to the EventDispatcher on the Event
* added a reference to the Event name on the event
* added fluid interface to the dispatch() method which now returns the Event
object
* added GenericEvent event class
* added the possibility for subscribers to subscribe several times for the
same event
* added ImmutableEventDispatcher

View File

@ -0,0 +1,202 @@
<?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 Symfony\Component\EventDispatcher;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Lazily loads listeners and subscribers from the dependency injection
* container
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*/
class ContainerAwareEventDispatcher extends EventDispatcher
{
/**
* The container from where services are loaded
* @var ContainerInterface
*/
private $container;
/**
* The service IDs of the event listeners and subscribers
* @var array
*/
private $listenerIds = array();
/**
* The services registered as listeners
* @var array
*/
private $listeners = array();
/**
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Adds a service as event listener
*
* @param string $eventName Event for which the listener is added
* @param array $callback The service ID of the listener service & the method
* name that has to be called
* @param integer $priority The higher this value, the earlier an event listener
* will be triggered in the chain.
* Defaults to 0.
*
* @throws \InvalidArgumentException
*/
public function addListenerService($eventName, $callback, $priority = 0)
{
if (!is_array($callback) || 2 !== count($callback)) {
throw new \InvalidArgumentException('Expected an array("service", "method") argument');
}
$this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
}
public function removeListener($eventName, $listener)
{
$this->lazyLoad($eventName);
if (isset($this->listeners[$eventName])) {
foreach ($this->listeners[$eventName] as $key => $l) {
foreach ($this->listenerIds[$eventName] as $i => $args) {
list($serviceId, $method, $priority) = $args;
if ($key === $serviceId.'.'.$method) {
if ($listener === array($l, $method)) {
unset($this->listeners[$eventName][$key]);
if (empty($this->listeners[$eventName])) {
unset($this->listeners[$eventName]);
}
unset($this->listenerIds[$eventName][$i]);
if (empty($this->listenerIds[$eventName])) {
unset($this->listenerIds[$eventName]);
}
}
}
}
}
}
parent::removeListener($eventName, $listener);
}
/**
* @see EventDispatcherInterface::hasListeners
*/
public function hasListeners($eventName = null)
{
if (null === $eventName) {
return (Boolean) count($this->listenerIds) || (Boolean) count($this->listeners);
}
if (isset($this->listenerIds[$eventName])) {
return true;
}
return parent::hasListeners($eventName);
}
/**
* @see EventDispatcherInterface::getListeners
*/
public function getListeners($eventName = null)
{
if (null === $eventName) {
foreach (array_keys($this->listenerIds) as $serviceEventName) {
$this->lazyLoad($serviceEventName);
}
} else {
$this->lazyLoad($eventName);
}
return parent::getListeners($eventName);
}
/**
* Adds a service as event subscriber
*
* @param string $serviceId The service ID of the subscriber service
* @param string $class The service's class name (which must implement EventSubscriberInterface)
*/
public function addSubscriberService($serviceId, $class)
{
foreach ($class::getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->listenerIds[$eventName][] = array($serviceId, $params, 0);
} elseif (is_string($params[0])) {
$this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritDoc}
*
* Lazily loads listeners for this event from the dependency injection
* container.
*
* @throws \InvalidArgumentException if the service is not defined
*/
public function dispatch($eventName, Event $event = null)
{
$this->lazyLoad($eventName);
return parent::dispatch($eventName, $event);
}
public function getContainer()
{
return $this->container;
}
/**
* Lazily loads listeners for this event from the dependency injection
* container.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
*/
protected function lazyLoad($eventName)
{
if (isset($this->listenerIds[$eventName])) {
foreach ($this->listenerIds[$eventName] as $args) {
list($serviceId, $method, $priority) = $args;
$listener = $this->container->get($serviceId);
$key = $serviceId.'.'.$method;
if (!isset($this->listeners[$eventName][$key])) {
$this->addListener($eventName, array($listener, $method), $priority);
} elseif ($listener !== $this->listeners[$eventName][$key]) {
parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
$this->addListener($eventName, array($listener, $method), $priority);
}
$this->listeners[$eventName][$key] = $listener;
}
}
}
}

View File

@ -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 Symfony\Component\EventDispatcher\Debug;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TraceableEventDispatcherInterface
{
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*/
public function getCalledListeners();
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners();
}

View File

@ -0,0 +1,129 @@
<?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 Symfony\Component\EventDispatcher;
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
class Event
{
/**
* @var Boolean Whether no further event listeners should be triggered
*/
private $propagationStopped = false;
/**
* @var EventDispatcher Dispatcher that dispatched this event
*/
private $dispatcher;
/**
* @var string This event's name
*/
private $name;
/**
* Returns whether further event listeners should be triggered.
*
* @see Event::stopPropagation
* @return Boolean Whether propagation was already stopped for this event.
*
* @api
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*
* @api
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
/**
* Stores the EventDispatcher that dispatches this Event
*
* @param EventDispatcherInterface $dispatcher
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
*
* @api
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* Returns the EventDispatcher that dispatches this Event
*
* @return EventDispatcherInterface
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
*
* @api
*/
public function getDispatcher()
{
return $this->dispatcher;
}
/**
* Gets the event's name.
*
* @return string
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call.
*
* @api
*/
public function getName()
{
return $this->name;
}
/**
* Sets the event's name property.
*
* @param string $name The event name.
*
* @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call.
*
* @api
*/
public function setName($name)
{
$this->name = $name;
}
}

View File

@ -0,0 +1,185 @@
<?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 Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*
* @api
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = array();
private $sorted = array();
/**
* @see EventDispatcherInterface::dispatch
*
* @api
*/
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
$event->setDispatcher($this);
$event->setName($eventName);
if (!isset($this->listeners[$eventName])) {
return $event;
}
$this->doDispatch($this->getListeners($eventName), $eventName, $event);
return $event;
}
/**
* @see EventDispatcherInterface::getListeners
*/
public function getListeners($eventName = null)
{
if (null !== $eventName) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach (array_keys($this->listeners) as $eventName) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return $this->sorted;
}
/**
* @see EventDispatcherInterface::hasListeners
*/
public function hasListeners($eventName = null)
{
return (Boolean) count($this->getListeners($eventName));
}
/**
* @see EventDispatcherInterface::addListener
*
* @api
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName]);
}
/**
* @see EventDispatcherInterface::removeListener
*/
public function removeListener($eventName, $listener)
{
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (false !== ($key = array_search($listener, $listeners, true))) {
unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
}
}
}
/**
* @see EventDispatcherInterface::addSubscriber
*
* @api
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->addListener($eventName, array($subscriber, $params));
} elseif (is_string($params[0])) {
$this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* @see EventDispatcherInterface::removeSubscriber
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_array($params) && is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, array($subscriber, $listener[0]));
}
} else {
$this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners.
* @param string $eventName The name of the event to dispatch.
* @param Event $event The event object to pass to the event handlers/listeners.
*/
protected function doDispatch($listeners, $eventName, Event $event)
{
foreach ($listeners as $listener) {
call_user_func($listener, $event, $eventName, $this);
if ($event->isPropagationStopped()) {
break;
}
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*
* @param string $eventName The name of the event.
*/
private function sortListeners($eventName)
{
$this->sorted[$eventName] = array();
if (isset($this->listeners[$eventName])) {
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
}
}
}

View File

@ -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 Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
interface EventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
* @param Event $event The event to pass to the event handlers/listeners.
* If not supplied, an empty Event instance is created.
*
* @return Event
*
* @api
*/
public function dispatch($eventName, Event $event = null);
/**
* Adds an event listener that listens on the specified events.
*
* @param string $eventName The event to listen on
* @param callable $listener The listener
* @param integer $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*
* @api
*/
public function addListener($eventName, $listener, $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events he is
* interested in and added as a listener for these events.
*
* @param EventSubscriberInterface $subscriber The subscriber.
*
* @api
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param string|array $eventName The event(s) to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, $listener);
/**
* Removes an event subscriber.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners.
*
* @param string $eventName The name of the event
*
* @return array The event listeners for the specified event, or all event listeners by event name
*/
public function getListeners($eventName = null);
/**
* Checks whether an event has any registered listeners.
*
* @param string $eventName The name of the event
*
* @return Boolean true if the specified event has any listeners, false otherwise
*/
public function hasListeners($eventName = null);
}

View File

@ -0,0 +1,50 @@
<?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 Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows himself what events he is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
*/
public static function getSubscribedEvents();
}

View File

@ -0,0 +1,186 @@
<?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 Symfony\Component\EventDispatcher;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
/**
* Event subject.
*
* @var mixed usually object or callable
*/
protected $subject;
/**
* Array of arguments.
*
* @var array
*/
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object.
* @param array $arguments Arguments to store in the event.
*/
public function __construct($subject = null, array $arguments = array())
{
$this->subject = $subject;
$this->arguments = $arguments;
}
/**
* Getter for subject property.
*
* @return mixed $subject The observer subject.
*/
public function getSubject()
{
return $this->subject;
}
/**
* Get argument by key.
*
* @param string $key Key.
*
* @throws \InvalidArgumentException If key is not found.
*
* @return mixed Contents of array key.
*/
public function getArgument($key)
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('%s not found in %s', $key, $this->getName()));
}
/**
* Add argument to event.
*
* @param string $key Argument name.
* @param mixed $value Value.
*
* @return GenericEvent
*/
public function setArgument($key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Set args property.
*
* @param array $args Arguments.
*
* @return GenericEvent
*/
public function setArguments(array $args = array())
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*
* @param string $key Key of arguments array.
*
* @return boolean
*/
public function hasArgument($key)
{
return array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key.
*
* @throws \InvalidArgumentException If key does not exist in $this->args.
*
* @return mixed
*/
public function offsetGet($key)
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set.
* @param mixed $value Value.
*/
public function offsetSet($key, $value)
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key.
*/
public function offsetUnset($key)
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key.
*
* @return boolean
*/
public function offsetExists($key)
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->arguments);
}
}

View File

@ -0,0 +1,92 @@
<?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 Symfony\Component\EventDispatcher;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
/**
* The proxied dispatcher.
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* Creates an unmodifiable proxy for an event dispatcher.
*
* @param EventDispatcherInterface $dispatcher The proxied event dispatcher.
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
return $this->dispatcher->dispatch($eventName, $event);
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2014 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,25 @@
EventDispatcher Component
=========================
The Symfony2 EventDispatcher component implements the Mediator pattern in a
simple and effective way to make your projects truly extensible.
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
$dispatcher = new EventDispatcher();
$dispatcher->addListener('event_name', function (Event $event) {
// ...
});
$dispatcher->dispatch('event_name');
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/EventDispatcher/
$ composer.phar install
$ phpunit

View File

@ -0,0 +1,244 @@
<?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 Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Scope;
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ContainerAwareEventDispatcherTest extends \PHPUnit_Framework_TestCase
{
public function testAddAListenerService()
{
$event = new Event();
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', $event);
}
public function testAddASubscriberService()
{
$event = new Event();
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService');
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.subscriber', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
$dispatcher->dispatch('onEvent', $event);
}
public function testPreventDuplicateListenerService()
{
$event = new Event();
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
$dispatcher->dispatch('onEvent', $event);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testTriggerAListenerServiceOutOfScope()
{
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$scope = new Scope('scope');
$container = new Container();
$container->addScope($scope);
$container->enterScope('scope');
$container->set('service.listener', $service, 'scope');
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$container->leaveScope('scope');
$dispatcher->dispatch('onEvent');
}
public function testReEnteringAScope()
{
$event = new Event();
$service1 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$service1
->expects($this->exactly(2))
->method('onEvent')
->with($event)
;
$scope = new Scope('scope');
$container = new Container();
$container->addScope($scope);
$container->enterScope('scope');
$container->set('service.listener', $service1, 'scope');
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', $event);
$service2 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$service2
->expects($this->once())
->method('onEvent')
->with($event)
;
$container->enterScope('scope');
$container->set('service.listener', $service2, 'scope');
$dispatcher->dispatch('onEvent', $event);
$container->leaveScope('scope');
$dispatcher->dispatch('onEvent');
}
public function testHasListenersOnLazyLoad()
{
$event = new Event();
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$event->setDispatcher($dispatcher);
$event->setName('onEvent');
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$this->assertTrue($dispatcher->hasListeners());
if ($dispatcher->hasListeners('onEvent')) {
$dispatcher->dispatch('onEvent');
}
}
public function testGetListenersOnLazyLoad()
{
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$listeners = $dispatcher->getListeners();
$this->assertTrue(isset($listeners['onEvent']));
$this->assertCount(1, $dispatcher->getListeners('onEvent'));
}
public function testRemoveAfterDispatch()
{
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', new Event());
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
public function testRemoveBeforeDispatch()
{
$service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
}
class Service
{
public function onEvent(Event $e)
{
}
}
class SubscriberService implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'onEvent' => 'onEvent',
'onEvent' => array('onEvent', 10),
'onEvent' => array('onEvent'),
);
}
public function onEvent(Event $e)
{
}
}

View File

@ -0,0 +1,346 @@
<?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 Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EventDispatcherTest extends \PHPUnit_Framework_TestCase
{
/* Some pseudo events */
const preFoo = 'pre.foo';
const postFoo = 'post.foo';
const preBar = 'pre.bar';
const postBar = 'post.bar';
/**
* @var EventDispatcher
*/
private $dispatcher;
private $listener;
protected function setUp()
{
$this->dispatcher = new EventDispatcher();
$this->listener = new TestEventListener();
}
protected function tearDown()
{
$this->dispatcher = null;
$this->listener = null;
}
public function testInitialState()
{
$this->assertEquals(array(), $this->dispatcher->getListeners());
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddListener()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
$this->assertCount(2, $this->dispatcher->getListeners());
}
public function testGetListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener1->name = '1';
$listener2->name = '2';
$listener3->name = '3';
$this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
$this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
$this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
$expected = array(
array($listener2, 'preFoo'),
array($listener3, 'preFoo'),
array($listener1, 'preFoo'),
);
$this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
}
public function testGetAllListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener4 = new TestEventListener();
$listener5 = new TestEventListener();
$listener6 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->addListener('post.foo', $listener4, -10);
$this->dispatcher->addListener('post.foo', $listener5);
$this->dispatcher->addListener('post.foo', $listener6, 10);
$expected = array(
'pre.foo' => array($listener3, $listener2, $listener1),
'post.foo' => array($listener6, $listener5, $listener4),
);
$this->assertSame($expected, $this->dispatcher->getListeners());
}
public function testDispatch()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->dispatcher->dispatch(self::preFoo);
$this->assertTrue($this->listener->preFooInvoked);
$this->assertFalse($this->listener->postFooInvoked);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
$event = new Event();
$return = $this->dispatcher->dispatch(self::preFoo, $event);
$this->assertEquals('pre.foo', $event->getName());
$this->assertSame($event, $return);
}
public function testDispatchForClosure()
{
$invoked = 0;
$listener = function () use (&$invoked) {
$invoked++;
};
$this->dispatcher->addListener('pre.foo', $listener);
$this->dispatcher->addListener('post.foo', $listener);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(1, $invoked);
}
public function testStopEventPropagation()
{
$otherListener = new TestEventListener();
// postFoo() stops the propagation, so only one listener should
// be executed
// Manually set priority to enforce $this->listener to be called first
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
$this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo'));
$this->dispatcher->dispatch(self::postFoo);
$this->assertTrue($this->listener->postFooInvoked);
$this->assertFalse($otherListener->postFooInvoked);
}
public function testDispatchByPriority()
{
$invoked = array();
$listener1 = function () use (&$invoked) {
$invoked[] = '1';
};
$listener2 = function () use (&$invoked) {
$invoked[] = '2';
};
$listener3 = function () use (&$invoked) {
$invoked[] = '3';
};
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(array('3', '2', '1'), $invoked);
}
public function testRemoveListener()
{
$this->dispatcher->addListener('pre.bar', $this->listener);
$this->assertTrue($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('pre.bar', $this->listener);
$this->assertFalse($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('notExists', $this->listener);
}
public function testAddSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
}
public function testAddSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertEquals('preFoo2', $listeners[0][1]);
}
public function testRemoveSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testRemoveSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testRemoveSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testEventReceivesTheDispatcherInstance()
{
$dispatcher = null;
$this->dispatcher->addListener('test', function ($event) use (&$dispatcher) {
$dispatcher = $event->getDispatcher();
});
$this->dispatcher->dispatch('test');
$this->assertSame($this->dispatcher, $dispatcher);
}
public function testEventReceivesTheDispatcherInstanceAsArgument()
{
$listener = new TestWithDispatcher();
$this->dispatcher->addListener('test', array($listener, 'foo'));
$this->assertNull($listener->name);
$this->assertNull($listener->dispatcher);
$this->dispatcher->dispatch('test');
$this->assertEquals('test', $listener->name);
$this->assertSame($this->dispatcher, $listener->dispatcher);
}
/**
* @see https://bugs.php.net/bug.php?id=62976
*
* This bug affects:
* - The PHP 5.3 branch for versions < 5.3.18
* - The PHP 5.4 branch for versions < 5.4.8
* - The PHP 5.5 branch is not affected
*/
public function testWorkaroundForPhpBug62976()
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener('bug.62976', new CallableClass());
$dispatcher->removeListener('bug.62976', function () {});
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
}
}
class CallableClass
{
public function __invoke()
{
}
}
class TestEventListener
{
public $preFooInvoked = false;
public $postFooInvoked = false;
/* Listener methods */
public function preFoo(Event $e)
{
$this->preFooInvoked = true;
}
public function postFoo(Event $e)
{
$this->postFooInvoked = true;
$e->stopPropagation();
}
}
class TestWithDispatcher
{
public $name;
public $dispatcher;
public function foo(Event $e, $name, $dispatcher)
{
$this->name = $name;
$this->dispatcher = $dispatcher;
}
}
class TestEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
}
}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'pre.foo' => array('preFoo', 10),
'post.foo' => array('postFoo'),
);
}
}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => array(
array('preFoo1'),
array('preFoo2', 10)
));
}
}

View File

@ -0,0 +1,84 @@
<?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 Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Test class for Event.
*/
class EventTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Symfony\Component\EventDispatcher\Event
*/
protected $event;
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
protected $dispatcher;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->event = new Event();
$this->dispatcher = new EventDispatcher();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
$this->event = null;
$this->dispatcher = null;
}
public function testIsPropagationStopped()
{
$this->assertFalse($this->event->isPropagationStopped());
}
public function testStopPropagationAndIsPropagationStopped()
{
$this->event->stopPropagation();
$this->assertTrue($this->event->isPropagationStopped());
}
public function testSetDispatcher()
{
$this->event->setDispatcher($this->dispatcher);
$this->assertSame($this->dispatcher, $this->event->getDispatcher());
}
public function testGetDispatcher()
{
$this->assertNull($this->event->getDispatcher());
}
public function testGetName()
{
$this->assertNull($this->event->getName());
}
public function testSetName()
{
$this->event->setName('foo');
$this->assertEquals('foo', $this->event->getName());
}
}

View File

@ -0,0 +1,140 @@
<?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 Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Test class for Event.
*/
class GenericEventTest extends \PHPUnit_Framework_TestCase
{
/**
* @var GenericEvent
*/
private $event;
private $subject;
/**
* Prepares the environment before running a test.
*/
protected function setUp()
{
parent::setUp();
$this->subject = new \stdClass();
$this->event = new GenericEvent($this->subject, array('name' => 'Event'), 'foo');
}
/**
* Cleans up the environment after running a test.
*/
protected function tearDown()
{
$this->subject = null;
$this->event = null;
parent::tearDown();
}
public function testConstruct()
{
$this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
}
/**
* Tests Event->getArgs()
*/
public function testGetArguments()
{
// test getting all
$this->assertSame(array('name' => 'Event'), $this->event->getArguments());
}
public function testSetArguments()
{
$result = $this->event->setArguments(array('foo' => 'bar'));
$this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
$this->assertSame($this->event, $result);
}
public function testSetArgument()
{
$result = $this->event->setArgument('foo2', 'bar2');
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
$this->assertEquals($this->event, $result);
}
public function testGetArgument()
{
// test getting key
$this->assertEquals('Event', $this->event->getArgument('name'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetArgException()
{
$this->event->getArgument('nameNotExist');
}
public function testOffsetGet()
{
// test getting key
$this->assertEquals('Event', $this->event['name']);
// test getting invalid arg
$this->setExpectedException('InvalidArgumentException');
$this->assertFalse($this->event['nameNotExist']);
}
public function testOffsetSet()
{
$this->event['foo2'] = 'bar2';
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
}
public function testOffsetUnset()
{
unset($this->event['name']);
$this->assertAttributeSame(array(), 'arguments', $this->event);
}
public function testOffsetIsset()
{
$this->assertTrue(isset($this->event['name']));
$this->assertFalse(isset($this->event['nameNotExist']));
}
public function testHasArgument()
{
$this->assertTrue($this->event->hasArgument('name'));
$this->assertFalse($this->event->hasArgument('nameNotExist'));
}
public function testGetSubject()
{
$this->assertSame($this->subject, $this->event->getSubject());
}
public function testHasIterator()
{
$data = array();
foreach ($this->event as $key => $value) {
$data[$key] = $value;
}
$this->assertEquals(array('name' => 'Event'), $data);
}
}

View File

@ -0,0 +1,105 @@
<?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 Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $innerDispatcher;
/**
* @var ImmutableEventDispatcher
*/
private $dispatcher;
protected function setUp()
{
$this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
}
public function testDispatchDelegates()
{
$event = new Event();
$this->innerDispatcher->expects($this->once())
->method('dispatch')
->with('event', $event)
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->dispatch('event', $event));
}
public function testGetListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('getListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->getListeners('event'));
}
public function testHasListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('hasListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->hasListeners('event'));
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddListenerDisallowed()
{
$this->dispatcher->addListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddSubscriberDisallowed()
{
$subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
$this->dispatcher->addSubscriber($subscriber);
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveListenerDisallowed()
{
$this->dispatcher->removeListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveSubscriberDisallowed()
{
$subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
$this->dispatcher->removeSubscriber($subscriber);
}
}

View File

@ -0,0 +1,38 @@
{
"name": "symfony/event-dispatcher",
"type": "library",
"description": "Symfony EventDispatcher Component",
"keywords": [],
"homepage": "http://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/dependency-injection": "~2.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"autoload": {
"psr-0": { "Symfony\\Component\\EventDispatcher\\": "" }
},
"target-dir": "Symfony/Component/EventDispatcher",
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Symfony EventDispatcher Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,81 @@
<?php
namespace cURL;
class Collection
{
/**
* @var array Collection
*/
protected $data = array();
/**
* Converts current object to array
*
* @return array
*/
public function toArray()
{
return $this->data;
}
/**
* Sets value
*
* @param mixed $key Key
* @param mixed $value Value
*
* @return self
*/
public function set($key, $value = null)
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->data[$k] = $v;
}
} else {
$this->data[$key] = $value;
}
return $this;
}
/**
* Checks if key does exist
*
* @param mixed $key Key
*
* @return bool TRUE if exists, FALSE otherwise
*/
public function has($key)
{
return isset($this->data[$key]);
}
/**
* Returns value of $key, throws Exception if does not exist
*
* @param mixed $key Key
*
* @return mixed Value of key
*/
public function get($key)
{
if ($this->has($key)) {
return $this->data[$key];
} else {
throw new Exception('Key does not exist.');
}
}
/**
* Removes key
*
* @param mixed $key Key to remove
*
* @return self
*/
public function remove($key)
{
unset($this->data[$key]);
return $this;
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace cURL;
class Error extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace cURL;
use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
class Event extends SymfonyEvent
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace cURL;
class Exception extends \Exception
{
}

View File

@ -0,0 +1,68 @@
<?php
namespace cURL;
class Options extends Collection
{
/**
* @var array Array of cURL constants required for intelligent setters
*/
protected static $curlConstantsTable = array();
/**
* Applies options to Request object
*
* @param Request $request
*
* @return self
*/
public function applyTo(Request $request)
{
if (!empty($this->data)) {
curl_setopt_array($request->getHandle(), $this->data);
}
return $this;
}
/**
* Prepares array for intelligent setters
*
* @return void
*/
public static function loadCurlConstantsTable()
{
$constants = get_defined_constants(true);
$table = array();
foreach ($constants['curl'] as $key => $value) {
if (strpos($key, 'CURLOPT_') === 0) {
$key = str_ireplace(array('CURLOPT', '_'), '', $key);
$table[$key] = $value;
}
}
self::$curlConstantsTable = $table;
}
/**
* Intelligent setters
*
* @param string $name Function name
* @param array $args Arguments
*
* @return self
*/
public function __call($name, $args)
{
if (substr($name, 0, 3) == 'set' && isset($args[0])) {
if (empty(self::$curlConstantsTable)) {
self::loadCurlConstantsTable();
}
$const = strtoupper(substr($name, 3));
if (isset(self::$curlConstantsTable[$const])) {
$this->data[self::$curlConstantsTable[$const]] = $args[0];
return $this;
} else {
throw new Exception('Constant CURLOPT_'.$const.' does not exist.');
}
}
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace cURL;
use Symfony\Component\EventDispatcher\EventDispatcher;
class Request extends EventDispatcher implements RequestInterface
{
/**
* @var resource cURL handler
*/
protected $ch;
/**
* @var RequestsQueue Queue instance when requesting async
*/
protected $queue;
/**
* @var Options Object containing options for current request
*/
protected $options = null;
/**
* Create new cURL handle
*
* @param string $url The URL to fetch.
*
* @return void
*/
public function __construct($url = null)
{
if ($url !== null) {
$this->getOptions()->set(CURLOPT_URL, $url);
}
$this->ch = curl_init();
}
/**
* Closes cURL resource and frees the memory.
* It is neccessary when you make a lot of requests
* and you want to avoid fill up the memory.
*
* @return void
*/
public function __destruct()
{
if (isset($this->ch)) {
curl_close($this->ch);
}
}
/**
* Get the cURL\Options instance
* Creates empty one if does not exist
*
* @return Options
*/
public function getOptions()
{
if (!isset($this->options)) {
$this->options = new Options;
}
return $this->options;
}
/**
* Sets Options
*
* @param Options $options Options
*
* @return void
*/
public function setOptions(Options $options)
{
$this->options = $options;
}
/**
* Returns cURL raw resource
*
* @return resource cURL handle
*/
public function getHandle()
{
return $this->ch;
}
/**
* Get unique id of cURL handle
* Useful for debugging or logging.
*
* @return int
*/
public function getUID()
{
return (int)$this->ch;
}
/**
* Perform a cURL session.
* Equivalent to curl_exec().
* This function should be called after initializing a cURL
* session and all the options for the session are set.
*
* @return Response
*/
public function send()
{
if ($this->options instanceof Options) {
$this->options->applyTo($this);
}
$content = curl_exec($this->ch);
$response = new Response($this, $content);
$errorCode = curl_errno($this->ch);
if ($errorCode !== CURLE_OK) {
$response->setError(new Error(curl_error($this->ch), $errorCode));
}
return $response;
}
protected function prepareQueue()
{
if (!isset($this->queue)) {
$request = $this;
$this->queue = new RequestsQueue;
$this->queue->addListener(
'complete',
function ($event) use ($request) {
$request->dispatch('complete', $event);
}
);
$this->queue->attach($this);
}
}
public function socketPerform()
{
$this->prepareQueue();
return $this->queue->socketPerform();
}
public function socketSelect($timeout = 1)
{
if (!isset($this->queue)) {
throw new Exception('Cannot select without perform before.');
}
return $this->queue->socketSelect($timeout);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace cURL;
interface RequestInterface
{
public function getOptions();
public function setOptions(Options $options);
public function getUID();
public function socketPerform();
public function socketSelect($timeout);
public function send();
}

View File

@ -0,0 +1,235 @@
<?php
namespace cURL;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Countable;
class RequestsQueue extends EventDispatcher implements RequestsQueueInterface, Countable
{
/**
* @var Options Default options for new Requests attached to RequestsQueue
*/
protected $defaultOptions = null;
/**
* @var resource cURL multi handler
*/
protected $mh;
/**
* @var int Amount of requests running
*/
protected $runningCount = 0;
/**
* @var array Array of requests attached
*/
protected $queue = array();
/**
* @var array Array of requests running
*/
protected $running = array();
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->mh = curl_multi_init();
}
/**
* Destructor, closes curl_multi handler
*
* @return void
*/
public function __destruct()
{
if (isset($this->mh)) {
curl_multi_close($this->mh);
}
}
/**
* Returns cURL\Options object with default request's options
*
* @return Options
*/
public function getDefaultOptions()
{
if (!isset($this->defaultOptions)) {
$this->defaultOptions = new Options;
}
return $this->defaultOptions;
}
/**
* Overrides default options with given Options object
*
* @param Options $defaultOptions New options
*
* @return void
*/
public function setDefaultOptions(Options $defaultOptions)
{
$this->defaultOptions = $defaultOptions;
}
/**
* Get cURL multi handle
*
* @return resource
*/
public function getHandle()
{
return $this->mh;
}
/**
* Attach request to queue.
*
* @param Request $request Request to add
*
* @return self
*/
public function attach(Request $request)
{
$this->queue[$request->getUID()] = $request;
return $this;
}
/**
* Detach request from queue.
*
* @param Request $request Request to remove
*
* @return self
*/
public function detach(Request $request)
{
unset($this->queue[$request->getUID()]);
return $this;
}
/**
* Processes handles which are ready and removes them from pool.
*
* @return int Amount of requests completed
*/
protected function read()
{
$n = 0;
while ($info = curl_multi_info_read($this->mh)) {
$n++;
$request = $this->queue[(int)$info['handle']];
$result = $info['result'];
curl_multi_remove_handle($this->mh, $request->getHandle());
unset($this->running[$request->getUID()]);
$this->detach($request);
$event = new Event;
$event->request = $request;
$event->response = new Response($request, curl_multi_getcontent($request->getHandle()));
if ($result !== CURLE_OK) {
$event->response->setError(new Error(curl_error($request->getHandle()), $result));
}
$event->queue = $this;
$this->dispatch('complete', $event);
}
return $n;
}
/**
* Returns count of handles in queue
*
* @return int Handles count
*/
public function count()
{
return count($this->queue);
}
/**
* Executes requests in parallel
*
* @return void
*/
public function send()
{
while ($this->socketPerform()) {
$this->socketSelect();
}
}
/**
* Returns requests present in $queue but not in $running
*
* @return array Array of requests
*/
protected function getRequestsNotRunning()
{
return array_diff_key($this->queue, $this->running);
}
/**
* Download available data on socket.
*
* @return bool TRUE when there are any requests on queue, FALSE when finished
*/
public function socketPerform()
{
if ($this->count() == 0) {
throw new Exception('Cannot perform if there are no requests in queue.');
}
$notRunning = $this->getRequestsNotRunning();
do {
/**
* Apply cURL options to new requests
*/
foreach ($notRunning as $request) {
$this->getDefaultOptions()->applyTo($request);
$request->getOptions()->applyTo($request);
curl_multi_add_handle($this->mh, $request->getHandle());
$this->running[$request->getUID()] = $request;
}
$runningBefore = $this->runningCount;
do {
$mrc = curl_multi_exec($this->mh, $this->runningCount);
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
$runningAfter = $this->runningCount;
$completed = ($runningAfter < $runningBefore) ? $this->read() : 0;
$notRunning = $this->getRequestsNotRunning();
} while (count($notRunning) > 0);
return $this->count() > 0;
}
/**
* Waits until activity on socket
* On success, returns TRUE. On failure, this function will
* return FALSE on a select failure or timeout (from the underlying
* select system call)
*
* @param float $timeout Maximum time to wait
*
* @return bool
*/
public function socketSelect($timeout = 1)
{
if ($this->count() == 0) {
throw new Exception('Cannot select if there are no requests in queue.');
}
return curl_multi_select($this->mh, $timeout) !== -1;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace cURL;
interface RequestsQueueInterface
{
public function getDefaultOptions();
public function setDefaultOptions(Options $defaultOptions);
public function attach(Request $request);
public function detach(Request $request);
public function send();
public function socketPerform();
public function socketSelect($timeout);
}

View File

@ -0,0 +1,83 @@
<?php
namespace cURL;
class Response
{
protected $ch;
protected $error;
protected $content = null;
/**
* Constructs response
*
* @param Request $request Request
* @param string $content Content of reponse
*
* @return void
*/
public function __construct(Request $request, $content = null)
{
$this->ch = $request->getHandle();
if (is_string($content)) {
$this->content = $content;
}
}
/**
* Get information regarding a current transfer
* If opt is given, returns its value as a string
* Otherwise, returns an associative array with
* the following elements (which correspond to opt), or FALSE on failure.
*
* @param int $key One of the CURLINFO_* constants
*
* @return mixed
*/
public function getInfo($key = null)
{
return $key === null ? curl_getinfo($this->ch) : curl_getinfo($this->ch, $key);
}
/**
* Returns content of request
*
* @return string Content
*/
public function getContent()
{
return $this->content;
}
/**
* Sets error instance
*
* @param Error $error Error to set
*
* @return void
*/
public function setError(Error $error)
{
$this->error = $error;
}
/**
* Returns a error instance
*
* @return Error|null
*/
public function getError()
{
return isset($this->error) ? $this->error : null;
}
/**
* Returns the error number for the last cURL operation.
*
* @return int Returns the error number or 0 (zero) if no error occurred.
*/
public function hasError()
{
return isset($this->error);
}
}