* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Behat\Testwork\ServiceContainer\Configuration; use Behat\Testwork\ServiceContainer\Exception\ConfigurationLoadingException; use Symfony\Component\Yaml\Yaml; /** * Loads configuration from different sources. * * @author Konstantin Kudryashov */ final class ConfigurationLoader { /** * @var null|string */ private $configurationPath; /** * @var null|string */ private $environmentVariable; /** * @var Boolean */ private $profileFound; /** * @var array */ private $debugInformation = array( 'environment_variable_name' => 'none', 'environment_variable_content' => 'none', 'configuration_file_path' => 'none' ); /** * Constructs reader. * * @param string $environmentVariableName Environment variable name * @param string $configurationPath Configuration file path */ public function __construct($environmentVariableName = null, $configurationPath = null) { $this->environmentVariable = $environmentVariableName; $this->configurationPath = $configurationPath; } /** * Sets environment variable name. * * @param null|string $variable */ public function setEnvironmentVariableName($variable) { $this->environmentVariable = $variable; } /** * Returns environment variable name. * * @return null|string */ public function getEnvironmentVariableName() { return $this->environmentVariable; } /** * Sets configuration file path. * * @param null|string $path */ public function setConfigurationFilePath($path) { $this->configurationPath = $path; } /** * Returns configuration file path. * * @return null|string */ public function getConfigurationFilePath() { return $this->configurationPath; } /** * Reads configuration sequence for specific profile. * * @param string $profile Profile name * * @return array * * @throws ConfigurationLoadingException */ public function loadConfiguration($profile = 'default') { $configs = array(); $this->profileFound = false; // first is ENV config foreach ($this->loadEnvironmentConfiguration() as $config) { $configs[] = $config; } // second is file configuration (if there is some) if ($this->configurationPath) { $this->debugInformation['configuration_file_path'] = $this->configurationPath; foreach ($this->loadFileConfiguration($this->configurationPath, $profile) as $config) { $configs[] = $config; } } // if specific profile has not been found if ('default' !== $profile && !$this->profileFound) { throw new ConfigurationLoadingException(sprintf( 'Can not find configuration for `%s` profile.', $profile )); } return $configs; } /** * Returns array with configuration debug information. * * @return array */ public function debugInformation() { return $this->debugInformation; } /** * Loads information from environment variable. * * @return array * * @throws ConfigurationLoadingException If environment variable environment var is set to invalid JSON */ protected function loadEnvironmentConfiguration() { $configs = array(); if (!$this->environmentVariable) { return $configs; } $this->debugInformation['environment_variable_name'] = $this->environmentVariable; if ($envConfig = getenv($this->environmentVariable)) { $config = @json_decode($envConfig, true); $this->debugInformation['environment_variable_content'] = $envConfig; if (!$config) { throw new ConfigurationLoadingException(sprintf( 'Environment variable `%s` should contain a valid JSON, but it is set to `%s`.', $this->environmentVariable, $envConfig )); } $configs[] = $config; } return $configs; } /** * Loads information from YAML configuration file. * * @param string $configPath Config file path * @param string $profile Profile name * * @return array * * @throws ConfigurationLoadingException If config file is not found */ protected function loadFileConfiguration($configPath, $profile) { if (!is_file($configPath) || !is_readable($configPath)) { throw new ConfigurationLoadingException(sprintf('Configuration file `%s` not found.', $configPath)); } $basePath = rtrim(dirname($configPath), DIRECTORY_SEPARATOR); $config = (array) Yaml::parse(file_get_contents($configPath)); return $this->loadConfigs($basePath, $config, $profile); } /** * Loads configs for provided config and profile. * * @param string $basePath * @param array $config * @param string $profile * * @return array */ private function loadConfigs($basePath, array $config, $profile) { $configs = array(); // first load default profile from current config, but only if custom profile requested if ('default' !== $profile && isset($config['default'])) { $configs[] = $config['default']; } // then recursively load profiles from imports if (isset($config['imports']) && is_array($config['imports'])) { $configs = array_merge($configs, $this->loadImports($basePath, $config['imports'], $profile)); } // then load specific profile from current config if (isset($config[$profile])) { $configs[] = $config[$profile]; $this->profileFound = true; } return $configs; } /** * Loads all provided imports. * * @param string $basePath * @param array $paths * @param string $profile * * @return array */ private function loadImports($basePath, array $paths, $profile) { $configs = array(); foreach ($paths as $path) { foreach ($this->parseImport($basePath, $path, $profile) as $importConfig) { $configs[] = $importConfig; } } return $configs; } /** * Parses import. * * @param string $basePath * @param string $path * @param string $profile * * @return array * * @throws ConfigurationLoadingException If import file not found */ private function parseImport($basePath, $path, $profile) { if (!file_exists($path) && file_exists($basePath . DIRECTORY_SEPARATOR . $path)) { $path = $basePath . DIRECTORY_SEPARATOR . $path; } if (!file_exists($path)) { throw new ConfigurationLoadingException(sprintf( 'Can not import `%s` configuration file. File not found.', $path )); } return $this->loadFileConfiguration($path, $profile); } }