JsonConfigSource.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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\Config;
  12. use Composer\Json\JsonFile;
  13. use Composer\Json\JsonManipulator;
  14. use Composer\Util\Silencer;
  15. /**
  16. * JSON Configuration Source
  17. *
  18. * @author Jordi Boggiano <j.boggiano@seld.be>
  19. * @author Beau Simensen <beau@dflydev.com>
  20. */
  21. class JsonConfigSource implements ConfigSourceInterface
  22. {
  23. /**
  24. * @var JsonFile
  25. */
  26. private $file;
  27. /**
  28. * @var bool
  29. */
  30. private $authConfig;
  31. /**
  32. * Constructor
  33. *
  34. * @param JsonFile $file
  35. * @param bool $authConfig
  36. */
  37. public function __construct(JsonFile $file, $authConfig = false)
  38. {
  39. $this->file = $file;
  40. $this->authConfig = $authConfig;
  41. }
  42. /**
  43. * {@inheritdoc}
  44. */
  45. public function getName()
  46. {
  47. return $this->file->getPath();
  48. }
  49. /**
  50. * {@inheritdoc}
  51. */
  52. public function addRepository($name, $config)
  53. {
  54. $this->manipulateJson('addRepository', $name, $config, function (&$config, $repo, $repoConfig) {
  55. $config['repositories'][$repo] = $repoConfig;
  56. });
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function removeRepository($name)
  62. {
  63. $this->manipulateJson('removeRepository', $name, function (&$config, $repo) {
  64. unset($config['repositories'][$repo]);
  65. });
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function addConfigSetting($name, $value)
  71. {
  72. $authConfig = $this->authConfig;
  73. $this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) use ($authConfig) {
  74. if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|http-basic|platform)\.}', $key)) {
  75. list($key, $host) = explode('.', $key, 2);
  76. if ($authConfig) {
  77. $config[$key][$host] = $val;
  78. } else {
  79. $config['config'][$key][$host] = $val;
  80. }
  81. } else {
  82. $config['config'][$key] = $val;
  83. }
  84. });
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function removeConfigSetting($name)
  90. {
  91. $authConfig = $this->authConfig;
  92. $this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) use ($authConfig) {
  93. if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|http-basic|platform)\.}', $key)) {
  94. list($key, $host) = explode('.', $key, 2);
  95. if ($authConfig) {
  96. unset($config[$key][$host]);
  97. } else {
  98. unset($config['config'][$key][$host]);
  99. }
  100. } else {
  101. unset($config['config'][$key]);
  102. }
  103. });
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. public function addProperty($name, $value)
  109. {
  110. $this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) {
  111. if (substr($key, 0, 6) === 'extra.') {
  112. $bits = explode('.', $key);
  113. $last = array_pop($bits);
  114. $arr =& $config['extra'];
  115. foreach ($bits as $bit) {
  116. if (!isset($arr[$bit])) {
  117. $arr[$bit] = array();
  118. }
  119. $arr =& $arr[$bit];
  120. }
  121. $arr[$last] = $val;
  122. } else {
  123. $config[$key] = $val;
  124. }
  125. });
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. public function removeProperty($name)
  131. {
  132. $authConfig = $this->authConfig;
  133. $this->manipulateJson('removeProperty', $name, function (&$config, $key) {
  134. if (substr($key, 0, 6) === 'extra.') {
  135. $bits = explode('.', $key);
  136. $last = array_pop($bits);
  137. $arr =& $config['extra'];
  138. foreach ($bits as $bit) {
  139. if (!isset($arr[$bit])) {
  140. return;
  141. }
  142. $arr =& $arr[$bit];
  143. }
  144. unset($arr[$last]);
  145. } else {
  146. unset($config[$key]);
  147. }
  148. });
  149. }
  150. /**
  151. * {@inheritdoc}
  152. */
  153. public function addLink($type, $name, $value)
  154. {
  155. $this->manipulateJson('addLink', $type, $name, $value, function (&$config, $type, $name, $value) {
  156. $config[$type][$name] = $value;
  157. });
  158. }
  159. /**
  160. * {@inheritdoc}
  161. */
  162. public function removeLink($type, $name)
  163. {
  164. $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) {
  165. unset($config[$type][$name]);
  166. });
  167. }
  168. protected function manipulateJson($method, $args, $fallback)
  169. {
  170. $args = func_get_args();
  171. // remove method & fallback
  172. array_shift($args);
  173. $fallback = array_pop($args);
  174. if ($this->file->exists()) {
  175. if (!is_writable($this->file->getPath())) {
  176. throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath()));
  177. }
  178. if (!is_readable($this->file->getPath())) {
  179. throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath()));
  180. }
  181. $contents = file_get_contents($this->file->getPath());
  182. } elseif ($this->authConfig) {
  183. $contents = "{\n}\n";
  184. } else {
  185. $contents = "{\n \"config\": {\n }\n}\n";
  186. }
  187. $manipulator = new JsonManipulator($contents);
  188. $newFile = !$this->file->exists();
  189. // override manipulator method for auth config files
  190. if ($this->authConfig && $method === 'addConfigSetting') {
  191. $method = 'addSubNode';
  192. list($mainNode, $name) = explode('.', $args[0], 2);
  193. $args = array($mainNode, $name, $args[1]);
  194. } elseif ($this->authConfig && $method === 'removeConfigSetting') {
  195. $method = 'removeSubNode';
  196. list($mainNode, $name) = explode('.', $args[0], 2);
  197. $args = array($mainNode, $name);
  198. }
  199. // try to update cleanly
  200. if (call_user_func_array(array($manipulator, $method), $args)) {
  201. file_put_contents($this->file->getPath(), $manipulator->getContents());
  202. } else {
  203. // on failed clean update, call the fallback and rewrite the whole file
  204. $config = $this->file->read();
  205. $this->arrayUnshiftRef($args, $config);
  206. call_user_func_array($fallback, $args);
  207. $this->file->write($config);
  208. }
  209. if ($newFile) {
  210. Silencer::call('chmod', $this->file->getPath(), 0600);
  211. }
  212. }
  213. /**
  214. * Prepend a reference to an element to the beginning of an array.
  215. *
  216. * @param array $array
  217. * @param mixed $value
  218. * @return array
  219. */
  220. private function arrayUnshiftRef(&$array, &$value)
  221. {
  222. $return = array_unshift($array, '');
  223. $array[0] = &$value;
  224. return $return;
  225. }
  226. }