PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Module/Listener/ConfigListener.php

http://github.com/zendframework/zf2
PHP | 355 lines | 227 code | 32 blank | 96 comment | 24 complexity | 517665ebed2469807050641283a2674a MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. namespace Zend\Module\Listener;
  3. use ArrayAccess,
  4. Traversable,
  5. Zend\Config\Config,
  6. Zend\Config\Xml as XmlConfig,
  7. Zend\Config\Ini as IniConfig,
  8. Zend\Config\Yaml as YamlConfig,
  9. Zend\Config\Json as JsonConfig,
  10. Zend\Module\ModuleEvent,
  11. Zend\Stdlib\IteratorToArray,
  12. Zend\EventManager\EventCollection,
  13. Zend\EventManager\ListenerAggregate;
  14. class ConfigListener extends AbstractListener
  15. implements ConfigMerger, ListenerAggregate
  16. {
  17. /**
  18. * @var array
  19. */
  20. protected $listeners = array();
  21. /**
  22. * @var array
  23. */
  24. protected $mergedConfig = array();
  25. /**
  26. * @var Config
  27. */
  28. protected $mergedConfigObject;
  29. /**
  30. * @var bool
  31. */
  32. protected $skipConfig = false;
  33. /**
  34. * @var array
  35. */
  36. protected $globPaths = array();
  37. /**
  38. * __construct
  39. *
  40. * @param ListenerOptions $options
  41. * @return void
  42. */
  43. public function __construct(ListenerOptions $options = null)
  44. {
  45. parent::__construct($options);
  46. if ($this->hasCachedConfig()) {
  47. $this->skipConfig = true;
  48. $this->setMergedConfig($this->getCachedConfig());
  49. }
  50. }
  51. /**
  52. * __invoke proxy to loadModule for easier attaching
  53. *
  54. * @param ModuleEvent $e
  55. * @return ConfigListener
  56. */
  57. public function __invoke(ModuleEvent $e)
  58. {
  59. return $this->loadModule($e);
  60. }
  61. /**
  62. * Attach one or more listeners
  63. *
  64. * @param EventCollection $events
  65. * @return ConfigListener
  66. */
  67. public function attach(EventCollection $events)
  68. {
  69. $this->listeners[] = $events->attach('loadModule', array($this, 'loadModule'), 1000);
  70. $this->listeners[] = $events->attach('loadModules.pre', array($this, 'loadModulesPre'), 9000);
  71. $this->listeners[] = $events->attach('loadModules.post', array($this, 'loadModulesPost'), 9000);
  72. return $this;
  73. }
  74. /**
  75. * Pass self to the ModuleEvent object early so everyone has access.
  76. *
  77. * @param ModuleEvent $e
  78. * @return ConfigListener
  79. */
  80. public function loadModulesPre(ModuleEvent $e)
  81. {
  82. $e->setConfigListener($this);
  83. return $this;
  84. }
  85. /**
  86. * Merge the config for each module
  87. *
  88. * @param ModuleEvent $e
  89. * @return ConfigListener
  90. */
  91. public function loadModule(ModuleEvent $e)
  92. {
  93. if (true === $this->skipConfig) {
  94. return;
  95. }
  96. $module = $e->getParam('module');
  97. if (is_callable(array($module, 'getConfig'))) {
  98. $this->mergeModuleConfig($module);
  99. }
  100. return $this;
  101. }
  102. /**
  103. * Merge all config files matched by the given glob()s
  104. *
  105. * This should really only be called by the module manager.
  106. *
  107. * @param ModuleEvent $e
  108. * @return ConfigListener
  109. */
  110. public function loadModulesPost(ModuleEvent $e)
  111. {
  112. if (true === $this->skipConfig) {
  113. return $this;
  114. }
  115. foreach ($this->globPaths as $globPath) {
  116. $this->mergeGlobPath($globPath);
  117. }
  118. return $this;
  119. }
  120. /**
  121. * Detach all previously attached listeners
  122. *
  123. * @param EventCollection $events
  124. * @return void
  125. */
  126. public function detach(EventCollection $events)
  127. {
  128. foreach ($this->listeners as $key => $listener) {
  129. $events->detach($listener);
  130. unset($this->listeners[$key]);
  131. }
  132. $this->listeners = array();
  133. return $this;
  134. }
  135. /**
  136. * getMergedConfig
  137. *
  138. * @param bool $returnConfigAsObject
  139. * @return mixed
  140. */
  141. public function getMergedConfig($returnConfigAsObject = true)
  142. {
  143. if ($returnConfigAsObject === true) {
  144. if ($this->mergedConfigObject === null) {
  145. $this->mergedConfigObject = new Config($this->mergedConfig);
  146. }
  147. return $this->mergedConfigObject;
  148. } else {
  149. return $this->mergedConfig;
  150. }
  151. }
  152. /**
  153. * setMergedConfig
  154. *
  155. * @param array $config
  156. * @return ConfigListener
  157. */
  158. public function setMergedConfig(array $config)
  159. {
  160. $this->mergedConfig = $config;
  161. $this->mergedConfigObject = null;
  162. return $this;
  163. }
  164. /**
  165. * Add a glob path of config files to merge after loading modules
  166. *
  167. * @param string $globPath
  168. * @return ConfigListener
  169. */
  170. public function addConfigGlobPath($globPath)
  171. {
  172. if (!is_string($globPath)) {
  173. throw new Exception\InvalidArgumentException(
  174. sprintf('Parameter to %s::%s() must be a string; %s given.',
  175. __CLASS__, __METHOD__, gettype($globPath))
  176. );
  177. }
  178. $this->globPaths[] = $globPath;
  179. return $this;
  180. }
  181. /**
  182. * Add an array of glob paths of config files to merge after loading modules
  183. *
  184. * @param mixed $globPaths
  185. * @return ConfigListener
  186. */
  187. public function addConfigGlobPaths($globPaths)
  188. {
  189. if ($globPaths instanceof Traversable) {
  190. $globPaths = IteratorToArray::convert($globPaths);
  191. }
  192. if (!is_array($globPaths)) {
  193. throw new Exception\InvalidArgumentException(
  194. sprintf('Argument passed to %::%s() must be an array, '
  195. . 'implement the \Traversable interface, or be an '
  196. . 'instance of Zend\Config\Config. %s given.',
  197. __CLASS__, __METHOD__, gettype($globPaths))
  198. );
  199. }
  200. foreach ($globPaths as $globPath) {
  201. $this->addConfigGlobPath($globPath);
  202. }
  203. return $this;
  204. }
  205. /**
  206. * Merge all config files matching a glob
  207. *
  208. * @param mixed $globPath
  209. * @return ConfigListener
  210. */
  211. protected function mergeGlobPath($globPath)
  212. {
  213. if (true === $this->skipConfig) {
  214. return $this;
  215. }
  216. foreach (glob($globPath, GLOB_BRACE) as $path) {
  217. $pathInfo = pathinfo($path);
  218. switch (strtolower($pathInfo['extension'])) {
  219. case 'php':
  220. case 'inc':
  221. $config = include $path;
  222. if (!is_array($config) && !$config instanceof ArrayAccess) {
  223. throw new Exception\RuntimeException(sprintf(
  224. 'Invalid configuration type returned by file at "%s"; received "%s"',
  225. $path,
  226. (is_object($config) ? get_class($config) : gettype($config))
  227. ));
  228. }
  229. break;
  230. case 'xml':
  231. $config = new XmlConfig($path);
  232. break;
  233. case 'json':
  234. $config = new JsonConfig($path);
  235. break;
  236. case 'ini':
  237. $config = new IniConfig($path);
  238. break;
  239. case 'yaml':
  240. case 'yml':
  241. $config = new YamlConfig($path);
  242. break;
  243. default:
  244. throw new Exception\RuntimeException(sprintf(
  245. 'Unable to detect config file type by extension: %s',
  246. $path
  247. ));
  248. break;
  249. }
  250. $this->mergeTraversableConfig($config);
  251. if ($this->getOptions()->getConfigCacheEnabled()) {
  252. $this->updateCache();
  253. }
  254. }
  255. return $this;
  256. }
  257. /**
  258. * mergeModuleConfig
  259. *
  260. * @param mixed $module
  261. * @return ConfigListener
  262. */
  263. protected function mergeModuleConfig($module)
  264. {
  265. if ((false === $this->skipConfig)
  266. && (is_callable(array($module, 'getConfig')))
  267. ) {
  268. $config = $module->getConfig();
  269. try {
  270. $this->mergeTraversableConfig($config);
  271. } catch (Exception\InvalidArgumentException $e) {
  272. // Throw a more descriptive exception
  273. throw new Exception\InvalidArgumentException(
  274. sprintf('getConfig() method of %s must be an array, '
  275. . 'implement the \Traversable interface, or be an '
  276. . 'instance of Zend\Config\Config. %s given.',
  277. get_class($module), gettype($config))
  278. );
  279. }
  280. if ($this->getOptions()->getConfigCacheEnabled()) {
  281. $this->updateCache();
  282. }
  283. }
  284. return $this;
  285. }
  286. protected function mergeTraversableConfig($config)
  287. {
  288. if ($config instanceof Traversable) {
  289. $config = IteratorToArray::convert($config);
  290. }
  291. if (!is_array($config)) {
  292. throw new Exception\InvalidArgumentException(
  293. sprintf('Config being merged must be an array, '
  294. . 'implement the \Traversable interface, or be an '
  295. . 'instance of Zend\Config\Config. %s given.', gettype($config))
  296. );
  297. }
  298. $this->setMergedConfig(array_replace_recursive($this->mergedConfig, $config));
  299. }
  300. protected function hasCachedConfig()
  301. {
  302. if (($this->getOptions()->getConfigCacheEnabled())
  303. && (file_exists($this->getOptions()->getConfigCacheFile()))
  304. ) {
  305. return true;
  306. }
  307. return false;
  308. }
  309. protected function getCachedConfig()
  310. {
  311. return include $this->getOptions()->getConfigCacheFile();
  312. }
  313. protected function updateCache()
  314. {
  315. if (($this->getOptions()->getConfigCacheEnabled())
  316. && (false === $this->skipConfig)
  317. ) {
  318. $configFile = $this->getOptions()->getConfigCacheFile();
  319. $this->writeArrayToFile($configFile, $this->getMergedConfig(false));
  320. }
  321. return $this;
  322. }
  323. }