4.1. Service Locator

THIS IS CONSIDERED TO BE AN ANTI-PATTERN!

Service Locator is considered for some people an anti-pattern. It violates the Dependency Inversion principle.Service Locator hides class’ dependencies instead of exposing them as you would do using the Dependency Injection. In case of changes of those dependencies you risk to break the functionality of classes which are using them, making your system difficult to maintain.

4.1.1. Purpose

To implement a loosely coupled architecture in order to get bettertestable, maintainable and extendable code. DI pattern and ServiceLocator pattern are an implementation of the Inverse of Control pattern.

4.1.2. Usage

With ServiceLocator you can register a service for a giveninterface. By using the interface you can retrieve the service and useit in the classes of the application without knowing its implementation.You can configure and inject the Service Locator object on bootstrap.

4.1.3. Examples

  • Zend Framework 2 uses Service Locator to create and share servicesused in the framework(i.e. EventManager, ModuleManager, all customuser services provided by modules, etc…)

4.1.4. UML Diagram

Alt ServiceLocator UML Diagram

4.1.5. Code

You can also find this code on GitHub

ServiceLocator.php

  1. <?php
  2.  
  3. namespace DesignPatterns\More\ServiceLocator;
  4.  
  5. class ServiceLocator
  6. {
  7. /**
  8. * @var array
  9. */
  10. private $services = [];
  11.  
  12. /**
  13. * @var array
  14. */
  15. private $instantiated = [];
  16.  
  17. /**
  18. * @var array
  19. */
  20. private $shared = [];
  21.  
  22. /**
  23. * instead of supplying a class here, you could also store a service for an interface
  24. *
  25. * @param string $class
  26. * @param object $service
  27. * @param bool $share
  28. */
  29. public function addInstance(string $class, $service, bool $share = true)
  30. {
  31. $this->services[$class] = $service;
  32. $this->instantiated[$class] = $service;
  33. $this->shared[$class] = $share;
  34. }
  35.  
  36. /**
  37. * instead of supplying a class here, you could also store a service for an interface
  38. *
  39. * @param string $class
  40. * @param array $params
  41. * @param bool $share
  42. */
  43. public function addClass(string $class, array $params, bool $share = true)
  44. {
  45. $this->services[$class] = $params;
  46. $this->shared[$class] = $share;
  47. }
  48.  
  49. public function has(string $interface): bool
  50. {
  51. return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
  52. }
  53.  
  54. /**
  55. * @param string $class
  56. *
  57. * @return object
  58. */
  59. public function get(string $class)
  60. {
  61. if (isset($this->instantiated[$class]) && $this->shared[$class]) {
  62. return $this->instantiated[$class];
  63. }
  64.  
  65. $args = $this->services[$class];
  66.  
  67. switch (count($args)) {
  68. case 0:
  69. $object = new $class();
  70. break;
  71. case 1:
  72. $object = new $class($args[0]);
  73. break;
  74. case 2:
  75. $object = new $class($args[0], $args[1]);
  76. break;
  77. case 3:
  78. $object = new $class($args[0], $args[1], $args[2]);
  79. break;
  80. default:
  81. throw new \OutOfRangeException('Too many arguments given');
  82. }
  83.  
  84. if ($this->shared[$class]) {
  85. $this->instantiated[$class] = $object;
  86. }
  87.  
  88. return $object;
  89. }
  90. }

LogService.php

  1. <?php
  2.  
  3. namespace DesignPatterns\More\ServiceLocator;
  4.  
  5. class LogService
  6. {
  7. }

4.1.6. Test

Tests/ServiceLocatorTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\More\ServiceLocator\Tests;
  4.  
  5. use DesignPatterns\More\ServiceLocator\LogService;
  6. use DesignPatterns\More\ServiceLocator\ServiceLocator;
  7. use PHPUnit\Framework\TestCase;
  8.  
  9. class ServiceLocatorTest extends TestCase
  10. {
  11. /**
  12. * @var ServiceLocator
  13. */
  14. private $serviceLocator;
  15.  
  16. public function setUp()
  17. {
  18. $this->serviceLocator = new ServiceLocator();
  19. }
  20.  
  21. public function testHasServices()
  22. {
  23. $this->serviceLocator->addInstance(LogService::class, new LogService());
  24.  
  25. $this->assertTrue($this->serviceLocator->has(LogService::class));
  26. $this->assertFalse($this->serviceLocator->has(self::class));
  27. }
  28.  
  29. public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet()
  30. {
  31. $this->serviceLocator->addClass(LogService::class, []);
  32. $logger = $this->serviceLocator->get(LogService::class);
  33.  
  34. $this->assertInstanceOf(LogService::class, $logger);
  35. }
  36. }