MigrateHelper.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <?php
  2. namespace common\helpers;
  3. use Yii;
  4. use yii\base\BaseObject;
  5. use yii\base\InvalidConfigException;
  6. use yii\db\MigrationInterface;
  7. use yii\web\NotFoundHttpException;
  8. use yii\web\UnprocessableEntityHttpException;
  9. /**
  10. * Class MigrateHelper
  11. * @package common\helpers
  12. * @author jianyan74 <751393839@qq.com>
  13. */
  14. class MigrateHelper
  15. {
  16. /**
  17. * 最长数据迁移数量
  18. */
  19. const MAX_NAME_LENGTH = 180;
  20. /**
  21. * 输出安装过程
  22. *
  23. * @var bool
  24. */
  25. protected static $compact = false;
  26. /**
  27. * 目录
  28. *
  29. * @addons/RfHelpers/console/migrations
  30. *
  31. * @var array
  32. */
  33. protected static $migrationPath = [];
  34. /**
  35. * 命名空间
  36. *
  37. * @var array
  38. */
  39. protected static $migrationNamespaces = [];
  40. /**
  41. * 数据迁移过程
  42. *
  43. * @var array
  44. */
  45. protected static $info = [];
  46. /**
  47. * 根据路径执行数据迁移
  48. *
  49. * @param array $path
  50. * @param bool $compact
  51. * @throws InvalidConfigException
  52. * @throws NotFoundHttpException
  53. * @throws UnprocessableEntityHttpException
  54. */
  55. public static function upByPath(array $path, $compact = false)
  56. {
  57. self::$migrationPath = $path;
  58. self::$compact = $compact;
  59. if (empty(self::$migrationPath)) {
  60. throw new InvalidConfigException('At least one of `migrationPath` should be specified.');
  61. }
  62. foreach (self::$migrationPath as $i => $path) {
  63. self::$migrationPath[$i] = Yii::getAlias($path);
  64. }
  65. return self::up();
  66. }
  67. /**
  68. *
  69. * 根据命名空间执行数据迁移
  70. *
  71. * @param array $namespaces
  72. * @param bool $compact
  73. * @throws InvalidConfigException
  74. * @throws NotFoundHttpException
  75. * @throws UnprocessableEntityHttpException
  76. */
  77. public static function upByNamespaces(array $namespaces, $compact = false)
  78. {
  79. self::$migrationNamespaces = $namespaces;
  80. self::$compact = $compact;
  81. if (empty(self::$migrationNamespaces)) {
  82. throw new InvalidConfigException('At least one of `migrationNamespaces` should be specified.');
  83. }
  84. foreach (self::$migrationNamespaces as $key => $value) {
  85. self::$migrationNamespaces[$key] = trim($value, '\\');
  86. }
  87. return self::up();
  88. }
  89. /**
  90. * 根据路径执行数据迁移
  91. *
  92. * @param array $path
  93. * @param bool $compact
  94. * @throws InvalidConfigException
  95. * @throws NotFoundHttpException
  96. * @throws UnprocessableEntityHttpException
  97. */
  98. public static function downByPath(array $path, $compact = false)
  99. {
  100. self::$migrationPath = $path;
  101. self::$compact = $compact;
  102. if (empty(self::$migrationPath)) {
  103. throw new InvalidConfigException('At least one of `migrationPath` should be specified.');
  104. }
  105. foreach (self::$migrationPath as $i => $path) {
  106. self::$migrationPath[$i] = Yii::getAlias($path);
  107. }
  108. return self::down();
  109. }
  110. /**
  111. *
  112. * 根据命名空间执行数据迁移
  113. *
  114. * @param array $namespaces
  115. * @param bool $compact
  116. * @throws InvalidConfigException
  117. * @throws NotFoundHttpException
  118. * @throws UnprocessableEntityHttpException
  119. */
  120. public static function downByNamespaces(array $namespaces, $compact = false)
  121. {
  122. self::$migrationNamespaces = $namespaces;
  123. self::$compact = $compact;
  124. if (empty(self::$migrationNamespaces)) {
  125. throw new InvalidConfigException('At least one of `migrationNamespaces` should be specified.');
  126. }
  127. foreach (self::$migrationNamespaces as $key => $value) {
  128. self::$migrationNamespaces[$key] = trim($value, '\\');
  129. }
  130. return self::down();
  131. }
  132. /**
  133. * @param int $limit
  134. * @throws InvalidConfigException
  135. * @throws NotFoundHttpException
  136. * @throws UnprocessableEntityHttpException
  137. */
  138. protected static function up($limit = 0)
  139. {
  140. $migrations = self::getNewMigrations();
  141. if (empty($migrations)) {
  142. throw new NotFoundHttpException('找不到可用的数据迁移');
  143. }
  144. $limit = (int)$limit;
  145. if ($limit > 0) {
  146. $migrations = array_slice($migrations, 0, $limit);
  147. }
  148. foreach ($migrations as $migration) {
  149. $nameLimit = static::MAX_NAME_LENGTH;
  150. if ($nameLimit !== null && strlen($migration) > $nameLimit) {
  151. throw new UnprocessableEntityHttpException("The migration name '$migration' is too long. Its not possible to apply this migration.");
  152. }
  153. }
  154. $applied = 0;
  155. foreach ($migrations as $migration) {
  156. if (!self::migrateUp($migration)) {
  157. throw new UnprocessableEntityHttpException( $migration . '迁移失败了。其余的迁移被取消');
  158. }
  159. $applied++;
  160. }
  161. return self::$info;
  162. }
  163. /**
  164. * @return array
  165. * @throws InvalidConfigException
  166. * @throws NotFoundHttpException
  167. * @throws UnprocessableEntityHttpException
  168. */
  169. protected static function down()
  170. {
  171. $migrations = self::getNewMigrations();
  172. if (empty($migrations)) {
  173. throw new NotFoundHttpException('找不到可用的数据迁移');
  174. }
  175. $reverted = 0;
  176. foreach ($migrations as $migration) {
  177. if (!self::migrateDown($migration)) {
  178. throw new UnprocessableEntityHttpException( $migration . '迁移失败了。其余的迁移被取消');
  179. }
  180. $reverted++;
  181. }
  182. self::$info[] = "Migrated down successfully.";
  183. return self::$info;
  184. }
  185. /**
  186. * @param $class
  187. * @return bool
  188. * @throws InvalidConfigException
  189. */
  190. protected static function migrateUp($class)
  191. {
  192. self::$info[] = "*** applying $class";
  193. // 打开输出缓冲区并获取内容
  194. ob_start();
  195. $start = microtime(true);
  196. $migration = self::createMigration($class);
  197. if ($migration->up() !== false) {
  198. $tmpInfo = explode('>', ob_get_contents());
  199. foreach ($tmpInfo as $item) {
  200. !empty(trim($item)) && self::$info[] = $item;
  201. }
  202. $time = microtime(true) - $start;
  203. self::$info[] = "*** applied $class (time: " . sprintf('%.3f', $time) . "s)";
  204. ob_end_clean();
  205. return true;
  206. }
  207. ob_end_clean();
  208. $time = microtime(true) - $start;
  209. self::$info[] = "*** failed to apply $class (time: " . sprintf('%.3f', $time) . "s)n";
  210. return false;
  211. }
  212. /**
  213. * @param $class
  214. * @return bool
  215. * @throws InvalidConfigException
  216. */
  217. protected static function migrateDown($class)
  218. {
  219. self::$info[] = "*** reverting $class";
  220. $start = microtime(true);
  221. // 打开输出缓冲区并获取内容
  222. ob_start();
  223. $migration = self::createMigration($class);
  224. if ($migration->down() !== false) {
  225. $time = microtime(true) - $start;
  226. self::$info[] = "*** reverted $class (time: " . sprintf('%.3f', $time) . "s)";
  227. ob_end_clean();
  228. return true;
  229. }
  230. ob_end_clean();
  231. $time = microtime(true) - $start;
  232. self::$info[] = "*** failed to revert $class (time: " . sprintf('%.3f', $time) . "s)";
  233. return false;
  234. }
  235. /**
  236. * @param $class
  237. * @return MigrationInterface
  238. * @throws InvalidConfigException
  239. */
  240. protected static function createMigration($class)
  241. {
  242. self::includeMigrationFile($class);
  243. /** @var MigrationInterface $migration */
  244. $migration = Yii::createObject($class);
  245. if ($migration instanceof BaseObject && $migration->canSetProperty('compact')) {
  246. $migration->compact = self::$compact;
  247. }
  248. return $migration;
  249. }
  250. /**
  251. * 包含给定迁移类名称的迁移文件
  252. *
  253. * @param $class
  254. */
  255. protected static function includeMigrationFile($class)
  256. {
  257. $class = trim($class, '\\');
  258. if (strpos($class, '\\') === false) {
  259. if (is_array(self::$migrationPath)) {
  260. foreach (self::$migrationPath as $path) {
  261. $file = $path . DIRECTORY_SEPARATOR . $class . '.php';
  262. if (is_file($file)) {
  263. require_once $file;
  264. break;
  265. }
  266. }
  267. } else {
  268. $file = self::$migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
  269. require_once $file;
  270. }
  271. }
  272. }
  273. /**
  274. * 获取数据迁移文件
  275. *
  276. * @return array
  277. */
  278. protected static function getNewMigrations()
  279. {
  280. $migrationPaths = [];
  281. if (is_array(self::$migrationPath)) {
  282. foreach (self::$migrationPath as $path) {
  283. $migrationPaths[] = [$path, ''];
  284. }
  285. } elseif (!empty(self::$migrationPath)) {
  286. $migrationPaths[] = [self::$migrationPath, ''];
  287. }
  288. foreach (self::$migrationNamespaces as $namespace) {
  289. $migrationPaths[] = [self::getNamespacePath($namespace), $namespace];
  290. }
  291. $migrations = [];
  292. foreach ($migrationPaths as $item) {
  293. list($migrationPath, $namespace) = $item;
  294. if (!file_exists($migrationPath)) {
  295. continue;
  296. }
  297. $handle = opendir($migrationPath);
  298. while (($file = readdir($handle)) !== false) {
  299. if ($file === '.' || $file === '..') {
  300. continue;
  301. }
  302. $path = $migrationPath . DIRECTORY_SEPARATOR . $file;
  303. if (preg_match('/^(m(\d{6}_?\d{6})\D.*?)\.php$/is', $file, $matches) && is_file($path)) {
  304. $class = $matches[1];
  305. if (!empty($namespace)) {
  306. $class = $namespace . '\\' . $class;
  307. }
  308. $time = str_replace('_', '', $matches[2]);
  309. $migrations[$time . '\\' . $class] = $class;
  310. }
  311. }
  312. closedir($handle);
  313. }
  314. ksort($migrations);
  315. return array_values($migrations);
  316. }
  317. /**
  318. * 根据命名空间获取路径
  319. *
  320. * @param $namespace
  321. * @return mixed
  322. */
  323. private static function getNamespacePath($namespace)
  324. {
  325. return str_replace('/', DIRECTORY_SEPARATOR, Yii::getAlias('@' . str_replace('\\', '/', $namespace)));
  326. }
  327. }
粤ICP备19079148号