image Rio das Ostras, Brazil, 06/2010

Unbreakable Laravel Migrations

<p>Did you ever broke Laravel migrations and had to fix the database manually? When creating/editing tables and columns, for applications or packages, sometimes I just mess the things up and get stuck with a migration I cannot rollback, because a table was created but it not ended successfully so the record wasn't inserted in the migrations table, and can't go forward either, because the table has already been created and now it's in the way. What about not having that anymore?</p> <p>The problem, of course, isn't in Laravel, but in my inability to do things right in the first try. The answer for me was to wrap them in a transaction layer, so, if something bad happens during a migrate command, the whole process is rolled back and I can just fix the migration and try again. Let's create a migration base class step by step, it's very simple:</p> <p>Let's create an abstract class, so we don't risk using it directly:</p> <pre class="prettyprint"><code >abstract class Migration extends Illuminate\Database\Migrations\Migration { ... } </code></pre> <p>Laravel's <code class="spancode">up()</code> and <code class="spancode">down()</code> methods will have to be renamed, we declare them abstract here not only because it's a good practice, but also because some IDEs, like PHPStorm, will create code for those methods automatically:</p> <pre class="prettyprint"><code >abstract protected function migrateUp(); abstract protected function migrateDown(); </code></pre> <p>You'll need Laravel's Database Manager, the Database Connection and the Schema Builder:</p> <pre class="prettyprint"><code >public function __construct() { $this->manager = app()->make('db'); $this->connection = $this->manager->connection(); $this->builder = $this->connection->getSchemaBuilder(); } </code></pre> <p>Laravel's <code class="spancode">up()</code> and <code class="spancode">down()</code> methods will just call your new ones, wrapping them in a transaction:</p> <pre class="prettyprint"><code >public function up() { $this->executeInTransaction('migrateUp'); } public function down() { $this->executeInTransaction('migrateDown'); } </code></pre> <p>The <code class="spancode">executeInTransaction()</code> method is what does what we really need here, it starts a transaction, try to execute your migrate commands and commit them all, but, in case of a exception, it rolls back everything before forwarding the exception to your terminal:</p> <pre class="prettyprint"><code >protected function executeInTransaction($method) { $this->connection->beginTransaction(); try { $this->{$method}(); } catch (\Exception $exception) { $this->connection->rollback(); $this->handleException($exception); } $this->connection->commit(); } </code></pre> <p>The exception handler takes care of correctly throwing a QueryException or a generic one:</p> <pre class="prettyprint"><code >protected function handleException($exception) { if ($exception instanceof \Illuminate\Database\QueryException) { throw new $exception($exception->getMessage(), $exception->getBindings(), $exception->previous); } else { throw new $exception($exception->getMessage(), $exception->previous); } } </code></pre> <p>Also, sometimes the problem is just in a missing table, so instead of dropping them one by one, I put them in an array:</p> <pre class="prettyprint"><code >protected $tables = array(); </code></pre> <p>And use a <code class="spancode">dropAll()</code> method, checking if the table exists before trying to drop it.</p> <pre class="prettyprint"><code >protected function dropAllTables() { foreach($this->tables as $table) { if ($this->builder->hasTable($table)) { $this->builder->drop($table); } } } </code></pre> <p>You can download the full source code for the <strong>Migration</strong> class <a href="https://github.com/antonioribeiro/support/blob/master/src/Migration.php">here</a>:</p> <p>Or you can use my <a href="https://github.com/antonioribeiro/support">Support</a> package:</p> <pre class="prettyprint"><code >composer require "pragmarx/support":"0.2.*" </code></pre> <p>And then create your migrations using it:</p> <pre class="prettyprint"><code >class CreateUsersTable extends PragmaRX\Support\Migrator { protected function migrateUp() { protected $tables = ['users']; $this->builder->create('users', function($table) { $table->increments('id'); $table->string('email'); $table->string('password'); $table->timestamps(); }); } protected function migrateDown() { $this->dropAll(); } } </code></pre> <p>If you are now asking yourself, "but, how do I automatically generate my migrations this way?", the answer is Jeffrey Way's <a href="https://github.com/JeffreyWay/Laravel-4-Generators">Laravel-4-Generators</a>. Just publish all templates and edit the <code class="spancode">migration.txt</code> file. Then you just have to</p> <pre class="prettyprint"><code >php artisan generate:migration create_posts_table </code></pre> <p><strong>Enjoy Laravel!</strong></p>




comments powered by Disqus