* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Behat\Behat\HelperContainer; use Behat\Behat\HelperContainer\Exception\ServiceNotFoundException; use Behat\Behat\HelperContainer\Exception\WrongServicesConfigurationException; use Interop\Container\ContainerInterface; use ReflectionClass; use ReflectionMethod; /** * Built-in service container. * * @author Konstantin Kudryashov */ final class BuiltInServiceContainer implements ContainerInterface { /** * @var array */ private $schema; /** * @var array */ private $instances; /** * Initialises container using provided service configuration. * * @param array $schema */ public function __construct(array $schema) { $this->schema = $schema; } /** * {@inheritdoc} */ public function has($id) { return array_key_exists($id, $this->schema); } /** * {@inheritdoc} */ public function get($id) { if (!$this->has($id)) { throw new ServiceNotFoundException( sprintf('Service with id `%s` was not defined inside the `services` setting`.', $id), $id ); } return $this->instances[$id] = isset($this->instances[$id]) ? $this->instances[$id] : $this->createInstance($id); } /** * Creates an instance of given service. * * @param string $id * * @return mixed */ private function createInstance($id) { $schema = $this->getAndValidateServiceSchema($id); $reflection = new ReflectionClass($schema['class']); $arguments = $schema['arguments']; if ($factoryMethod = $this->getAndValidateFactoryMethod($reflection, $schema)) { return $factoryMethod->invokeArgs(null, $arguments); } return $reflection->newInstanceArgs($arguments); } /** * Gets and validates a service configuration for a service with given ID. * * @param string $id * * @throws WrongServicesConfigurationException * * @return array|string */ private function getAndValidateServiceSchema($id) { $schema = $this->schema[$id]; if (null === $schema) { $schema = array('class' => $id); } if (is_string($schema)) { $schema = array('class' => $schema); } $schema['class'] = $this->getAndValidateClass($id, $schema); $schema['arguments'] = $this->getAndValidateArguments($schema); return $schema; } /** * Gets and validates a class from schema. * * @param string $id * @param string|array $schema * * @return string */ private function getAndValidateClass($id, array $schema) { if (!isset($schema['class'])) { $schema['class'] = $id; } return $schema['class']; } /** * Gets and validates arguments from schema. * * @param array $schema * * @return array */ private function getAndValidateArguments(array $schema) { return isset($schema['arguments']) ? (array)$schema['arguments'] : array(); } /** * Gets and validates a factory method. * * @param ReflectionClass $reflection * @param array $schema * * @return null|ReflectionMethod */ private function getAndValidateFactoryMethod(ReflectionClass $reflection, array $schema) { if (!isset($schema['factory_method'])) { return null; } $factoryMethod = $schema['factory_method']; $this->assertFactoryMethodExists($reflection, $factoryMethod); $method = $reflection->getMethod($factoryMethod); $this->assertFactoryMethodIsStatic($method); return $method; } /** * Checks if factory method exists. * * @param ReflectionClass $class * @param string $methodName * * @throws WrongServicesConfigurationException */ private function assertFactoryMethodExists(ReflectionClass $class, $methodName) { if (!$class->hasMethod($methodName)) { throw new WrongServicesConfigurationException(sprintf( 'Factory method `%s::%s` does not exist.', $class->getName(), $methodName )); } } /** * Checks if factory method is static. * * @param ReflectionMethod $method * * @throws WrongServicesConfigurationException */ private function assertFactoryMethodIsStatic(ReflectionMethod $method) { if (!$method->isStatic()) { throw new WrongServicesConfigurationException(sprintf( 'Service factory methods must be static, but `%s::%s` is not.', $method->getDeclaringClass()->getName(), $method->getName() )); } } }