1: <?php namespace Laravel\CLI\Tasks\Migrate;
2:
3: use Laravel\Str;
4: use Laravel\File;
5: use Laravel\Bundle;
6: use Laravel\CLI\Tasks\Task;
7: use Laravel\Database\Schema;
8:
9: class Migrator extends Task {
10:
11: /**
12: * The migration resolver instance.
13: *
14: * @var Resolver
15: */
16: protected $resolver;
17:
18: /**
19: * The migration database instance.
20: *
21: * @var Database
22: */
23: protected $database;
24:
25: /**
26: * Create a new instance of the Migrator CLI task.
27: *
28: * @param Resolver $resolver
29: * @param Database $database
30: * @return void
31: */
32: public function __construct(Resolver $resolver, Database $database)
33: {
34: $this->resolver = $resolver;
35: $this->database = $database;
36: }
37:
38: /**
39: * Run a database migration command.
40: *
41: * @param array $arguments
42: * @return void
43: */
44: public function run($arguments = array())
45: {
46: // If no arguments were passed to the task, we will just migrate
47: // to the latest version across all bundles. Otherwise, we will
48: // parse the arguments to determine the bundle for which the
49: // database migrations should be run.
50: if (count($arguments) == 0)
51: {
52: $this->migrate();
53: }
54: else
55: {
56: $this->migrate(array_get($arguments, 0));
57: }
58: }
59:
60: /**
61: * Run the outstanding migrations for a given bundle.
62: *
63: * @param string $bundle
64: * @param int $version
65: * @return void
66: */
67: public function migrate($bundle = null, $version = null)
68: {
69: $migrations = $this->resolver->outstanding($bundle);
70:
71: if (count($migrations) == 0)
72: {
73: echo "No outstanding migrations.";
74:
75: return;
76: }
77:
78: // We need to grab the latest batch ID and increment it by one.
79: // This allows us to group the migrations so we can easily
80: // determine which migrations need to roll back.
81: $batch = $this->database->batch() + 1;
82:
83: foreach ($migrations as $migration)
84: {
85: $migration['migration']->up();
86:
87: echo 'Migrated: '.$this->display($migration).PHP_EOL;
88:
89: // After running a migration, we log its execution in the migration
90: // table so that we can easily determine which migrations we'll
91: // reverse in the event of a migration rollback.
92: $this->database->log($migration['bundle'], $migration['name'], $batch);
93: }
94: }
95:
96: /**
97: * Rollback the latest migration command.
98: *
99: * @param array $arguments
100: * @return bool
101: */
102: public function rollback($arguments = array())
103: {
104: $migrations = $this->resolver->last();
105:
106: // If bundles supplied, filter migrations to rollback only bundles'
107: // migrations.
108: if (count($arguments) > 0)
109: {
110: $bundles = $arguments;
111:
112: if ( ! is_array($bundles)) $bundles = array($bundles);
113:
114: $migrations = array_filter($migrations, function($migration) use ($bundles)
115: {
116: return in_array($migration['bundle'], $bundles);
117: });
118: }
119:
120: if (count($migrations) == 0)
121: {
122: echo "Nothing to rollback.".PHP_EOL;
123:
124: return false;
125: }
126:
127: // The "last" method on the resolver returns an array of migrations,
128: // along with their bundles and names. We will iterate through each
129: // migration and run the "down" method.
130: foreach (array_reverse($migrations) as $migration)
131: {
132: $migration['migration']->down();
133:
134: echo 'Rolled back: '.$this->display($migration).PHP_EOL;
135:
136: // By only removing the migration after it has successfully rolled back,
137: // we can re-run the rollback command in the event of any errors with
138: // the migration and pick up where we left off.
139: $this->database->delete($migration['bundle'], $migration['name']);
140: }
141:
142: return true;
143: }
144:
145: /**
146: * Rollback all of the executed migrations.
147: *
148: * @param array $arguments
149: * @return void
150: */
151: public function reset($arguments = array())
152: {
153: while ($this->rollback($arguments)) {};
154: }
155:
156: /**
157: * Reset the database to pristine state and run all migrations
158: *
159: * @param array $arguments
160: * @return void
161: */
162: public function rebuild()
163: {
164: // Clean the database
165: $this->reset();
166:
167: echo PHP_EOL;
168:
169: // Re-run all migrations
170: $this->migrate();
171:
172: echo 'The database was successfully rebuilt'.PHP_EOL;
173: }
174:
175: /**
176: * Install the database tables used by the migration system.
177: *
178: * @return void
179: */
180: public function install()
181: {
182: Schema::table('laravel_migrations', function($table)
183: {
184: $table->create();
185:
186: // Migrations can be run for a specific bundle, so we'll use
187: // the bundle name and string migration name as a unique ID
188: // for the migrations, allowing us to easily identify which
189: // migrations have been run for each bundle.
190: $table->string('bundle', 50);
191:
192: $table->string('name', 200);
193:
194: // When running a migration command, we will store a batch
195: // ID with each of the rows on the table. This will allow
196: // us to grab all of the migrations that were run for the
197: // last command when performing rollbacks.
198: $table->integer('batch');
199:
200: $table->primary(array('bundle', 'name'));
201: });
202:
203: echo "Migration table created successfully.".PHP_EOL;
204: }
205:
206: /**
207: * Generate a new migration file.
208: *
209: * @param array $arguments
210: * @return string
211: */
212: public function make($arguments = array())
213: {
214: if (count($arguments) == 0)
215: {
216: throw new \Exception("I need to know what to name the migration.");
217: }
218:
219: list($bundle, $migration) = Bundle::parse($arguments[0]);
220:
221: // The migration path is prefixed with the date timestamp, which
222: // is a better way of ordering migrations than a simple integer
223: // incrementation, since developers may start working on the
224: // next migration at the same time unknowingly.
225: $prefix = date('Y_m_d_His');
226:
227: $path = Bundle::path($bundle).'migrations'.DS;
228:
229: // If the migration directory does not exist for the bundle,
230: // we will create the directory so there aren't errors when
231: // when we try to write the migration file.
232: if ( ! is_dir($path)) mkdir($path);
233:
234: $file = $path.$prefix.'_'.$migration.EXT;
235:
236: File::put($file, $this->stub($bundle, $migration));
237:
238: echo "Great! New migration created!";
239:
240: // Once the migration has been created, we'll return the
241: // migration file name so it can be used by the task
242: // consumer if necessary for further work.
243: return $file;
244: }
245:
246: /**
247: * Get the stub migration with the proper class name.
248: *
249: * @param string $bundle
250: * @param string $migration
251: * @return string
252: */
253: protected function stub($bundle, $migration)
254: {
255: $stub = File::get(path('sys').'cli/tasks/migrate/stub'.EXT);
256:
257: $prefix = Bundle::class_prefix($bundle);
258:
259: // The class name is formatted similarly to tasks and controllers,
260: // where the bundle name is prefixed to the class if it is not in
261: // the default "application" bundle.
262: $class = $prefix.Str::classify($migration);
263:
264: return str_replace('{{class}}', $class, $stub);
265: }
266:
267: /**
268: * Get the migration bundle and name for display.
269: *
270: * @param array $migration
271: * @return string
272: */
273: protected function display($migration)
274: {
275: return $migration['bundle'].'/'.$migration['name'];
276: }
277:
278: }
279: