<?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Di; /** * Registry of instantiated objects, their names and the parameters used to build them */ class InstanceManager /* implements InstanceManagerInterface */ { /** * Array of shared instances * @var array */ protected $sharedInstances = array(); /** * Array of shared instances with params * @var array */ protected $sharedInstancesWithParams = array('hashShort' => array(), 'hashLong' => array()); /** * Array of class aliases * @var array key: alias, value: class */ protected $aliases = array(); /** * The template to use for housing configuration information * @var array */ protected $configurationTemplate = array( /** * alias|class => alias|class * interface|abstract => alias|class|object * name => value */ 'parameters' => array(), /** * injection type => array of ordered method params */ 'injections' => array(), /** * alias|class => bool */ 'shared' => true ); /** * An array of instance configuration data * @var array */ protected $configurations = array(); /** * An array of globally preferred implementations for interfaces/abstracts * @var array */ protected $typePreferences = array(); /** * Does this instance manager have this shared instance * @param string $classOrAlias * @return bool */ public function hasSharedInstance($classOrAlias) { return isset($this->sharedInstances[$classOrAlias]); } /** * getSharedInstance() */ public function getSharedInstance($classOrAlias) { return $this->sharedInstances[$classOrAlias]; } /** * Add shared instance * * @param object $instance * @param string $classOrAlias * @throws Exception\InvalidArgumentException */ public function addSharedInstance($instance, $classOrAlias) { if (!is_object($instance)) { throw new Exception\InvalidArgumentException('This method requires an object to be shared. Class or Alias given: ' . $classOrAlias); } $this->sharedInstances[$classOrAlias] = $instance; } /** * hasSharedInstanceWithParameters() * * @param string $classOrAlias * @param array $params * @param bool $returnFastHashLookupKey * @return bool|string */ public function hasSharedInstanceWithParameters($classOrAlias, array $params, $returnFastHashLookupKey = false) { ksort($params); $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { $hashValue = $this->createHashForValues($classOrAlias, $params); if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { return ($returnFastHashLookupKey) ? $hashKey . '/' . $hashValue : true; } } return false; } /** * addSharedInstanceWithParameters() * * @param object $instance * @param string $classOrAlias * @param array $params * @return void */ public function addSharedInstanceWithParameters($instance, $classOrAlias, array $params) { ksort($params); $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); $hashValue = $this->createHashForValues($classOrAlias, $params); if (!isset($this->sharedInstancesWithParams[$hashKey]) || !is_array($this->sharedInstancesWithParams[$hashKey])) { $this->sharedInstancesWithParams[$hashKey] = array(); } $this->sharedInstancesWithParams['hashShort'][$hashKey] = true; $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue] = $instance; } /** * Retrieves an instance by its name and the parameters stored at its instantiation * * @param string $classOrAlias * @param array $params * @param bool|null $fastHashFromHasLookup * @return object|bool false if no instance was found */ public function getSharedInstanceWithParameters($classOrAlias, array $params, $fastHashFromHasLookup = null) { if ($fastHashFromHasLookup) { return $this->sharedInstancesWithParams['hashLong'][$fastHashFromHasLookup]; } ksort($params); $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { $hashValue = $this->createHashForValues($classOrAlias, $params); if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { return $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue]; } } return false; } /** * Check for an alias * * @param string $alias * @return bool */ public function hasAlias($alias) { return (isset($this->aliases[$alias])); } /** * Get aliases * * @return array */ public function getAliases() { return $this->aliases; } /** * getClassFromAlias() * * @param string * @return string|bool * @throws Exception\RuntimeException */ public function getClassFromAlias($alias) { if (!isset($this->aliases[$alias])) { return false; } $r = 0; while (isset($this->aliases[$alias])) { $alias = $this->aliases[$alias]; $r++; if ($r > 100) { throw new Exception\RuntimeException( sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias) ); } } return $alias; } /** * @param string $alias * @return string|bool * @throws Exception\RuntimeException */ protected function getBaseAlias($alias) { if (!$this->hasAlias($alias)) { return false; } $lastAlias = false; $r = 0; while (isset($this->aliases[$alias])) { $lastAlias = $alias; $alias = $this->aliases[$alias]; $r++; if ($r > 100) { throw new Exception\RuntimeException( sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias) ); } } return $lastAlias; } /** * Add alias * * @throws Exception\InvalidArgumentException * @param string $alias * @param string $class * @param array $parameters * @return void */ public function addAlias($alias, $class, array $parameters = array()) { if (!preg_match('#^[a-zA-Z0-9-_]+$#', $alias)) { throw new Exception\InvalidArgumentException( 'Aliases must be alphanumeric and can contain dashes and underscores only.' ); } $this->aliases[$alias] = $class; if ($parameters) { $this->setParameters($alias, $parameters); } } /** * Check for configuration * * @param string $aliasOrClass * @return bool */ public function hasConfig($aliasOrClass) { $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; if (!isset($this->configurations[$key])) { return false; } if ($this->configurations[$key] === $this->configurationTemplate) { return false; } return true; } /** * Sets configuration for a single alias/class * * @param string $aliasOrClass * @param array $configuration * @param bool $append */ public function setConfig($aliasOrClass, array $configuration, $append = false) { $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; if (!isset($this->configurations[$key]) || !$append) { $this->configurations[$key] = $this->configurationTemplate; } // Ignore anything but 'parameters' and 'injections' $configuration = array( 'parameters' => isset($configuration['parameters']) ? $configuration['parameters'] : array(), 'injections' => isset($configuration['injections']) ? $configuration['injections'] : array(), 'shared' => isset($configuration['shared']) ? $configuration['shared'] : true ); $this->configurations[$key] = array_replace_recursive($this->configurations[$key], $configuration); } /** * Get classes * * @return array */ public function getClasses() { $classes = array(); foreach ($this->configurations as $name => $data) { if (strpos($name, 'alias') === 0) continue; $classes[] = $name; } return $classes; } /** * @param string $aliasOrClass * @return array */ public function getConfig($aliasOrClass) { $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; if (isset($this->configurations[$key])) { return $this->configurations[$key]; } return $this->configurationTemplate; } /** * setParameters() is a convenience method for: * setConfig($type, array('parameters' => array(...)), true); * * @param string $aliasOrClass Alias or Class * @param array $parameters Multi-dim array of parameters and their values * @return void */ public function setParameters($aliasOrClass, array $parameters) { $this->setConfig($aliasOrClass, array('parameters' => $parameters), true); } /** * setInjections() is a convenience method for: * setConfig($type, array('injections' => array(...)), true); * * @param string $aliasOrClass Alias or Class * @param array $injections Multi-dim array of methods and their parameters * @return void */ public function setInjections($aliasOrClass, array $injections) { $this->setConfig($aliasOrClass, array('injections' => $injections), true); } /** * Set shared * * @param string $aliasOrClass * @param bool $isShared * @return void */ public function setShared($aliasOrClass, $isShared) { $this->setConfig($aliasOrClass, array('shared' => (bool) $isShared), true); } /** * Check for type preferences * * @param string $interfaceOrAbstract * @return bool */ public function hasTypePreferences($interfaceOrAbstract) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; return (isset($this->typePreferences[$key]) && $this->typePreferences[$key]); } /** * Set type preference * * @param string $interfaceOrAbstract * @param array $preferredImplementations * @return InstanceManager */ public function setTypePreference($interfaceOrAbstract, array $preferredImplementations) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; foreach ($preferredImplementations as $preferredImplementation) { $this->addTypePreference($key, $preferredImplementation); } return $this; } /** * Get type preferences * * @param string $interfaceOrAbstract * @return array */ public function getTypePreferences($interfaceOrAbstract) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; if (isset($this->typePreferences[$key])) { return $this->typePreferences[$key]; } return array(); } /** * Unset type preferences * * @param string $interfaceOrAbstract * @return void */ public function unsetTypePreferences($interfaceOrAbstract) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; unset($this->typePreferences[$key]); } /** * Adds a type preference. A type preference is a redirection to a preferred alias or type when an abstract type * $interfaceOrAbstract is requested * * @param string $interfaceOrAbstract * @param string $preferredImplementation * @return self */ public function addTypePreference($interfaceOrAbstract, $preferredImplementation) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; if (!isset($this->typePreferences[$key])) { $this->typePreferences[$key] = array(); } $this->typePreferences[$key][] = $preferredImplementation; return $this; } /** * Removes a previously set type preference * * @param string $interfaceOrAbstract * @param string $preferredType * @return bool|self */ public function removeTypePreference($interfaceOrAbstract, $preferredType) { $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; if (!isset($this->typePreferences[$key]) || !in_array($preferredType, $this->typePreferences[$key])) { return false; } unset($this->typePreferences[$key][array_search($key, $this->typePreferences)]); return $this; } /** * @param string $classOrAlias * @param string[] $paramKeys * @return string */ protected function createHashForKeys($classOrAlias, $paramKeys) { return $classOrAlias . ':' . implode('|', $paramKeys); } /** * @param string $classOrAlias * @param array $paramValues * @return string */ protected function createHashForValues($classOrAlias, $paramValues) { $hashValue = ''; foreach ($paramValues as $param) { switch (gettype($param)) { case 'object': $hashValue .= spl_object_hash($param) . '|'; break; case 'integer': case 'string': case 'boolean': case 'NULL': case 'double': $hashValue .= $param . '|'; break; case 'array': $hashValue .= 'Array|'; break; case 'resource': $hashValue .= 'resource|'; break; } } return $hashValue; } }