PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/ModuleManager/Listener/ConfigListener.php

https://bitbucket.org/gencer/zf2
PHP | 394 lines | 200 code | 47 blank | 147 comment | 18 complexity | 8bbbe205448432f42dcb9656f65bb42a MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\ModuleManager\Listener;
  10. use Traversable;
  11. use Zend\Config\Config;
  12. use Zend\Config\Factory as ConfigFactory;
  13. use Zend\EventManager\EventManagerInterface;
  14. use Zend\EventManager\ListenerAggregateInterface;
  15. use Zend\ModuleManager\Feature\ConfigProviderInterface;
  16. use Zend\ModuleManager\ModuleEvent;
  17. use Zend\Stdlib\ArrayUtils;
  18. use Zend\Stdlib\Glob;
  19. /**
  20. * Config listener
  21. */
  22. class ConfigListener extends AbstractListener implements
  23. ConfigMergerInterface,
  24. ListenerAggregateInterface
  25. {
  26. const STATIC_PATH = 'static_path';
  27. const GLOB_PATH = 'glob_path';
  28. /**
  29. * @var array
  30. */
  31. protected $callbacks = array();
  32. /**
  33. * @var array
  34. */
  35. protected $configs = array();
  36. /**
  37. * @var array
  38. */
  39. protected $mergedConfig = array();
  40. /**
  41. * @var Config
  42. */
  43. protected $mergedConfigObject;
  44. /**
  45. * @var bool
  46. */
  47. protected $skipConfig = false;
  48. /**
  49. * @var array
  50. */
  51. protected $paths = array();
  52. /**
  53. * __construct
  54. *
  55. * @param ListenerOptions $options
  56. */
  57. public function __construct(ListenerOptions $options = null)
  58. {
  59. parent::__construct($options);
  60. if ($this->hasCachedConfig()) {
  61. $this->skipConfig = true;
  62. $this->setMergedConfig($this->getCachedConfig());
  63. } else {
  64. $this->addConfigGlobPaths($this->getOptions()->getConfigGlobPaths());
  65. $this->addConfigStaticPaths($this->getOptions()->getConfigStaticPaths());
  66. }
  67. }
  68. /**
  69. * {@inheritDoc}
  70. */
  71. public function attach(EventManagerInterface $events)
  72. {
  73. $this->callbacks[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onloadModulesPre'), 1000);
  74. if ($this->skipConfig) {
  75. // We already have the config from cache, no need to collect or merge.
  76. return $this;
  77. }
  78. $this->callbacks[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, array($this, 'onLoadModule'));
  79. $this->callbacks[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModules'), -1000);
  80. $this->callbacks[] = $events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this, 'onMergeConfig'), 1000);
  81. return $this;
  82. }
  83. /**
  84. * Pass self to the ModuleEvent object early so everyone has access.
  85. *
  86. * @param ModuleEvent $e
  87. * @return ConfigListener
  88. */
  89. public function onloadModulesPre(ModuleEvent $e)
  90. {
  91. $e->setConfigListener($this);
  92. return $this;
  93. }
  94. /**
  95. * Merge the config for each module
  96. *
  97. * @param ModuleEvent $e
  98. * @return ConfigListener
  99. */
  100. public function onLoadModule(ModuleEvent $e)
  101. {
  102. $module = $e->getModule();
  103. if (!$module instanceof ConfigProviderInterface
  104. && !is_callable(array($module, 'getConfig'))
  105. ) {
  106. return $this;
  107. }
  108. $config = $module->getConfig();
  109. $this->addConfig($e->getModuleName(), $config);
  110. return $this;
  111. }
  112. /**
  113. * Merge all config files matched by the given glob()s
  114. *
  115. * This is only attached if config is not cached.
  116. *
  117. * @param ModuleEvent $e
  118. * @return ConfigListener
  119. */
  120. public function onMergeConfig(ModuleEvent $e)
  121. {
  122. // Load the config files
  123. foreach ($this->paths as $path) {
  124. $this->addConfigByPath($path['path'], $path['type']);
  125. }
  126. // Merge all of the collected configs
  127. $this->mergedConfig = $this->getOptions()->getExtraConfig() ?: array();
  128. foreach ($this->configs as $config) {
  129. $this->mergedConfig = ArrayUtils::merge($this->mergedConfig, $config);
  130. }
  131. return $this;
  132. }
  133. /**
  134. * Optionally cache merged config
  135. *
  136. * This is only attached if config is not cached.
  137. *
  138. * @param ModuleEvent $e
  139. * @return ConfigListener
  140. */
  141. public function onLoadModules(ModuleEvent $e)
  142. {
  143. // Trigger MERGE_CONFIG event. This is a hook to allow the merged application config to be
  144. // modified before it is cached (In particular, allows the removal of config keys)
  145. $e->getTarget()->getEventManager()->trigger(ModuleEvent::EVENT_MERGE_CONFIG, $e->getTarget(), $e);
  146. // If enabled, update the config cache
  147. if (
  148. $this->getOptions()->getConfigCacheEnabled()
  149. && false === $this->skipConfig
  150. ) {
  151. $configFile = $this->getOptions()->getConfigCacheFile();
  152. $this->writeArrayToFile($configFile, $this->getMergedConfig(false));
  153. }
  154. return $this;
  155. }
  156. /**
  157. * {@inheritDoc}
  158. */
  159. public function detach(EventManagerInterface $events)
  160. {
  161. foreach ($this->callbacks as $index => $callback) {
  162. if ($events->detach($callback)) {
  163. unset($this->callbacks[$index]);
  164. }
  165. }
  166. }
  167. /**
  168. * getMergedConfig
  169. *
  170. * @param bool $returnConfigAsObject
  171. * @return mixed
  172. */
  173. public function getMergedConfig($returnConfigAsObject = true)
  174. {
  175. if ($returnConfigAsObject === true) {
  176. if ($this->mergedConfigObject === null) {
  177. $this->mergedConfigObject = new Config($this->mergedConfig);
  178. }
  179. return $this->mergedConfigObject;
  180. }
  181. return $this->mergedConfig;
  182. }
  183. /**
  184. * setMergedConfig
  185. *
  186. * @param array $config
  187. * @return ConfigListener
  188. */
  189. public function setMergedConfig(array $config)
  190. {
  191. $this->mergedConfig = $config;
  192. $this->mergedConfigObject = null;
  193. return $this;
  194. }
  195. /**
  196. * Add an array of glob paths of config files to merge after loading modules
  197. *
  198. * @param array|Traversable $globPaths
  199. * @return ConfigListener
  200. */
  201. public function addConfigGlobPaths($globPaths)
  202. {
  203. $this->addConfigPaths($globPaths, self::GLOB_PATH);
  204. return $this;
  205. }
  206. /**
  207. * Add a glob path of config files to merge after loading modules
  208. *
  209. * @param string $globPath
  210. * @return ConfigListener
  211. */
  212. public function addConfigGlobPath($globPath)
  213. {
  214. $this->addConfigPath($globPath, self::GLOB_PATH);
  215. return $this;
  216. }
  217. /**
  218. * Add an array of static paths of config files to merge after loading modules
  219. *
  220. * @param array|Traversable $staticPaths
  221. * @return ConfigListener
  222. */
  223. public function addConfigStaticPaths($staticPaths)
  224. {
  225. $this->addConfigPaths($staticPaths, self::STATIC_PATH);
  226. return $this;
  227. }
  228. /**
  229. * Add a static path of config files to merge after loading modules
  230. *
  231. * @param string $staticPath
  232. * @return ConfigListener
  233. */
  234. public function addConfigStaticPath($staticPath)
  235. {
  236. $this->addConfigPath($staticPath, self::STATIC_PATH);
  237. return $this;
  238. }
  239. /**
  240. * Add an array of paths of config files to merge after loading modules
  241. *
  242. * @param Traversable|array $paths
  243. * @param string $type
  244. * @throws Exception\InvalidArgumentException
  245. * @return ConfigListener
  246. */
  247. protected function addConfigPaths($paths, $type)
  248. {
  249. if ($paths instanceof Traversable) {
  250. $paths = ArrayUtils::iteratorToArray($paths);
  251. }
  252. if (!is_array($paths)) {
  253. throw new Exception\InvalidArgumentException(
  254. sprintf('Argument passed to %::%s() must be an array, '
  255. . 'implement the Traversable interface, or be an '
  256. . 'instance of Zend\Config\Config. %s given.',
  257. __CLASS__, __METHOD__, gettype($paths))
  258. );
  259. }
  260. foreach ($paths as $path) {
  261. $this->addConfigPath($path, $type);
  262. }
  263. }
  264. /**
  265. * Add a path of config files to load and merge after loading modules
  266. *
  267. * @param string $path
  268. * @param string $type
  269. * @throws Exception\InvalidArgumentException
  270. * @return ConfigListener
  271. */
  272. protected function addConfigPath($path, $type)
  273. {
  274. if (!is_string($path)) {
  275. throw new Exception\InvalidArgumentException(
  276. sprintf('Parameter to %s::%s() must be a string; %s given.',
  277. __CLASS__, __METHOD__, gettype($path))
  278. );
  279. }
  280. $this->paths[] = array('type' => $type, 'path' => $path);
  281. return $this;
  282. }
  283. /**
  284. * @param string $key
  285. * @param array|Traversable $config
  286. * @throws Exception\InvalidArgumentException
  287. * @return ConfigListener
  288. */
  289. protected function addConfig($key, $config)
  290. {
  291. if ($config instanceof Traversable) {
  292. $config = ArrayUtils::iteratorToArray($config);
  293. }
  294. if (!is_array($config)) {
  295. throw new Exception\InvalidArgumentException(
  296. sprintf('Config being merged must be an array, '
  297. . 'implement the Traversable interface, or be an '
  298. . 'instance of Zend\Config\Config. %s given.', gettype($config))
  299. );
  300. }
  301. $this->configs[$key] = $config;
  302. return $this;
  303. }
  304. /**
  305. * Given a path (glob or static), fetch the config and add it to the array
  306. * of configs to merge.
  307. *
  308. * @param string $path
  309. * @param string $type
  310. * @return ConfigListener
  311. */
  312. protected function addConfigByPath($path, $type)
  313. {
  314. switch ($type) {
  315. case self::STATIC_PATH:
  316. $this->addConfig($path, ConfigFactory::fromFile($path));
  317. break;
  318. case self::GLOB_PATH:
  319. // We want to keep track of where each value came from so we don't
  320. // use ConfigFactory::fromFiles() since it does merging internally.
  321. foreach (Glob::glob($path, Glob::GLOB_BRACE) as $file) {
  322. $this->addConfig($file, ConfigFactory::fromFile($file));
  323. }
  324. break;
  325. }
  326. return $this;
  327. }
  328. /**
  329. * @return bool
  330. */
  331. protected function hasCachedConfig()
  332. {
  333. if (($this->getOptions()->getConfigCacheEnabled())
  334. && (file_exists($this->getOptions()->getConfigCacheFile()))
  335. ) {
  336. return true;
  337. }
  338. return false;
  339. }
  340. /**
  341. * @return mixed
  342. */
  343. protected function getCachedConfig()
  344. {
  345. return include $this->getOptions()->getConfigCacheFile();
  346. }
  347. }