merged skunkworks into develop.

This commit is contained in:
Taylor Otwell
2012-01-16 13:59:24 -06:00
parent 610d8827c4
commit b5442c67fc
117 changed files with 7268 additions and 3999 deletions

View File

@@ -0,0 +1,83 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Database as DB;
class Database {
/**
* Log a migration in the migration table.
*
* @param string $bundle
* @param string $name
* @param int $batch
* @return void
*/
public function log($bundle, $name, $batch)
{
$this->table()->insert(compact('bundle', 'name', 'batch'));
}
/**
* Delete a row from the migration table.
*
* @param string $bundle
* @param string $name
* @return void
*/
public function delete($bundle, $name)
{
$this->table()->where_bundle_and_name($bundle, $name)->delete();
}
/**
* Return an array of the last batch of migrations.
*
* @return array
*/
public function last()
{
$table = $this->table();
// First we need to grab the last batch ID from the migration table,
// as this will allow us to grab the lastest batch of migrations
// that need to be run for a rollback command.
$id = $this->batch();
// Once we have the batch ID, we will pull all of the rows for that
// batch. Then we can feed the results into the resolve method to
// get the migration instances for the command.
return $table->where_batch($id)->order_by('name', 'desc')->get();
}
/**
* Get all of the migrations that have run for a bundle.
*
* @param string $bundle
* @return array
*/
public function ran($bundle)
{
return $this->table()->where_bundle($bundle)->lists('name');
}
/**
* Get the maximum batch ID from the migration table.
*
* @return int
*/
public function batch()
{
return $this->table()->max('batch');
}
/**
* Get a database query instance for the migration table.
*
* @return Query
*/
protected function table()
{
return DB::connection()->table('laravel_migrations');
}
}

View File

@@ -0,0 +1,235 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Str;
use Laravel\File;
use Laravel\Bundle;
use Laravel\CLI\Tasks\Task;
use Laravel\Database\Schema;
class Migrator extends Task {
/**
* The migration resolver instance.
*
* @var Resolver
*/
protected $resolver;
/**
* The migration database instance.
*
* @var Database
*/
protected $database;
/**
* Create a new instance of the Migrator CLI task.
*
* @param Resolver $resolver
* @param Database $database
* @return void
*/
public function __construct(Resolver $resolver, Database $database)
{
$this->resolver = $resolver;
$this->database = $database;
}
/**
* Run a database migration command.
*
* @param array $arguments
* @return void
*/
public function run($arguments = array())
{
// If no arguments were passed to the task, we will just migrate
// to the latest version across all bundles. Otherwise, we will
// parse the arguments to determine the bundle for which the
// database migrations should be run.
if (count($arguments) == 0)
{
$this->migrate();
}
else
{
$this->migrate(array_get($arguments, 0));
}
}
/**
* Run the outstanding migrations for a given bundle.
*
* @param string $bundle
* @param int $version
* @return void
*/
public function migrate($bundle = null, $version = null)
{
$migrations = $this->resolver->outstanding($bundle);
if (count($migrations) == 0)
{
echo "No outstanding migrations.";
return;
}
// We need to grab the latest batch ID and increment it
// by one. This allows us to group the migrations such
// that we can easily determine which migrations need
// to be rolled back for a given command.
$batch = $this->database->batch() + 1;
foreach ($migrations as $migration)
{
$migration['migration']->up();
echo 'Migrated: '.$this->display($migration).PHP_EOL;
// After running a migration, we log its execution in the
// migration table so that we can easily determine which
// migrations we will need to reverse on a rollback.
$this->database->log($migration['bundle'], $migration['name'], $batch);
}
}
/**
* Rollback the latest migration command.
*
* @param array $arguments
* @return bool
*/
public function rollback($arguments = array())
{
$migrations = $this->resolver->last();
if (count($migrations) == 0)
{
echo "Nothing to rollback.";
return false;
}
// The "last" method on the resolver returns an array of migrations,
// along with their bundles and names. We will iterate through each
// migration and run the "down" method, removing them from the
// database as we go.
foreach ($migrations as $migration)
{
$migration['migration']->down();
echo 'Rolled back: '.$this->display($migration).PHP_EOL;
// By only removing the migration after it has successfully rolled back,
// we can re-run the rollback command in the event of any errors with
// the migration. When we re-run, only the migrations that have not
// been rolled-back for the batch will still be in the database.
$this->database->delete($migration['bundle'], $migration['name']);
}
return true;
}
/**
* Rollback all of the executed migrations.
*
* @param array $arguments
* @return void
*/
public function reset($arguments = array())
{
while ($this->rollback()) {};
}
/**
* Install the database tables used by the migration system.
*
* @return void
*/
public function install()
{
Schema::table('laravel_migrations', function($table)
{
$table->create();
// Migrations can be run for a specific bundle, so we'll use
// the bundle name and string migration name as an unique ID
// for the migrations, allowing us to easily identify which
// migrations have been run for each bundle.
$table->string('bundle');
$table->string('name');
// When running a migration command, we will store a batch
// ID with each of the rows on the table. This will allow
// us to grab all of the migrations that were run for the
// last command when performing rollbacks.
$table->integer('batch');
$table->primary(array('bundle', 'name'));
});
echo "Migration table created successfully.";
}
/**
* Generate a new migration file.
*
* @param array $arguments
* @return void
*/
public function make($arguments = array())
{
if (count($arguments) == 0)
{
throw new \Exception("I need to know what to name the migration.");
}
list($bundle, $migration) = Bundle::parse($arguments[0]);
// The migration path is prefixed with the UNIX timestamp, which
// is a better way of ordering migrations than a simple integer
// incrementation, since developers may start working on the
// next migration at the same time unknowingly.
$date = date('Y_m_d').'_'.time();
$path = Bundle::path($bundle).'migrations/'.$date.'_'.$migration.EXT;
File::put($path, $this->stub($bundle, $migration));
echo "Great! New migration created!";
}
/**
* Get the stub migration with the proper class name.
*
* @param string $bundle
* @param string $migration
* @return string
*/
protected function stub($bundle, $migration)
{
$stub = File::get(SYS_PATH.'cli/tasks/migrate/stub'.EXT);
// The class name is formatted simialrly to tasks and controllers,
// where the bundle name is prefixed to the class if it is not in
// the default bundle. However, unlike tasks, there is nothing
// appended to the class name since they're already unique.
$class = Bundle::class_prefix($bundle).Str::classify($migration);
return str_replace('{{class}}', $class, $stub);
}
/**
* Get the migration bundle and name for display.
*
* @param array $migration
* @return string
*/
protected function display($migration)
{
return $migration['bundle'].'/'.$migration['name'];
}
}

View File

@@ -0,0 +1,159 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Bundle;
class Resolver {
/**
* The migration database instance.
*
* @var Database
*/
protected $database;
/**
* Create a new instance of the migration resolver.
*
* @param Database $datbase
* @return void
*/
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* Resolve all of the outstanding migrations for a bundle.
*
* @param string $bundle
* @return array
*/
public function outstanding($bundle = null)
{
$migrations = array();
// If no bundle was given to the command, we'll grab every bundle for
// the application, including the "application" bundle, which is not
// returned by "all" method on the Bundle class.
if (is_null($bundle))
{
$bundles = array_merge(Bundle::all(), array('application'));
}
else
{
$bundles = array($bundle);
}
foreach ($bundles as $bundle)
{
// First we need to grab all of the migrations that have already
// run for this bundle, as well as all of the migration files
// for the bundle. Once we have these, we can determine which
// migrations are still outstanding.
$ran = $this->database->ran($bundle);
$files = $this->migrations($bundle);
// To find outstanding migrations, we will simply iterate over
// the migration files and add the files that do not exist in
// the array of ran migrations to the outstanding array.
foreach ($files as $key => $name)
{
if ( ! in_array($name, $ran))
{
$migrations[] = compact('bundle', 'name');
}
}
}
return $this->resolve($migrations);
}
/**
* Resolve an array of the last batch of migrations.
*
* @return array
*/
public function last()
{
return $this->resolve($this->database->last());
}
/**
* Resolve an array of migration instances.
*
* @param array $migrations
* @return array
*/
protected function resolve($migrations)
{
$instances = array();
foreach ($migrations as $migration)
{
$migration = (array) $migration;
// The migration array contains the bundle name, so we will get the
// path to the bundle's migrations and resolve an instance of the
// migration using the name.
$bundle = $migration['bundle'];
$path = Bundle::path($bundle).'migrations/';
// Migrations are not resolved through the auto-loader, so we will
// manually instantiate the migration class instances for each of
// the migration names we're given.
$name = $migration['name'];
require_once $path.$name.EXT;
// Since the migration name will begin with the numeric ID, we'll
// slice off the ID so we are left with the migration class name.
// The IDs are for sorting when resolving outstanding migrations.
//
// Migrations that exist within bundles other than the default
// will be prefixed with the bundle name to avoid any possible
// naming collisions with other bundle's migrations.
$prefix = Bundle::class_prefix($bundle);
$class = $prefix.substr($name, 22);
$migration = new $class;
// When adding to the array of instances, we will actually
// add the migration instance, the bundle, and the name.
// This allows the migrator to log the bundle and name
// when the migration is executed.
$instances[] = compact('bundle', 'name', 'migration');
}
return $instances;
}
/**
* Grab all of the migration filenames for a bundle.
*
* @param string $bundle
* @return array
*/
protected function migrations($bundle)
{
$files = glob(Bundle::path($bundle).'migrations/*_*'.EXT);
// Once we have the array of files in the migration directory,
// we'll take the basename of the file and remove the PHP file
// extension, which isn't needed.
foreach ($files as &$file)
{
$file = str_replace(EXT, '', basename($file));
}
// We'll also sort the files so that the earlier migrations
// will be at the front of the array and will be resolved
// first by this class' resolve method.
sort($files);
return $files;
}
}

View File

@@ -0,0 +1,25 @@
<?php
class {{class}} {
/**
* Make changes to the database.
*
* @return void
*/
public function up()
{
//
}
/**
* Revert the changes to the database.
*
* @return void
*/
public function down()
{
//
}
}