1+ <?php
2+ /**
3+ * @see https://github.com/zendframework/zend-config-aggregator for the canonical source repository
4+ * @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
5+ * @copyright Copyright (c) 2015-2016 Mateusz Tymek (http://mateusztymek.pl)
6+ * @license https://github.com/zendframework/zend-config-aggregator/blob/master/LICENSE.md New BSD License
7+ */
8+ namespace ZendServer \DepH \ConfigAggregator ;
9+
10+ use Closure ;
11+ use Generator ;
12+ use Zend \Stdlib \ArrayUtils \MergeRemoveKey ;
13+ use Zend \Stdlib \ArrayUtils \MergeReplaceKeyInterface ;
14+ use ZendServer \DepH \ConfigAggregator \Exception \InvalidConfigProviderException ;
15+
16+ /**
17+ * Aggregate configuration generated by configuration providers.
18+ */
19+ class ConfigAggregator
20+ {
21+ const ENABLE_CACHE = 'config_cache_enabled ' ;
22+ const CACHE_TEMPLATE = <<< 'EOT'
23+ <?php
24+ /**
25+ * This configuration cache file was generated by %s
26+ * at %s
27+ */
28+ return %s;
29+ EOT;
30+ /**
31+ * @var array
32+ */
33+ private $ config ;
34+ /**
35+ * @param array $providers Array of providers. These may be callables, or
36+ * string values representing classes that act as providers. If the
37+ * latter, they must be instantiable without constructor arguments.
38+ * @param null|string $cachedConfigFile Configuration cache file; config is
39+ * loaded from this file if present, and written to it if not. null
40+ * disables caching.
41+ */
42+ public function __construct (
43+ array $ providers = array (),
44+ $ cachedConfigFile = null
45+ ) {
46+ if ($ this ->loadConfigFromCache ($ cachedConfigFile )) {
47+ return ;
48+ }
49+ $ this ->config = $ this ->loadConfigFromProviders ($ providers );
50+ $ this ->cacheConfig ($ this ->config , $ cachedConfigFile );
51+ }
52+ /**
53+ * @return array
54+ */
55+ public function getMergedConfig ()
56+ {
57+ return $ this ->config ;
58+ }
59+ /**
60+ * Resolve a provider.
61+ *
62+ * If the provider is a string class name, instantiates that class and
63+ * tests if it is callable, returning it if true.
64+ *
65+ * If the provider is a callable, returns it verbatim.
66+ *
67+ * Raises an exception for any other condition.
68+ *
69+ * @param string|callable $provider
70+ * @return callable
71+ * @throws InvalidConfigProviderException
72+ */
73+ private function resolveProvider ($ provider )
74+ {
75+ if (is_string ($ provider )) {
76+ if (! class_exists ($ provider )) {
77+ throw new InvalidConfigProviderException ("Cannot read config from $ provider - class cannot be loaded. " );
78+ }
79+ $ provider = new $ provider ();
80+ }
81+ if (! is_callable ($ provider )) {
82+ throw new InvalidConfigProviderException (
83+ sprintf ("Cannot read config from %s - config provider must be callable. " , get_class ($ provider ))
84+ );
85+ }
86+ return $ provider ;
87+ }
88+ /**
89+ * Perform a recursive merge of two multi-dimensional arrays.
90+ *
91+ * Copied from https://github.com/zendframework/zend-stdlib/blob/980ce463c29c1a66c33e0eb67961bba895d0e19e/src/ArrayUtils.php#L269
92+ *
93+ * @param array $a
94+ * @param array $b
95+ * @return $a
96+ */
97+ private function mergeArray (array $ a , array $ b )
98+ {
99+ foreach ($ b as $ key => $ value ) {
100+ if ($ value instanceof MergeReplaceKeyInterface) {
101+ $ a [$ key ] = $ value ->getData ();
102+ } elseif (isset ($ a [$ key ]) || array_key_exists ($ key , $ a )) {
103+ if ($ value instanceof MergeRemoveKey) {
104+ unset($ a [$ key ]);
105+ } elseif (is_int ($ key )) {
106+ $ a [] = $ value ;
107+ } elseif (is_array ($ value ) && is_array ($ a [$ key ])) {
108+ $ a [$ key ] = $ this ->mergeArray ($ a [$ key ], $ value );
109+ } else {
110+ $ a [$ key ] = $ value ;
111+ }
112+ } else {
113+ if (! $ value instanceof MergeRemoveKey) {
114+ $ a [$ key ] = $ value ;
115+ }
116+ }
117+ }
118+ return $ a ;
119+ }
120+ /**
121+ * Merge configuration from a provider with existing configuration.
122+ *
123+ * @param array $mergedConfig Passed by reference as a performance/resource
124+ * optimization.
125+ * @param mixed|array $config Configuration generated by the $provider.
126+ * @param callable $provider Provider responsible for generating $config;
127+ * used for exception messages only.
128+ * @return void
129+ * @throws InvalidConfigProviderException
130+ */
131+ private function mergeConfig (&$ mergedConfig , $ config , $ provider )
132+ {
133+ if (!is_callable ($ provider )) {
134+ throw new InvalidProviderException ('Callable provider expected. ' );
135+ }
136+
137+ if (! is_array ($ config )) {
138+ $ type = '' ;
139+ if (is_object ($ provider ) && ! $ provider instanceof Closure) {
140+ $ type = get_class ($ provider );
141+ }
142+ if ($ provider instanceof Closure) {
143+ $ type = 'Closure ' ;
144+ }
145+ if (is_callable ($ provider ) && ! $ provider instanceof Closure) {
146+ $ type = is_string ($ provider ) ? $ provider : gettype ($ provider );
147+ }
148+ throw new InvalidConfigProviderException (sprintf (
149+ 'Cannot read config from %s; does not return array ' ,
150+ $ type
151+ ));
152+ }
153+ $ mergedConfig = $ this ->mergeArray ($ mergedConfig , $ config );
154+ }
155+ /**
156+ * Iterate providers, merging config from each with the previous.
157+ *
158+ * @param array $providers
159+ * @return array
160+ */
161+ private function loadConfigFromProviders (array $ providers )
162+ {
163+ $ mergedConfig = array ();
164+ foreach ($ providers as $ provider ) {
165+ $ provider = $ this ->resolveProvider ($ provider );
166+ $ config = $ provider ();
167+ if (! $ config instanceof Generator) {
168+ $ this ->mergeConfig ($ mergedConfig , $ config , $ provider );
169+ continue ;
170+ }
171+ // Handle generators
172+ foreach ($ config as $ cfg ) {
173+ $ this ->mergeConfig ($ mergedConfig , $ cfg , $ provider );
174+ }
175+ }
176+ return $ mergedConfig ;
177+ }
178+ /**
179+ * Attempt to load the configuration from a cache file.
180+ *
181+ * @param null|string $cachedConfigFile
182+ * @return bool
183+ */
184+ private function loadConfigFromCache ($ cachedConfigFile )
185+ {
186+ if (null === $ cachedConfigFile ) {
187+ return false ;
188+ }
189+ if (! file_exists ($ cachedConfigFile )) {
190+ return false ;
191+ }
192+ $ this ->config = require $ cachedConfigFile ;
193+ return true ;
194+ }
195+ /**
196+ * Attempt to cache discovered configuration.
197+ *
198+ * @param array $config
199+ * @param null|string $cachedConfigFile
200+ */
201+ private function cacheConfig (array $ config , $ cachedConfigFile )
202+ {
203+ if (null === $ cachedConfigFile ) {
204+ return ;
205+ }
206+ if (empty ($ config [static ::ENABLE_CACHE ])) {
207+ return ;
208+ }
209+ file_put_contents ($ cachedConfigFile , sprintf (
210+ self ::CACHE_TEMPLATE ,
211+ get_class ($ this ),
212+ date ('c ' ),
213+ var_export ($ config , true )
214+ ));
215+ }
216+ }
0 commit comments