AppController.php 9.06 KB
Newer Older
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

8 9 10
namespace yii\console\controllers;

use yii\console\Controller;
Qiang Xue committed
11
use yii\helpers\FileHelper;
12
use yii\base\Exception;
13

Qiang Xue committed
14
/**
15
 * This command creates an Yii Web application at the specified location.
Qiang Xue committed
16 17
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
18
 * @author Alexander Makarov <sam@rmcreative.ru>
19
 * @since 2.0
Qiang Xue committed
20
 */
21
class AppController extends Controller
Qiang Xue committed
22 23
{
	private $_rootPath;
24 25 26 27
	private $_config;

	/**
	 * @var string custom template path. If specified, templates will be
28
	 * searched there additionally to `framework/console/webapp`.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
	 */
	public $templatesPath;

	/**
	 * @var string application type. If not specified default application
	 * skeleton will be used.
	 */
	public $type = 'default';

	public function init()
	{
		parent::init();

		if($this->templatesPath && !is_dir($this->templatesPath)) {
			throw new Exception('--templatesPath "'.$this->templatesPath.'" does not exist or can not be read.');
		}
	}
Qiang Xue committed
46

47 48 49 50 51 52 53
	public function globalOptions()
	{
		return array('templatesPath', 'type');
	}

	public function actionIndex()
	{
54
		$this->forward('help/index', array('-args' => array('app/create')));
55 56
	}

Qiang Xue committed
57
	/**
58 59
	 * Generates Yii application at the path specified via appPath parameter.
	 *
Alexander Makarov committed
60
	 * @param string $path the directory where the new application will be created.
61 62
	 * If the directory does not exist, it will be created. After the application
	 * is created, please make sure the directory has enough permissions.
63 64
	 *
	 * @throws \yii\base\Exception if path specified is not valid
65
	 * @return integer the exit status
Qiang Xue committed
66
	 */
67
	public function actionCreate($path)
Qiang Xue committed
68
	{
69 70 71 72 73 74
		$path = strtr($path, '/\\', DIRECTORY_SEPARATOR);
		if(strpos($path, DIRECTORY_SEPARATOR) === false) {
			$path = '.'.DIRECTORY_SEPARATOR.$path;
		}
		$dir = rtrim(realpath(dirname($path)), '\\/');
		if($dir === false || !is_dir($dir)) {
75
			throw new Exception("The directory '$path' is not valid. Please make sure the parent directory exists.");
76
		}
77

78 79 80 81 82 83
		if(basename($path) === '.') {
			$this->_rootPath = $path = $dir;
		}
		else {
			$this->_rootPath = $path = $dir.DIRECTORY_SEPARATOR.basename($path);
		}
84 85 86 87 88

		if($this->confirm("Create \"$this->type\" application under '$path'?")) {
			$sourceDir = $this->getSourceDir();
			$config = $this->getConfig();

Qiang Xue committed
89
			$list = $this->buildFileList($sourceDir, $path);
90 91 92 93 94 95 96 97 98

			if(is_array($config)) {
				foreach($config as $file => $settings) {
					if(isset($settings['handler'])) {
						$list[$file]['callback'] = $settings['handler'];
					}
				}
			}

Qiang Xue committed
99
			$this->copyFiles($list);
100 101 102 103 104 105 106 107 108

			if(is_array($config)) {
				foreach($config as $file => $settings) {
					if(isset($settings['permissions'])) {
						@chmod($path.'/'.$file, $settings['permissions']);
					}
				}
			}

Qiang Xue committed
109 110 111 112
			echo "\nYour application has been created successfully under {$path}.\n";
		}
	}

113
	/**
114 115
	 * @throws \yii\base\Exception if source directory wasn't located
	 * @return string
116
	 */
117
	protected function getSourceDir()
Qiang Xue committed
118
	{
119 120 121 122 123 124 125 126 127 128 129 130
		$customSource = realpath($this->templatesPath.'/'.$this->type);
		$defaultSource = realpath($this->getDefaultTemplatesPath().'/'.$this->type);

		if($customSource) {
			return $customSource;
		}
		elseif($defaultSource) {
			return $defaultSource;
		}
		else {
			throw new Exception('Unable to locate the source directory for "'.$this->type.'".');
		}
Qiang Xue committed
131 132
	}

133
	/**
134
	 * @return string default templates path
135
	 */
136
	protected function getDefaultTemplatesPath()
Qiang Xue committed
137
	{
138
		return realpath(__DIR__.'/../webapp');
Qiang Xue committed
139 140
	}

141
	/**
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
	 * @return array|null template configuration
	 */
	protected function getConfig()
	{
		if($this->_config===null) {
			$this->_config = require $this->getDefaultTemplatesPath().'/config.php';
			if($this->templatesPath && file_exists($this->templatesPath)) {
				$this->_config = array_merge($this->_config, require $this->templatesPath.'/config.php');
			}
		}
		if(isset($this->_config[$this->type])) {
			return $this->_config[$this->type];
		}
	}

	/**
	 * @param string $source path to source file
	 * @param string $pathTo path to file we want to get relative path for
	 * @param string $varName variable name w/o $ to replace value with relative path for
161
	 *
Alexander Makarov committed
162
	 * @return string target file contents
163
	 */
164
	public function replaceRelativePath($source, $pathTo, $varName)
Qiang Xue committed
165
	{
166
		$content = file_get_contents($source);
167 168 169 170 171 172
		$relativeFile = str_replace($this->getSourceDir(), '', $source);

		$relativePath = $this->getRelativePath($pathTo, $this->_rootPath.$relativeFile);
		$relativePath = str_replace('\\', '\\\\', $relativePath);

		return preg_replace('/\$'.$varName.'\s*=(.*?);/', "\$".$varName."=$relativePath;", $content);
Qiang Xue committed
173 174
	}

175
	/**
Qiang Xue committed
176 177
	 * @param string $path1 absolute path
	 * @param string $path2 absolute path
178 179 180 181
	 *
	 * @return string relative path
	 */
	protected function getRelativePath($path1, $path2)
Qiang Xue committed
182
	{
183 184 185 186
		$segs1 = explode(DIRECTORY_SEPARATOR, $path1);
		$segs2 = explode(DIRECTORY_SEPARATOR, $path2);
		$n1 = count($segs1);
		$n2 = count($segs2);
Qiang Xue committed
187

188 189
		for($i=0; $i<$n1 && $i<$n2; ++$i) {
			if($segs1[$i] !== $segs2[$i]) {
Qiang Xue committed
190
				break;
191
			}
Qiang Xue committed
192 193
		}

194
		if($i===0) {
Qiang Xue committed
195
			return "'".$path1."'";
196
		}
Qiang Xue committed
197
		$up='';
198
		for($j=$i;$j<$n2-1;++$j) {
Qiang Xue committed
199
			$up.='/..';
200 201
		}
		for(; $i<$n1-1; ++$i) {
Qiang Xue committed
202
			$up.='/'.$segs1[$i];
203
		}
Qiang Xue committed
204

205
		return '__DIR__.\''.$up.'/'.basename($path1).'\'';
Qiang Xue committed
206
	}
Qiang Xue committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296


	/**
	 * Copies a list of files from one place to another.
	 * @param array $fileList the list of files to be copied (name=>spec).
	 * The array keys are names displayed during the copy process, and array values are specifications
	 * for files to be copied. Each array value must be an array of the following structure:
	 * <ul>
	 * <li>source: required, the full path of the file/directory to be copied from</li>
	 * <li>target: required, the full path of the file/directory to be copied to</li>
	 * <li>callback: optional, the callback to be invoked when copying a file. The callback function
	 *   should be declared as follows:
	 *   <pre>
	 *   function foo($source,$params)
	 *   </pre>
	 *   where $source parameter is the source file path, and the content returned
	 *   by the function will be saved into the target file.</li>
	 * <li>params: optional, the parameters to be passed to the callback</li>
	 * </ul>
	 * @see buildFileList
	 */
	protected function copyFiles($fileList)
	{
		$overwriteAll = false;
		foreach($fileList as $name=>$file) {
			$source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR);
			$target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR);
			$callback = isset($file['callback']) ? $file['callback'] : null;
			$params = isset($file['params']) ? $file['params'] : null;

			if(is_dir($source)) {
				if (!is_dir($target)) {
					mkdir($target, 0777, true);
				}
				continue;
			}

			if($callback !== null) {
				$content = call_user_func($callback, $source, $params);
			}
			else {
				$content = file_get_contents($source);
			}
			if(is_file($target)) {
				if($content === file_get_contents($target)) {
					echo "  unchanged $name\n";
					continue;
				}
				if($overwriteAll) {
					echo "  overwrite $name\n";
				}
				else {
					echo "      exist $name\n";
					echo "            ...overwrite? [Yes|No|All|Quit] ";
					$answer = trim(fgets(STDIN));
					if(!strncasecmp($answer, 'q', 1)) {
						return;
					}
					elseif(!strncasecmp($answer, 'y', 1)) {
						echo "  overwrite $name\n";
					}
					elseif(!strncasecmp($answer, 'a', 1)) {
						echo "  overwrite $name\n";
						$overwriteAll = true;
					}
					else {
						echo "       skip $name\n";
						continue;
					}
				}
			}
			else {
				if (!is_dir(dirname($target))) {
					mkdir(dirname($target), 0777, true);
				}
				echo "   generate $name\n";
			}
			file_put_contents($target, $content);
		}
	}

	/**
	 * Builds the file list of a directory.
	 * This method traverses through the specified directory and builds
	 * a list of files and subdirectories that the directory contains.
	 * The result of this function can be passed to {@link copyFiles}.
	 * @param string $sourceDir the source directory
	 * @param string $targetDir the target directory
	 * @param string $baseDir base directory
	 * @param array $ignoreFiles list of the names of files that should
297
	 * be ignored in list building process.
Qiang Xue committed
298 299 300 301 302 303 304 305 306
	 * @param array $renameMap hash array of file names that should be
	 * renamed. Example value: array('1.old.txt'=>'2.new.txt').
	 * @return array the file list (see {@link copyFiles})
	 */
	protected function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array())
	{
		$list = array();
		$handle = opendir($sourceDir);
		while(($file = readdir($handle)) !== false) {
307
			if(in_array($file, array('.', '..', '.svn', '.gitignore', '.hgignore')) || in_array($file, $ignoreFiles)) {
Qiang Xue committed
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
				continue;
			}
			$sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file;
			$targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap);
			$name = $baseDir === '' ? $file : $baseDir.'/'.$file;
			$list[$name] = array(
				'source' => $sourcePath,
				'target' => $targetPath,
			);
			if(is_dir($sourcePath)) {
				$list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap));
			}
		}
		closedir($handle);
		return $list;
	}
Zander Baldwin committed
324
}