/** * Resolve a parameter if it references other parameters. * * Works recursively. * * @param mixed $parameter Parameter value to be resolved. * @return mixed */ public function resolve($parameter) { // allow for deep resolving if (is_array($parameter)) { foreach ($parameter as $key => $value) { $parameter[$key] = $this->resolve($value); } return $parameter; } if (!is_string($parameter)) { return $parameter; } // only bother with resolving when there are at least two % $parameterLength = mb_strlen($parameter); $firstDelimeter = strpos($parameter, '%'); $secondDelimeter = strpos($parameter, '%', min((int) $firstDelimeter + 1, $parameterLength)); if ($firstDelimeter === false || $secondDelimeter === false || $firstDelimeter === $secondDelimeter) { return $parameter; } // special case when fully referencing another parameter, to avoid regex // but also handle cases where referencing an array parameter // (otherwise preg_replace_callback below will trigger array to string conversion) if ($firstDelimeter === 0 && $secondDelimeter === $parameterLength - 1) { $referenced = mb_substr($parameter, 1, -1); return $this->container->hasParameter($referenced) ? $this->container->getParameter($referenced) : $parameter; } $container = $this->container; $original = $parameter; $parameter = preg_replace_callback('#(%%|%)([\\w\\d_\\.]+)%#i', function ($matches) use($container, $original) { if ($matches[1] === '%%') { return '%' . $matches[2]; } $name = $matches[2]; if ($container->hasParameter($name)) { $param = $container->getParameter($name); // only scalar types can be referenced like that if (!is_scalar($param)) { throw new InvalidParameterException('Invalid parameter construction - cannot reference non-scalar type parameter in "' . $original . '".'); } return $container->getParameter($name); } return '%' . $name . '%'; }, $parameter); return $parameter; }
/** * Reroutes notifications from one service name to another. * * This is especially used when a notification was referring to its target by an alias and the alias's target * has just become known to the container. * * @param string $from Original notifications target. * @param string $to New notifications target. */ public function rerouteNotifications($from, $to) { if (!isset($this->notifications[$from])) { return; } // $to might also be an alias, so lets dig deeper $to = $this->container->resolveServiceName($to); foreach ($this->notifications[$from] as $notification) { $this->notifications[$to][] = $notification; } }
/** * Resolves function arguments to applicable arguments. * * This method will resolve all parameters in arguments as well as references to * other services (in the form of `@service_name`). * * @param array|mixed $argument Either an array of arguments or a single argument. * @param string $selfServiceName If the arguments can reference "self service name", the name of * such references service should be passed here. It can be referenced * in arguments as "@=". Default: `null`. * @return array|mixed */ public function resolve($argument, $selfServiceName = null) { // deeply resolve arguments if (is_array($argument)) { foreach ($argument as $i => $arg) { $argument[$i] = $this->resolve($arg, $selfServiceName); } return $argument; } // possible parameter argument if (is_string($argument)) { $argument = $this->parametersResolver->resolve($argument); } // if possible to reference self in arguments then do it // (reference by name instead of getting the service now) if ($selfServiceName !== null && $argument === '@') { $argument = '@' . $selfServiceName; } // if possible to reference self name in arguments then do it if ($selfServiceName !== null && $argument === '@=') { $argument = $selfServiceName; } // and maybe referencing a different service? if (is_string($argument) && mb_substr($argument, 0, 1) === '@') { $serviceName = mb_substr($argument, 1); $optional = mb_substr($argument, -1) === '?'; $serviceName = $optional ? mb_substr($serviceName, 0, -1) : $serviceName; try { $argument = $this->container->get($serviceName); } catch (ServiceNotFoundException $e) { if ($optional) { $argument = null; } else { throw new ServiceNotFoundException('Could not find service "' . $serviceName . '" when resolving a non-optional argument.', 0, $e); } } } return $argument; }
public function get($name) { $instance = parent::get($name); // if already loaded from cache, then don't do anything if ($this->loadedFromCache) { return $instance; } // if cache hasn't been loaded yet, then keep reference to this // resolved service so that on cache load we can deliver all // notifications to it $realName = $this->resolveServiceName($name); $this->preCacheServices[$realName] = array('name' => $realName, 'instance' => $instance); return $instance; }
/** * Instantiate the service and perform constructor injection if necessary. * * @param Service $service Service definition. * @return object */ protected function instantiateService(Service $service) { $name = $service->name; // if already resolved the definition, then just call the resolved closure factory if (isset($this->instantiateClosuresCache[$name])) { $instantiateClosure = $this->instantiateClosuresCache[$name]; return $instantiateClosure(); } // deal with closure services if ($service instanceof ClosureService) { $serviceClosure = $service->closure; return $serviceClosure($this->container); } // deal with object services if ($service instanceof ObjectService) { return $service->getInstance(); } // deal with factory services if ($service instanceof FactoryService) { try { $factoryServiceName = $this->parametersResolver->resolve($service->factoryService); $factoryServiceDefinition = $this->container->getDefinition($factoryServiceName); $factoryService = $this->container->get($factoryServiceName); return $this->callMethod($factoryServiceDefinition, $service->factoryMethod, $service->factoryArguments, $factoryService); } catch (Exception $e) { throw new InvalidServiceException('Could not instantiate service "' . $name . '" due to: ' . $e->getMessage(), 0, $e); } } // class can be defined as a parameter $class = $this->parametersResolver->resolve($service->class); if (!class_exists($class)) { throw new InvalidServiceException('Could not instantiate service "' . $name . '" because class ' . $class . ' was not found.'); } $instantiateClosure = $this->createInstantiationClosure($class, $service->arguments); // cache this closure $this->instantiateClosuresCache[$name] = $instantiateClosure; // and finally call it return $instantiateClosure(); }
public function testDumpServices() { $container = new Container(); $container->setParameter('collection.class', $this->collectionServiceClass); $container->register('simple', $this->simpleServiceClass); $container->register('simple.alias', array('alias' => 'simple')); $container->register('called_service', array('class' => $this->calledServiceClass, 'arguments' => array('asd', 3))); $container->register('simple_factory', $this->simpleFactoryClass); $container->register('simple_factory.product.one', array('factory' => array('@simple_factory', 'get'), 'aliases' => array('factoried_service'))); $container->register('collection', '%collection.class%'); $this->assertEquals(array('container' => array('name' => 'container', 'class' => 'Splot\\DependencyInjection\\Container'), 'service_container' => array('name' => 'service_container', 'alias' => 'container'), 'services_container' => array('name' => 'services_container', 'alias' => 'container'), 'di_container' => array('name' => 'di_container', 'alias' => 'container'), 'simple' => array('name' => 'simple', 'class' => $this->simpleServiceClass), 'simple.alias' => array('name' => 'simple.alias', 'alias' => 'simple'), 'called_service' => array('name' => 'called_service', 'class' => $this->calledServiceClass), 'simple_factory' => array('name' => 'simple_factory', 'class' => $this->simpleFactoryClass), 'simple_factory.product.one' => array('name' => 'simple_factory.product.one'), 'factoried_service' => array('name' => 'factoried_service', 'alias' => 'simple_factory.product.one'), 'collection' => array('name' => 'collection', 'class' => $this->collectionServiceClass)), $container->dump()); }
public function testEscapingParameterSign() { $container = new Container(); $container->setParameter('lorem', 'ipsum'); $container->setParameter('dolor', 'sit.amet'); $container->setParameter('adipiscit.elit', '%lorem%%dolor%'); $container->setParameter('lipsum', '%%lorem%%'); $container->setParameter('lipsum.com', 'it.%dolor%%%lorem%%'); $this->assertEquals('ipsumsit.amet', $container->getParameter('adipiscit.elit')); $this->assertEquals('%lorem%', $container->getParameter('lipsum')); $this->assertEquals('it.sit.amet%lorem%', $container->getParameter('lipsum.com')); }