localdisk

PHP とか Java とか Web とか好きなことを書きます。

Laravel でWebアプリのインストーラーを作ってみる

このエントリはLaravel Advent Calendar 2013の23日目の記事です。

実はパッケージを作った話をしようと思ったのですが、死ぬほど地味で Laravel ほとんど関係なくなった*1ので別のネタで。

20日目でこんな記事がありました。
laravel4を使ったアプリケーション配布時の工夫 - Qiita [キータ]

これをもうちょっとカジュアルにLaravelっぽくやってみようという試みです。WordPress のインストーラーを思い浮かべると処理としては大体以下のような感じ。

  1. インストールされているかチェック
    1. DB につながっているかつ、テーブルが作成されていればインストール済みとみなす
  2. インストール画面から接続情報を取得して DB に接続できるように設定ファイルを書き換える
  3. DB に繋がったらマイグレーションファイルからテーブルを作成
  4. シーダーから初期データをインポート

みたいな感じになると思います。

では、まずインストールされているかどうかのチェックを行いましょう。これは routes.php よりは filters.php に書くほうがいいでしょう。

<?php
App::before(function($request)
{
    // パスチェック. インストール時はチェックしない
    if (strpos($request->path(), 'install') !== false) {
        return;
    }
    $installed = false;
    try {
        // データベースにつながるならOK
        // あるいはインストール後に設定ファイルに書き込んで
        // それをチェックするのもありかも
        $connection = DB::connection();
        $connection->table('posts');
        $installed = true;
    } catch (Exception $exc) {
        return Redirect::to('install');
    }
});

Laravel の場合、コネクションが取得できなかった場合、例外が発生しますので catch 節に達した場合は install にリダイレクトします。ここにインストール画面を表示させます。

<?php
Route::get('install', function() {
    return View::make('install');
});

インストール画面はこんなかんじで

<!DOCTYPE HTML>
<html lang="ja-JP">
    <head>
        <title>install</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
        <style type="text/css" media="screen">
            .container {
                max-width: 400px;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="page-header">
                <h1>install</h1>
                <?= Form::open(['url' => 'install/execute']) ?>
                <div class="form-group">
                    <label for="db">DB</label>
                    <select id="db" name="db" class="form-control">
                        <option value="mysql">MySQL</option>
                        <option value="pgsql">PostgreSQL</option>
                        <option value="sqlite">SQLite</option>
                        <option value="sqlsrv">SQLServer</option>
                    </select>
                </div>
                <div class="form-group">
                    <label for="host">HOST</label>
                    <input type="text" id="host" name="host" class="form-control" value="<?= Input::old('host') ?>" />
                </div>
                <div class="form-group">
                    <label for="database">DB</label>
                    <input type="text" id="database" name="database" class="form-control" value="<?= Input::old('database') ?>" />
                </div>
                <div class="form-group">
                    <label for="username">UserName</label>
                    <input type="text" id="username" name="username" class="form-control" value="<?= Input::old('username') ?>" />
                </div>
                <div class="form-group">
                    <label for="password">Password</label>
                    <input type="password" id="password" name="password" class="form-control" value="" />
                </div>
                <div class="form-group">
                    <input type="submit" class="btn btn-primary" value="Submit" />
                </div>
                <?= Form::close() ?>
            </div>
        </div>
    </body>
</html>

で、ここからが本処理。やることは

です。設定ファイルの書き換えはvar_export 関数を使って設定ファイルを書き換えてみる - localdiskを参考にしてください。今回の肝は Artisan::call を使用して SQL ファイルを用意することなくちゃちゃっとデータを整えちゃおうというところです。ここまで長かった…。

<?php
Route::post('install/execute', function() {
    $rules = [
        'db' => 'required',
        'host' => 'required',
        'database' => 'required',
        'username' => 'required',
        'password' => 'required'
    ];
    $val = Validator::make(Input::all(), $rules);
    if ($val->fails()) {
        return Redirect::back()->withInput()->withErrors($val);
    }
    // 設定ファイルを書き換える
    $config = Config::get('database');
    $config['default'] = Input::get('db');
    $config['connections'][Input::get('db')]['host'] = Input::get('host');
    $config['connections'][Input::get('db')]['database'] = Input::get('database');
    $config['connections'][Input::get('db')]['username'] = Input::get('username');
    $config['connections'][Input::get('db')]['password'] = Input::get('password');
    
    $file = '<?php' . "\n" . var_export($config, true) . ';';
    File::put(app_path('config') . '/database.php', $file);
    
    try {
        // マイグレーション
        Artisan::call('migrate:install'); // マイグレーション導入
        Artisan::call('migrate');         // マイグレーション実行
        // 初期データ投入
        Artisan::call('db:seed');
    } catch (Exception $e) {
        return Response::make($e->getMessage(), 500);
    }
    return View::make('install/complete');
});

まとめ

作りがすごい甘かったり、ちゃんと動かしてないコードですがおおよその流れは掴めたかと思います。年内にはちゃんと書いて github にあげておきます。

*1:ServiceProvider と Facade 作るだけ