1
\$\begingroup\$

I'm creating an event loop for an asynchronous event execution library for PHP in PHP. Note that this is for a library and not an application. My event loop is just that -- a simple event loop that doesn't know how to poll / wait for anything specific. It just executes callbacks in order.

Events being waited for are abstracted out of the event loop (unlike most implementations I've seen) and are instead provided by a handful of event devices, which provide the ability to poll for that device's kind of event.

Now, the event loop should not depend on specific devices and instead should just have a list of devices it should be polling. The when and how is up to the event loop's algorithms.

The problem I need to solve is how to allow parts of a program (any part) to add a device to the event loop, only if there isn't one similar to it already added. Different pieces of code should ideally depend on a specialized device interface (like, say an IOStreamEventDeviceInterface) instead of depending on or having to provide a concrete implementation. In addition, there should really be only one instance of a device for each kind of device based on what it provides. e.g. there shouldn't be two device instances that both wait on I/O streams.

Below is my current solution, which is (sort of?) like a service locator or DI container. It just has a list of object instances, and when you want to retrieve one, you specify $type as the classname of an interface, with an optional concrete class to use if an instance implementing the given interface isn't found.

My question is, is this a horrible way to do this? Is this an unholy mutant of a service locator that should be burned at the stake? Even worse, I've been told that library code should never provide or depend on specific DI containers. Does this apply here, even though there could be multiple EventDeviceManager instances (one per event loop instance, really) and only code interested in fetching event devices will be using it?

/**
 * Manages a group of event devices on behalf of an event loop.
 */
class EventDeviceManager implements \IteratorAggregate
{
 /**
 * @var \SplObjectStorage A collection of event devices attached to this event loop.
 */
 protected $devices;
 /**
 * @var LoopInterface The event loop the device manager belongs to.
 */
 protected $loop;
 /**
 * Creates a new event device manager.
 *
 * @param LoopInterface $loop The event loop the device manager belongs to.
 */
 public function __construct(LoopInterface $loop)
 {
 $this->devices = new \SplObjectStorage();
 $this->loop = $loop;
 }
 /**
 * Gets the number of active event devices.
 *
 * @return int
 */
 public function activeDeviceCount()
 {
 $count = 0;
 foreach ($this->devices as $device) {
 if ($device->isActive()) {
 $count++;
 }
 }
 return $count;
 }
 /**
 * Attaches an event device instance to the event loop.
 *
 * @param EventDeviceInterface $device
 */
 public function attachDevice(EventDeviceInterface $device)
 {
 // attach the device...
 $this->devices->attach($device);
 // ...and set the device's loop context
 $device->setLoop($this->loop);
 }
 /**
 * Detaches an event device from the event loop.
 *
 * @param EventDeviceInterface $device
 */
 public function detachDevice(EventDeviceInterface $device)
 {
 // check if the device exists
 if ($this->devices->contains($device)) {
 // remove loop context
 $device->setLoop(null);
 $this->devices->detach($device);
 }
 }
 /**
 * Gets an attached event device instance of a given type, or creates a new
 * instance of one cannot be found.
 *
 * This is a convenience method that allows abstraction over event device
 * types. An interface can be given as `$type` and any device that implements
 * the interface could be returned. A concrete type can be specified as an
 * alternative to use.
 *
 * @param string $type The type of the event device.
 * @param string $defaultType The type to use if an instance of the given type cannot be found.
 * @return EventDeviceInterface An event device instance.
 *
 * @throws TypeException Thrown if a new instance of a type could not be created.
 */
 public function getDeviceOfType($type, $defaultType = null)
 {
 // find a device of the given type
 foreach ($this->devices as $device) {
 if ($device instanceof $type) {
 return $device;
 }
 }
 // instance not found
 // if no default type is specified, we will try to instantiate the given class
 if ($defaultType === null) {
 $defaultType = $type;
 }
 // check if the type exists
 if (!class_exists($defaultType) && !interface_exists($defaultType)) {
 throw new TypeException("Class or interface \"$defaultType\" does not exist.");
 }
 // check if the type is instantiable
 $class = new \ReflectionClass($defaultType);
 if ($class->isInstantiable()) {
 // attach & return a new instance
 $instance = $class->newInstance();
 $this->attachDevice($instance);
 return $instance;
 }
 // can't instantiate the type
 throw new TypeException("Cannot create instance of type \"$defaultType\".");
 }
 /**
 * Gets an iterator for looping over each event device.
 *
 * @return \Iterator
 */
 public function getIterator()
 {
 return new \IteratorIterator($this->devices);
 }
}

Here is how you would use such a class:

// get some manager from an event loop (who creates it)
$manager = $eventLoop->getDevices();
// attach a StreamEventDevice, which implements, lets say, IOEventDevice
$manager->attachDevice(new StreamEventDevice());
// ... somewhere else in code, we need an IOEventDevice instance
$deviceInstance = $manager->getDeviceOfType('IOEventDevice', 'StreamEventDevice');

Please let me know if this code and design smell of rotting fish, or if my fears are only imagined. Again, this isn't really about dependency injection.

See the rest of the project code here.

asked Jan 20, 2015 at 4:21
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Besides splitting getDeviceOfType into multiple functions (such as findDeviceOfType, deviceTypeExists and createDeviceOfType), I think the code looks alright.

Should attach be checking for devices of it's type in the collection and throwing an error if it exists?

Pimgd
22.5k5 gold badges68 silver badges144 bronze badges
answered Jan 20, 2015 at 8:29
\$\endgroup\$
4
  • \$\begingroup\$ Into which multiple functions should getDeviceOfType be splitted? \$\endgroup\$ Commented Jan 20, 2015 at 11:24
  • \$\begingroup\$ I'd probably go with findDeviceOfType, deviceTypeExists and createDeviceOfType \$\endgroup\$ Commented Jan 20, 2015 at 12:06
  • \$\begingroup\$ Perhaps attach should check for the device type, but not by interface, since a device could implement multiple interfaces which could cross over but be different than the interfaces some other device implements. \$\endgroup\$ Commented Jan 20, 2015 at 17:01
  • \$\begingroup\$ Ah, of course :) \$\endgroup\$ Commented Jan 21, 2015 at 11:21

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.