ConfigCommand.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Command;
  12. use Composer\Util\Platform;
  13. use Composer\Util\Silencer;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Input\InputArgument;
  16. use Symfony\Component\Console\Input\InputOption;
  17. use Symfony\Component\Console\Output\OutputInterface;
  18. use Composer\Config;
  19. use Composer\Config\JsonConfigSource;
  20. use Composer\Factory;
  21. use Composer\Json\JsonFile;
  22. use Composer\Semver\VersionParser;
  23. use Composer\Package\BasePackage;
  24. /**
  25. * @author Joshua Estes <Joshua.Estes@iostudio.com>
  26. * @author Jordi Boggiano <j.boggiano@seld.be>
  27. */
  28. class ConfigCommand extends BaseCommand
  29. {
  30. /**
  31. * @var Config
  32. */
  33. protected $config;
  34. /**
  35. * @var JsonFile
  36. */
  37. protected $configFile;
  38. /**
  39. * @var JsonConfigSource
  40. */
  41. protected $configSource;
  42. /**
  43. * @var JsonFile
  44. */
  45. protected $authConfigFile;
  46. /**
  47. * @var JsonConfigSource
  48. */
  49. protected $authConfigSource;
  50. /**
  51. * {@inheritDoc}
  52. */
  53. protected function configure()
  54. {
  55. $this
  56. ->setName('config')
  57. ->setDescription('Sets config options.')
  58. ->setDefinition(array(
  59. new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
  60. new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'),
  61. new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'),
  62. new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
  63. new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
  64. new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
  65. new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
  66. new InputArgument('setting-key', null, 'Setting key'),
  67. new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
  68. ))
  69. ->setHelp(<<<EOT
  70. This command allows you to edit composer config settings and repositories
  71. in either the local composer.json file or the global config.json file.
  72. Additionally it lets you edit most properties in the local composer.json.
  73. To set a config setting:
  74. <comment>%command.full_name% bin-dir bin/</comment>
  75. To read a config setting:
  76. <comment>%command.full_name% bin-dir</comment>
  77. Outputs: <info>bin</info>
  78. To edit the global config.json file:
  79. <comment>%command.full_name% --global</comment>
  80. To add a repository:
  81. <comment>%command.full_name% repositories.foo vcs https://bar.com</comment>
  82. To remove a repository (repo is a short alias for repositories):
  83. <comment>%command.full_name% --unset repo.foo</comment>
  84. To disable packagist:
  85. <comment>%command.full_name% repo.packagist false</comment>
  86. You can alter repositories in the global config.json file by passing in the
  87. <info>--global</info> option.
  88. To edit the file in an external editor:
  89. <comment>%command.full_name% --editor</comment>
  90. To choose your editor you can set the "EDITOR" env variable.
  91. To get a list of configuration values in the file:
  92. <comment>%command.full_name% --list</comment>
  93. You can always pass more than one option. As an example, if you want to edit the
  94. global config.json file.
  95. <comment>%command.full_name% --editor --global</comment>
  96. EOT
  97. )
  98. ;
  99. }
  100. /**
  101. * {@inheritDoc}
  102. */
  103. protected function initialize(InputInterface $input, OutputInterface $output)
  104. {
  105. parent::initialize($input, $output);
  106. if ($input->getOption('global') && null !== $input->getOption('file')) {
  107. throw new \RuntimeException('--file and --global can not be combined');
  108. }
  109. $io = $this->getIO();
  110. $this->config = Factory::createConfig($io);
  111. // Get the local composer.json, global config.json, or if the user
  112. // passed in a file to use
  113. $configFile = $input->getOption('global')
  114. ? ($this->config->get('home') . '/config.json')
  115. : ($input->getOption('file') ?: trim(getenv('COMPOSER')) ?: 'composer.json');
  116. // Create global composer.json if this was invoked using `composer global config`
  117. if ($configFile === 'composer.json' && !file_exists($configFile) && realpath(getcwd()) === realpath($this->config->get('home'))) {
  118. file_put_contents($configFile, "{\n}\n");
  119. }
  120. $this->configFile = new JsonFile($configFile, null, $io);
  121. $this->configSource = new JsonConfigSource($this->configFile);
  122. $authConfigFile = $input->getOption('global')
  123. ? ($this->config->get('home') . '/auth.json')
  124. : dirname(realpath($configFile)) . '/auth.json';
  125. $this->authConfigFile = new JsonFile($authConfigFile, null, $io);
  126. $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
  127. // Initialize the global file if it's not there, ignoring any warnings or notices
  128. if ($input->getOption('global') && !$this->configFile->exists()) {
  129. touch($this->configFile->getPath());
  130. $this->configFile->write(array('config' => new \ArrayObject));
  131. Silencer::call('chmod', $this->configFile->getPath(), 0600);
  132. }
  133. if ($input->getOption('global') && !$this->authConfigFile->exists()) {
  134. touch($this->authConfigFile->getPath());
  135. $this->authConfigFile->write(array('bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject));
  136. Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
  137. }
  138. if (!$this->configFile->exists()) {
  139. throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
  140. }
  141. }
  142. /**
  143. * {@inheritDoc}
  144. */
  145. protected function execute(InputInterface $input, OutputInterface $output)
  146. {
  147. // Open file in editor
  148. if ($input->getOption('editor')) {
  149. $editor = escapeshellcmd(getenv('EDITOR'));
  150. if (!$editor) {
  151. if (Platform::isWindows()) {
  152. $editor = 'notepad';
  153. } else {
  154. foreach (array('editor', 'vim', 'vi', 'nano', 'pico', 'ed') as $candidate) {
  155. if (exec('which '.$candidate)) {
  156. $editor = $candidate;
  157. break;
  158. }
  159. }
  160. }
  161. }
  162. $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
  163. system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`'));
  164. return 0;
  165. }
  166. if (!$input->getOption('global')) {
  167. $this->config->merge($this->configFile->read());
  168. $this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array()));
  169. }
  170. // List the configuration of the file settings
  171. if ($input->getOption('list')) {
  172. $this->listConfiguration($this->config->all(), $this->config->raw(), $output);
  173. return 0;
  174. }
  175. $settingKey = $input->getArgument('setting-key');
  176. if (!$settingKey) {
  177. return 0;
  178. }
  179. // If the user enters in a config variable, parse it and save to file
  180. if (array() !== $input->getArgument('setting-value') && $input->getOption('unset')) {
  181. throw new \RuntimeException('You can not combine a setting value with --unset');
  182. }
  183. // show the value if no value is provided
  184. if (array() === $input->getArgument('setting-value') && !$input->getOption('unset')) {
  185. $properties = array('name', 'type', 'description', 'homepage', 'version', 'minimum-stability', 'prefer-stable', 'keywords', 'license', 'extra');
  186. $rawData = $this->configFile->read();
  187. $data = $this->config->all();
  188. if (preg_match('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) {
  189. if (!isset($matches[1]) || $matches[1] === '') {
  190. $value = isset($data['repositories']) ? $data['repositories'] : array();
  191. } else {
  192. if (!isset($data['repositories'][$matches[1]])) {
  193. throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined');
  194. }
  195. $value = $data['repositories'][$matches[1]];
  196. }
  197. } elseif (strpos($settingKey, '.')) {
  198. $bits = explode('.', $settingKey);
  199. if ($bits[0] === 'extra') {
  200. $data = $rawData;
  201. } else {
  202. $data = $data['config'];
  203. }
  204. $match = false;
  205. foreach ($bits as $bit) {
  206. $key = isset($key) ? $key.'.'.$bit : $bit;
  207. $match = false;
  208. if (isset($data[$key])) {
  209. $match = true;
  210. $data = $data[$key];
  211. unset($key);
  212. }
  213. }
  214. if (!$match) {
  215. throw new \RuntimeException($settingKey.' is not defined.');
  216. }
  217. $value = $data;
  218. } elseif (isset($data['config'][$settingKey])) {
  219. $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
  220. } elseif (in_array($settingKey, $properties, true) && isset($rawData[$settingKey])) {
  221. $value = $rawData[$settingKey];
  222. } else {
  223. throw new \RuntimeException($settingKey.' is not defined');
  224. }
  225. if (is_array($value)) {
  226. $value = json_encode($value);
  227. }
  228. $this->getIO()->write($value);
  229. return 0;
  230. }
  231. $values = $input->getArgument('setting-value'); // what the user is trying to add/change
  232. $booleanValidator = function ($val) {
  233. return in_array($val, array('true', 'false', '1', '0'), true);
  234. };
  235. $booleanNormalizer = function ($val) {
  236. return $val !== 'false' && (bool) $val;
  237. };
  238. // handle config values
  239. $uniqueConfigValues = array(
  240. 'process-timeout' => array('is_numeric', 'intval'),
  241. 'use-include-path' => array($booleanValidator, $booleanNormalizer),
  242. 'preferred-install' => array(
  243. function ($val) {
  244. return in_array($val, array('auto', 'source', 'dist'), true);
  245. },
  246. function ($val) {
  247. return $val;
  248. },
  249. ),
  250. 'store-auths' => array(
  251. function ($val) {
  252. return in_array($val, array('true', 'false', 'prompt'), true);
  253. },
  254. function ($val) {
  255. if ('prompt' === $val) {
  256. return 'prompt';
  257. }
  258. return $val !== 'false' && (bool) $val;
  259. },
  260. ),
  261. 'notify-on-install' => array($booleanValidator, $booleanNormalizer),
  262. 'vendor-dir' => array('is_string', function ($val) {
  263. return $val;
  264. }),
  265. 'bin-dir' => array('is_string', function ($val) {
  266. return $val;
  267. }),
  268. 'archive-dir' => array('is_string', function ($val) {
  269. return $val;
  270. }),
  271. 'archive-format' => array('is_string', function ($val) {
  272. return $val;
  273. }),
  274. 'data-dir' => array('is_string', function ($val) {
  275. return $val;
  276. }),
  277. 'cache-dir' => array('is_string', function ($val) {
  278. return $val;
  279. }),
  280. 'cache-files-dir' => array('is_string', function ($val) {
  281. return $val;
  282. }),
  283. 'cache-repo-dir' => array('is_string', function ($val) {
  284. return $val;
  285. }),
  286. 'cache-vcs-dir' => array('is_string', function ($val) {
  287. return $val;
  288. }),
  289. 'cache-ttl' => array('is_numeric', 'intval'),
  290. 'cache-files-ttl' => array('is_numeric', 'intval'),
  291. 'cache-files-maxsize' => array(
  292. function ($val) {
  293. return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0;
  294. },
  295. function ($val) {
  296. return $val;
  297. },
  298. ),
  299. 'bin-compat' => array(
  300. function ($val) {
  301. return in_array($val, array('auto', 'full'));
  302. },
  303. function ($val) {
  304. return $val;
  305. },
  306. ),
  307. 'discard-changes' => array(
  308. function ($val) {
  309. return in_array($val, array('stash', 'true', 'false', '1', '0'), true);
  310. },
  311. function ($val) {
  312. if ('stash' === $val) {
  313. return 'stash';
  314. }
  315. return $val !== 'false' && (bool) $val;
  316. },
  317. ),
  318. 'autoloader-suffix' => array('is_string', function ($val) {
  319. return $val === 'null' ? null : $val;
  320. }),
  321. 'sort-packages' => array($booleanValidator, $booleanNormalizer),
  322. 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
  323. 'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
  324. 'apcu-autoloader' => array($booleanValidator, $booleanNormalizer),
  325. 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
  326. 'disable-tls' => array($booleanValidator, $booleanNormalizer),
  327. 'secure-http' => array($booleanValidator, $booleanNormalizer),
  328. 'cafile' => array(
  329. function ($val) {
  330. return file_exists($val) && is_readable($val);
  331. },
  332. function ($val) {
  333. return $val === 'null' ? null : $val;
  334. },
  335. ),
  336. 'capath' => array(
  337. function ($val) {
  338. return is_dir($val) && is_readable($val);
  339. },
  340. function ($val) {
  341. return $val === 'null' ? null : $val;
  342. },
  343. ),
  344. 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
  345. );
  346. $multiConfigValues = array(
  347. 'github-protocols' => array(
  348. function ($vals) {
  349. if (!is_array($vals)) {
  350. return 'array expected';
  351. }
  352. foreach ($vals as $val) {
  353. if (!in_array($val, array('git', 'https', 'ssh'))) {
  354. return 'valid protocols include: git, https, ssh';
  355. }
  356. }
  357. return true;
  358. },
  359. function ($vals) {
  360. return $vals;
  361. },
  362. ),
  363. 'github-domains' => array(
  364. function ($vals) {
  365. if (!is_array($vals)) {
  366. return 'array expected';
  367. }
  368. return true;
  369. },
  370. function ($vals) {
  371. return $vals;
  372. },
  373. ),
  374. 'gitlab-domains' => array(
  375. function ($vals) {
  376. if (!is_array($vals)) {
  377. return 'array expected';
  378. }
  379. return true;
  380. },
  381. function ($vals) {
  382. return $vals;
  383. },
  384. ),
  385. );
  386. if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
  387. return $this->configSource->removeConfigSetting($settingKey);
  388. }
  389. if (isset($uniqueConfigValues[$settingKey])) {
  390. return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
  391. }
  392. if (isset($multiConfigValues[$settingKey])) {
  393. return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
  394. }
  395. // handle properties
  396. $uniqueProps = array(
  397. 'name' => array('is_string', function ($val) {
  398. return $val;
  399. }),
  400. 'type' => array('is_string', function ($val) {
  401. return $val;
  402. }),
  403. 'description' => array('is_string', function ($val) {
  404. return $val;
  405. }),
  406. 'homepage' => array('is_string', function ($val) {
  407. return $val;
  408. }),
  409. 'version' => array('is_string', function ($val) {
  410. return $val;
  411. }),
  412. 'minimum-stability' => array(
  413. function ($val) {
  414. return isset(BasePackage::$stabilities[VersionParser::normalizeStability($val)]);
  415. },
  416. function ($val) {
  417. return VersionParser::normalizeStability($val);
  418. },
  419. ),
  420. 'prefer-stable' => array($booleanValidator, $booleanNormalizer),
  421. );
  422. $multiProps = array(
  423. 'keywords' => array(
  424. function ($vals) {
  425. if (!is_array($vals)) {
  426. return 'array expected';
  427. }
  428. return true;
  429. },
  430. function ($vals) {
  431. return $vals;
  432. },
  433. ),
  434. 'license' => array(
  435. function ($vals) {
  436. if (!is_array($vals)) {
  437. return 'array expected';
  438. }
  439. return true;
  440. },
  441. function ($vals) {
  442. return $vals;
  443. },
  444. ),
  445. );
  446. if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || substr($settingKey, 0, 6) === 'extra.')) {
  447. throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
  448. }
  449. if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
  450. return $this->configSource->removeProperty($settingKey);
  451. }
  452. if (isset($uniqueProps[$settingKey])) {
  453. return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
  454. }
  455. if (isset($multiProps[$settingKey])) {
  456. return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
  457. }
  458. // handle repositories
  459. if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
  460. if ($input->getOption('unset')) {
  461. return $this->configSource->removeRepository($matches[1]);
  462. }
  463. if (2 === count($values)) {
  464. return $this->configSource->addRepository($matches[1], array(
  465. 'type' => $values[0],
  466. 'url' => $values[1],
  467. ));
  468. }
  469. if (1 === count($values)) {
  470. $value = strtolower($values[0]);
  471. if (true === $booleanValidator($value)) {
  472. if (false === $booleanNormalizer($value)) {
  473. return $this->configSource->addRepository($matches[1], false);
  474. }
  475. } else {
  476. $value = JsonFile::parseJson($values[0]);
  477. return $this->configSource->addRepository($matches[1], $value);
  478. }
  479. }
  480. throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
  481. }
  482. // handle extra
  483. if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
  484. if ($input->getOption('unset')) {
  485. return $this->configSource->removeProperty($settingKey);
  486. }
  487. return $this->configSource->addProperty($settingKey, $values[0]);
  488. }
  489. // handle platform
  490. if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
  491. if ($input->getOption('unset')) {
  492. return $this->configSource->removeConfigSetting($settingKey);
  493. }
  494. return $this->configSource->addConfigSetting($settingKey, $values[0]);
  495. }
  496. // handle auth
  497. if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic)\.(.+)/', $settingKey, $matches)) {
  498. if ($input->getOption('unset')) {
  499. $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
  500. $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
  501. return;
  502. }
  503. if ($matches[1] === 'bitbucket-oauth') {
  504. if (2 !== count($values)) {
  505. throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values));
  506. }
  507. $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
  508. $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('consumer-key' => $values[0], 'consumer-secret' => $values[1]));
  509. } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token'), true)) {
  510. if (1 !== count($values)) {
  511. throw new \RuntimeException('Too many arguments, expected only one token');
  512. }
  513. $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
  514. $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
  515. } elseif ($matches[1] === 'http-basic') {
  516. if (2 !== count($values)) {
  517. throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
  518. }
  519. $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
  520. $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
  521. }
  522. return;
  523. }
  524. throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
  525. }
  526. protected function handleSingleValue($key, array $callbacks, array $values, $method)
  527. {
  528. list($validator, $normalizer) = $callbacks;
  529. if (1 !== count($values)) {
  530. throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300');
  531. }
  532. if (true !== $validation = $validator($values[0])) {
  533. throw new \RuntimeException(sprintf(
  534. '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''),
  535. $values[0]
  536. ));
  537. }
  538. return call_user_func(array($this->configSource, $method), $key, $normalizer($values[0]));
  539. }
  540. protected function handleMultiValue($key, array $callbacks, array $values, $method)
  541. {
  542. list($validator, $normalizer) = $callbacks;
  543. if (true !== $validation = $validator($values)) {
  544. throw new \RuntimeException(sprintf(
  545. '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''),
  546. json_encode($values)
  547. ));
  548. }
  549. return call_user_func(array($this->configSource, $method), $key, $normalizer($values));
  550. }
  551. /**
  552. * Display the contents of the file in a pretty formatted way
  553. *
  554. * @param array $contents
  555. * @param array $rawContents
  556. * @param OutputInterface $output
  557. * @param string|null $k
  558. */
  559. protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, $k = null)
  560. {
  561. $origK = $k;
  562. $io = $this->getIO();
  563. foreach ($contents as $key => $value) {
  564. if ($k === null && !in_array($key, array('config', 'repositories'))) {
  565. continue;
  566. }
  567. $rawVal = isset($rawContents[$key]) ? $rawContents[$key] : null;
  568. if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) {
  569. $k .= preg_replace('{^config\.}', '', $key . '.');
  570. $this->listConfiguration($value, $rawVal, $output, $k);
  571. $k = $origK;
  572. continue;
  573. }
  574. if (is_array($value)) {
  575. $value = array_map(function ($val) {
  576. return is_array($val) ? json_encode($val) : $val;
  577. }, $value);
  578. $value = '['.implode(', ', $value).']';
  579. }
  580. if (is_bool($value)) {
  581. $value = var_export($value, true);
  582. }
  583. if (is_string($rawVal) && $rawVal != $value) {
  584. $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $rawVal . ' (' . $value . ')</info>');
  585. } else {
  586. $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $value . '</info>');
  587. }
  588. }
  589. }
  590. }