Environment::mergeArray()   B
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 8.8571
cc 6
eloc 9
nc 4
nop 2
1
<?php
2
namespace marcovtwout\YiiEnvironment;
3
4
/**
5
 * @name Environment
6
 * @author Marco van 't Wout | Tremani
7
 *
8
 * Simple class used to set configuration and debugging depending on environment.
9
 * Using this you can predefine configurations for use in different environments,
10
 * like _development, testing, staging and production_.
11
 *
12
 * @see README.md
13
 */
14
class Environment
15
{
16
    /**
17
     * Inherit key that can be used in configConsole
18
     */
19
    const INHERIT_KEY = 'inherit';
20
21
    /**
22
     * @var string name of env var to check
23
     */
24
    protected $envVar = 'YII_ENVIRONMENT';
25
    
26
    /**
27
     * @var string selected environment mode
28
     */
29
    protected $mode;
30
31
    /**
32
     * @var string config dir
33
     */
34
    protected $configDir;
35
36
    /**
37
     * @var string path to yii.php
38
     */
39
    public $yiiPath;
40
41
    /**
42
     * @var string path to yiic.php
43
     */
44
    public $yiicPath;
45
46
    /**
47
     * @var string path to yiit.php
48
     */
49
    public $yiitPath;
50
51
    /**
52
     * @var int debug level
53
     */
54
    public $yiiDebug;
55
56
    /**
57
     * @var int trace level
58
     */
59
    public $yiiTraceLevel;
60
61
    /**
62
     * @var array web config array
63
     */
64
    public $configWeb;
65
66
    /**
67
     * @var array console config array
68
     */
69
    public $configConsole;
70
71
    /**
72
     * Extend Environment class and merge parent array if you want to modify/extend these
73
     * @return string[] list of valid modes
74
     */
75
    protected function getValidModes()
76
    {
77
        return array(
78
            100 => 'DEVELOPMENT',
79
            200 => 'TEST',
80
            300 => 'STAGING',
81
            400 => 'PRODUCTION'
82
        );
83
    }
84
85
    /**
86
     * Initilizes the Environment class with the given mode
87
     * @param string $mode used to override automatically setting mode
88
     * @param string $configDir override default configDir
89
     */
90
    public function __construct($mode = null, $configDir = null)
91
    {
92
        $this->setConfigDir($configDir);
93
        $this->setMode($mode);
94
        $this->setEnvironment();
95
    }
96
    
97
    /**
98
     * Set config dir.
99
     * @param string $configDir
100
     */
101
    protected function setConfigDir($configDir)
102
    {
103
        if ($configDir !== null) {
104
            $this->configDir = rtrim($configDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
105
        } else {
106
            $this->configDir = __DIR__ . '/../../../config/';
107
        }
108
    }
109
110
    /**
111
     * Set environment mode, if valid mode can be determined.
112
     * @param string $mode if left empty, determine automatically
113
     */
114
    protected function setMode($mode = null)
115
    {
116
        // If not overridden, determine automatically
117
        if ($mode === null) {
118
            $mode = $this->determineMode();
119
        }
120
121
        // Check if mode is valid
122
        $mode = strtoupper($mode);
123
        if (!in_array($mode, $this->getValidModes(), true)) {
124
            throw new \Exception('Invalid environment mode supplied or selected.');
125
        }
126
127
        $this->mode = $mode;
128
    }
129
    
130
    /**
131
     * Determine current environment mode depending on environment variable.
132
     * Also checks if there is a mode file that might override this environment.
133
     * Override this function if you want to implement your own method.
134
     * @return string mode
135
     */
136
    protected function determineMode()
137
    {
138
        if (file_exists($this->getModeFilePath())) {
139
            // Is there a mode file?
140
            $mode = trim(file_get_contents($this->getModeFilePath()));
141
        } else {
142
            // Else, return mode based on environment var
143
            $mode = getenv($this->envVar);
144
            if ($mode === false) {
145
                throw new \Exception('"Environment mode cannot be determined, see class for instructions.');
146
            }
147
        }
148
        return $mode;
149
    }
150
    
151
    /**
152
     * @return string mode file path
153
     */
154
    protected function getModeFilePath()
155
    {
156
        return $this->configDir . 'mode.php';
157
    }
158
159
    /**
160
     * Load and merge config files into one array.
161
     * @return array $config array to be processed by setEnvironment.
162
     */
163
    protected function getConfig()
164
    {
165
        // Load main config
166
        $fileMainConfig = $this->configDir . 'main.php';
167
        if (!file_exists($fileMainConfig)) {
168
            throw new \Exception('Cannot find main config file "' . $fileMainConfig . '".');
169
        }
170
        $configMain = require($fileMainConfig);
171
172
        // Load specific config
173
        $fileSpecificConfig = $this->configDir . 'mode_' . strtolower($this->mode) . '.php';
174
        if (!file_exists($fileSpecificConfig)) {
175
            throw new \Exception('Cannot find mode specific config file "' . $fileSpecificConfig . '".');
176
        }
177
        $configSpecific = require($fileSpecificConfig);
178
179
        // Merge specific config into main config
180
        $config = self::mergeArray($configMain, $configSpecific);
181
182
        // If one exists, load and merge local config
183
        $fileLocalConfig = $this->configDir . 'local.php';
184
        if (file_exists($fileLocalConfig)) {
185
            $configLocal = require($fileLocalConfig);
186
            $config = self::mergeArray($config, $configLocal);
187
        }
188
189
        // Return
190
        return $config;
191
    }
192
193
    /**
194
     * Sets the environment and configuration for the selected mode.
195
     */
196
    protected function setEnvironment()
197
    {
198
        $config = $this->getConfig();
199
200
        // Set attributes
201
        $this->yiiPath = $config['yiiPath'];
202
        if (isset($config['yiicPath'])) {
203
            $this->yiicPath = $config['yiicPath'];
204
        }
205
        if (isset($config['yiitPath'])) {
206
            $this->yiitPath = $config['yiitPath'];
207
        }
208
        $this->yiiDebug = $config['yiiDebug'];
209
        $this->yiiTraceLevel = $config['yiiTraceLevel'];
210
        $this->configWeb = $config['configWeb'];
211
        $this->configWeb['params']['environment'] = strtolower($this->mode);
212
213
        // Set console attributes and related actions
214
        if (isset($config['configConsole']) && !empty($config['configConsole'])) {
215
            $this->configConsole = $config['configConsole'];
216
            $this->processInherits($this->configConsole); // Process configConsole for inherits
217
            $this->configConsole['params']['environment'] = strtolower($this->mode);
218
        }
219
    }
220
221
    /**
222
     * Show current Environment class values
223
     */
224
    public function showDebug()
225
    {
226
        echo '<div style="position: absolute; bottom: 0; left: 0; z-index: 99999; height: 250px; overflow: auto;
227
            background-color: #ddd; color: #000; border: 1px solid #000; margin: 5px; padding: 5px;">
228
            <pre>' . htmlspecialchars(print_r($this, true)) . '</pre>
229
        </div>';
230
    }
231
232
    /**
233
     * Merges two arrays into one recursively.
234
     * @param array $a array to be merged to
235
     * @param array $b array to be merged from
236
     * @return array the merged array (the original arrays are not changed.)
237
     *
238
     * Taken from Yii's CMap::mergeArray, since php does not supply a native
239
     * function that produces the required result.
240
     * @see http://www.yiiframework.com/doc/api/1.1/CMap#mergeArray-detail
241
     */
242
    protected static function mergeArray($a, $b)
243
    {
244
        foreach ($b as $k => $v) {
245
            if (is_integer($k)) {
246
                $a[] = $v;
247
            } elseif (is_array($v) && isset($a[$k]) && is_array($a[$k])) {
248
                $a[$k] = self::mergeArray($a[$k], $v);
249
            } else {
250
                $a[$k] = $v;
251
            }
252
        }
253
        return $a;
254
    }
255
256
    /**
257
     * Loop through console config array, replacing values called 'inherit' by values from $this->configWeb
258
     * @param mixed $array target array
259
     * @param string[] $path array that keeps track of current path
260
     */
261
    private function processInherits(&$array, $path = array())
262
    {
263
        foreach ($array as $key => &$value) {
264
            if (is_array($value)) {
265
                $this->processInherits($value, array_merge($path, array($key)));
266
            }
267
268
            if ($value === self::INHERIT_KEY) {
269
                $value = $this->getValueFromArray($this->configWeb, array_reverse(array_merge($path, array($key))));
270
            }
271
        }
272
    }
273
274
    /**
275
     * Walk $array through $path until the end, and return value
276
     * @param array $array target
277
     * @param array $path path array, from deep key to shallow key
278
     * @return mixed
279
     */
280
    private function getValueFromArray(&$array, $path)
281
    {
282
        if (count($path) > 1) {
283
            return $this->getValueFromArray($array[array_pop($path)], $path);
284
        } else {
285
            return $array[reset($path)];
286
        }
287
    }
288
}
289