Completed
Push — master ( 67d50f...8a7d08 )
by Nazar
04:56
created

Composer::file_put_json()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   Composer
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2015-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\Composer;
10
use
11
	cs\Config,
12
	cs\Event,
13
	cs\Singleton,
14
	Symfony\Component\Console\Input\ArrayInput;
15
16
/**
17
 * Provides next events:
18
 *  Composer/generate_package
19
 *  [
20
 *   'package' => &$package, //Composer package generated, might be modified
21
 *   'meta'    => $meta      //Parsed `meta.json` for component, package is generated for
22
 *  ]
23
 *
24
 *  Composer/generate_composer_json
25
 *  [
26
 *   'composer_json' => &$composer_json //`composer.json` structure that will be used for dependencies installation, might be modified
27
 *   'auth_json'     => &$auth_json     //`auth.json` structure that will be used for auth credentials during dependencies installation, might be modified
28
 *  ]
29
 *
30
 *  Composer/Composer
31
 *  [
32
 *   'Composer' => $Composer //Instance of `\Composer\Composer`, so that it is possible, for instance, to inject some Composer plugins manually
33
 *  ]
34
 *
35
 *  Composer/updated
36
 *  [
37
 *   'composer_json' => $composer_json, //`composer.json` structure that was used for dependencies installation
38
 *   'composer_lock' => $composer_lock  //`composer.lock` structure that was generated during dependencies installation
39
 *   'composer_root' => $composer_root  //Path to directory where dependencies were installed, and where `composer.json` and `composer.lock` are located
40
 *  ]
41
 *
42
 * @method static $this instance($check = false)
43
 */
44
class Composer {
45
	use
46
		Singleton;
47
	const MODE_ADD    = 1;
48
	const MODE_DELETE = 2;
49
	/**
50
	 * Force update even if nothing changed
51
	 *
52
	 * @var bool
53
	 */
54
	protected $force_update = false;
55
	protected function construct () {
56
		require_once 'phar://'.__DIR__.'/composer.phar/src/bootstrap.php';
57
	}
58
	/**
59
	 * Update composer even if nothing changed
60
	 *
61
	 * @return array
62
	 */
63
	function force_update () {
64
		$this->force_update = true;
65
		return $this->update();
66
	}
67
	/**
68
	 * Update composer
69
	 *
70
	 * @param string $component_name Is specified if called before component actually installed (to satisfy dependencies)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $component_name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
71
	 * @param int    $mode           Composer::MODE_ADD or Composer::MODE_DELETE
72
	 *
73
	 * @return array Array with `code` and `description` elements, first represents status code returned by composer, second contains ANSI text returned by
74
	 *               composer
75
	 */
76
	function update ($component_name = null, $mode = self::MODE_ADD) {
77
		time_limit_pause();
78
		$storage     = STORAGE.'/Composer';
79
		$status_code = 0;
80
		$description = '';
81
		$this->prepare($storage);
82
		$composer_json = $this->generate_composer_json($component_name, $mode);
83
		$Config        = Config::instance();
84
		$auth_json     = _json_decode($Config->module('Composer')->auth_json ?: '[]');
85
		$Event         = Event::instance();
86
		$Event->fire(
87
			'Composer/generate_composer_json',
88
			[
89
				'composer_json' => &$composer_json,
90
				'auth_json'     => &$auth_json
91
			]
92
		);
93
		if ($composer_json['repositories']) {
94
			$this->file_put_json("$storage/tmp/composer.json", $composer_json);
95
			$this->file_put_json("$storage/tmp/auth.json", $auth_json);
96
			if (
97
				$this->force_update ||
98
				!file_exists("$storage/composer.json") ||
99
				md5_file("$storage/tmp/composer.json") != md5_file("$storage/composer.json")
100
			) {
101
				$this->force_update = false;
102
				$Application        = new Application(
103
					function ($Composer) use ($Event, &$Application) {
104
						$Event->fire(
105
							'Composer/Composer',
106
							[
107
								'Application' => $Application,
108
								'Composer'    => $Composer
109
							]
110
						);
111
					}
112
				);
113
				$verbosity          = !DEBUG && $Config->core['simple_admin_mode'] ? '-vv' : '-vvv';
114
				$input              = new ArrayInput(
115
					[
116
						'command'               => 'update',
117
						'--working-dir'         => "$storage/tmp",
118
						'--no-dev'              => true,
119
						'--ansi'                => true,
120
						'--prefer-dist'         => true,
121
						'--optimize-autoloader' => true,
122
						$verbosity              => true
123
					]
124
				);
125
				$output             = new Output;
126
				$Application->setAutoExit(false);
127
				$status_code = $Application->run($input, $output);
128
				$description = $output->get_buffer();
129
				if ($status_code == 0) {
130
					rmdir_recursive("$storage/vendor");
131
					@unlink("$storage/composer.json");
132
					@unlink("$storage/composer.lock");
133
					rename("$storage/tmp/vendor", "$storage/vendor");
134
					rename("$storage/tmp/composer.json", "$storage/composer.json");
135
					rename("$storage/tmp/composer.lock", "$storage/composer.lock");
136
					$Event->fire(
137
						'Composer/updated',
138
						[
139
							'composer_json' => file_get_json("$storage/composer.json"),
140
							'composer_lock' => file_get_json("$storage/composer.lock"),
141
							'composer_root' => $storage
142
						]
143
					);
144
				}
145
			}
146
		} else {
147
			rmdir_recursive("$storage/vendor");
148
			@unlink("$storage/composer.json");
149
			@unlink("$storage/composer.lock");
150
		}
151
		$this->cleanup($storage);
152
		time_limit_pause(false);
153
		return [
154
			'code'        => $status_code,
155
			'description' => $description
156
		];
157
	}
158
	/**
159
	 * @param string $filename
160
	 * @param array  $data
161
	 *
162
	 * @return int
163
	 */
164
	protected function file_put_json ($filename, $data) {
165
		return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
166
	}
167
	/**
168
	 * @param string $storage
169
	 */
170
	protected function prepare ($storage) {
171
		if (!is_dir($storage)) {
172
			/** @noinspection MkdirRaceConditionInspection */
173
			@mkdir($storage, 0770);
174
		}
175
		rmdir_recursive("$storage/home");
176
		/** @noinspection MkdirRaceConditionInspection */
177
		@mkdir("$storage/home", 0770);
178
		rmdir_recursive("$storage/tmp");
179
		/** @noinspection MkdirRaceConditionInspection */
180
		@mkdir("$storage/tmp", 0770);
181
		putenv("COMPOSER_HOME=$storage/home");
182
		@ini_set('display_errors', 1);
183
		@ini_set('memory_limit', '512M');
184
		@unlink("$storage/last_execution.log");
185
	}
186
	/**
187
	 * @param string $component_name
188
	 * @param int    $mode `self::MODE_ADD` or `self::MODE_DELETE`
189
	 *
190
	 * @return array Resulting `composer.json` structure in form of array
191
	 */
192
	protected function generate_composer_json ($component_name, $mode) {
193
		$composer = [
194
			'repositories' => [],
195
			'require'      => []
196
		];
197
		$Config   = Config::instance();
198
		foreach (array_keys($Config->components['modules']) as $module) {
199
			if (
200
				$module == $component_name &&
201
				$mode == self::MODE_DELETE
202
			) {
203
				continue;
204
			}
205
			if (
206
				file_exists(MODULES."/$module/meta.json") &&
207
				(
208
					!$Config->module($module)->uninstalled() ||
209
					$component_name == $module
210
				)
211
			) {
212
				$this->generate_package(
213
					$composer,
214
					file_get_json(MODULES."/$module/meta.json")
215
				);
216
			}
217
		}
218
		return $composer;
219
	}
220
	/**
221
	 * @param array $composer
222
	 * @param array $meta
223
	 */
224
	protected function generate_package (&$composer, $meta) {
225
		if (!isset($meta['version'])) {
226
			return;
227
		}
228
		$package_name = "$meta[category]/$meta[package]";
229
		$package      = [
230
			'name'    => $package_name,
231
			'version' => $meta['version'],
232
			'type'    => 'metapackage',
233
			'require' => isset($meta['require_composer']) ? $meta['require_composer'] : []
234
		];
235
		if ($meta['package'] == 'Composer') {
236
			$package['replace'] = file_get_json(__DIR__.'/packages_bundled_with_system.json');
237
		}
238
		Event::instance()->fire(
239
			'Composer/generate_package',
240
			[
241
				'package' => &$package,
242
				'meta'    => $meta
243
			]
244
		);
245
		if (!$package['require'] && !isset($package['replace'])) {
246
			return;
247
		}
248
		$composer['repositories'][]         = [
249
			'type'    => 'package',
250
			'package' => $package
251
		];
252
		$composer['require'][$package_name] = $meta['version'];
253
	}
254
	/**
255
	 * @param string $storage
256
	 */
257
	protected function cleanup ($storage) {
258
		rmdir_recursive("$storage/home");
259
		rmdir_recursive("$storage/tmp");
260
	}
261
}
262