Skip to content

Instantly share code, notes, and snippets.

@texdc
Last active August 8, 2017 15:20
Show Gist options
  • Save texdc/8373911 to your computer and use it in GitHub Desktop.
Save texdc/8373911 to your computer and use it in GitHub Desktop.
A paper-napkin sketch exploring the use of SRP decorators for ZF's service locator.

A use-case for events on a service locator is rare. The most obvious would be logging, but there may be others, good or bad. Still, this should illustrate a more flexible, extensible, and maintainable architecture based around the SRP and decorators.

Pros

  • Locators don't need access to the cross-cutting concerns anymore, reducing the number of dependencies and increasing maintainability and testability.
  • Handles cross-cutting concerns, that can make the code very complex otherwise, in a clean way.
  • All the concerns are easily composable and the result is a SOLID approach towards them.
  • The decorator pattern also allows us to add or remove concerns later at one central location without having to change the business code.
  • The locator can be tailored to the application itself and end up being very small and easily understandable.

Cons

  • We need some additional code and extra classes, which might be too much for small applications and the indirection of handling cross-cutting concerns might be confusing.
  • While other languages don’t need this because of their support for AOP and annotations (Spring for Java for example) this is necessary in PHP because we don’t have these features without a PECL extension.

HT: @beberlei

<?php
namespace Zend\Service\Locator;
use Zend\Event\Manager\ProviderInterface as EventManagerProvider;
use Zend\Event\Manager\ProviderTrait as EventManagerProviderTrait;
use Zend\Service\Locator\Event\ManagerInterface as LocatorEventManager;
use Zend\Service\LocatorInterface;
/**
* Decorates a service locator with events as a façade
*
* @see http://en.wikipedia.org/wiki/Facade_pattern
*/
class EventfulLocator implements NestedLocatorInterface, EventManagerProvider
{
use EventManagerProviderTrait;
use NestedLocatorTrait;
public function __construct(LocatorInterface $aLocator, LocatorEventManager $anEventManager)
{
$this->setNestedLocator($aLocator);
$this->setEventManager($anEventManager);
}
public function get(string $aServiceName)
{
$this->eventManager->preGet($aServiceName, $this->locator);
$service = $this->locator->get($aServiceName);
$this->eventManager->postGet($aServiceName, $service);
return $service;
}
// ...
}
<?php
namespace Zend\Service;
use Zend\Service\Locator;
final class LocatorFactory
{
public function __invoke(array $options) : LocatorInterface
{
return $this->build(new Locator\Configuration($options));
}
public function build(Locator\Configuration $aConfiguration) : LocatorInterface
{
$locator = new Locator\SimpleLocator($aConfiguration->getServiceMap());
if ($aConfiguration->isPeeringEnabled()) {
$locator = new Locator\PeeringLocator($locator);
}
if ($aConfiguration->areProxiesEnabled()) {
$locator = new Locator\ProxyLocator($locator);
}
if ($aConfiguration->isCacheEnabled()) {
$cacheManager = $locator->get($aConfiguration->getCacheManagerClass());
$locator = new Locator\CachingLocator($locator, $cacheManager);
}
if ($aConfiguration->areEventsEnabled()) {
$eventMangager = $locator->get($aConfiguration->getEventManagerClass());
$locator = new Locator\EventfulLocator($locator, $eventManager);
}
return $locator;
}
}
<?php
namespace Zend\Service\Locator\Event;
use Zend\Service\LocatorInterface;
interface ManagerInterface
{
/**
* Trigger a {@link PreGetEvent}
*
* @param string $aServiceName the requested service name
* @param LocatorInterface $aLocator the locator handling the request
*/
public function preGet(string $aServiceName, LocatorInterface $aLocator) : null;
/**
* Trigger a {@link PostGetEvent}
*
* @param string $aServiceName the requested service name
* @param object $aService the located service
*/
public function postGet(string $aServiceName, $aService) : null;
// ...
}
<?php
namespace Zend\Service\Locator;
use Zend\Service\LocatorInterface;
trait NestedLocatorTrait
{
use ProviderTrait {
getLocator as getNestedLocator;
}
private function setNestedLocator(LocatorInterface $aLocator) : null
{
if ($aLocator instanceof self) {
throw new Exception\RecursiveLocatorException(get_class($aLocator));
}
$this->setLocator($aLocator);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment