localdisk

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

そろそろ Simple HTML DOM Parser を使うのはやめたほうがいい

Simple HTML DOM Parser といえば定番のスクレイピングライブラリで、僕も随分お世話になったわけだけど遅いし重いので Goutte 使ったほうがいいという話です。某サイトをスクレイピングするのに比較したら特にメモリ消費に大きな差がでました。

比較したスクリプトGithub においてありますので自由に使ってください。なおプロファイラは @koriym さんが Qiita にポストしたものを使用させていただきました。

プログラム

スクレイピング対象のサイトはこのブログにしました。あざといですね。タイトルのリストを出力するプログラムです。

Simple HTML DOM Parser

<?php

require_once './vendor/autoload.php';
require_once './profiler.php';
$html = \SimpleHtmlDom\str_get_html(file_get_contents('http://localdisk.hatenablog.com/'));
$nodes = $html->find('h1.entry-title');
foreach ($nodes as $node) {
    echo trim($node->text()) . "\n";
}
$html->clear();

Goutte

<?php

require_once './vendor/autoload.php';
require_once './profiler.php';

$client = new \Goutte\Client;
$crawler = $client->request('GET', 'http://localdisk.hatenablog.com/');
$crawler->filter('h1.entry-title')->each(function($node)
{
    echo trim($node->text()) . "\n";
});

結果

処理時間は大差ありませんが*1、メモリ消費にかなりの差が出ているのがわかると思います。プログラムを見てもわかるように使い方に大差ありません。むしろ Goutte のほうが Closure 使えて嬉しいですね。

Simple HTML DOM Parser

便利な日時操作ライブラリ Carbon
Response::xml macro を作ったよ
NHK番組表API for PHP を書いたよ
足りないインフラ力をAnsibleでまかなう
すべてのPOSTリクエストに対してCSRFフィルターを適用する
今年もよろしくお願いします
Laravel でトランザクション
完結済の俺が好きな漫画+
Laravel でWebアプリのインストーラーを作ってみる
Laravel 最速マスター

Memory: 9,014,440 / 10,773,432 bytes
Time: 1756.099939 ms
Declared: 141 classes
Ibcluded: 10 

Goutte

便利な日時操作ライブラリ Carbon
Response::xml macro を作ったよ
NHK番組表API for PHP を書いたよ
足りないインフラ力をAnsibleでまかなう
すべてのPOSTリクエストに対してCSRFフィルターを適用する
今年もよろしくお願いします
Laravel でトランザクション
完結済の俺が好きな漫画+
Laravel でWebアプリのインストーラーを作ってみる
Laravel 最速マスター

Memory: 2,417,136 / 2,437,208 bytes
Time: 1246.071815 ms
Declared: 205 classes
Ibcluded: 95 

考察

なぜこういう結果になるのか? Simple HTML DOM Parser は HTML の解析に正規表現を用いており、Goutte では DOMDocument を使用しています*2。おそらく正規表現よりは DOMDocument のほうが効率がいいんでしょうね。
余談ですが、DOMDocument では、filter メソッドの引数(この場合は h1.entry-title)のような指定では要素が取得できません。これは Simfony/CSSSelector が引数を解析して XPath に変換しているんですね。これは感心させられました。

*1:ややこしいことすると Goutte のほうがかなり速くなる

*2:正確には Symfony/DOMCrawler.

便利な日時操作ライブラリ Carbon

僕は頭が悪いので date 関数とか strtotime 関数とかの引数を覚えきれない。いい加減いやになったところで見つけたのが Carbon という日付操作ライブラリです。

このライブラリ、Laravel で使われてまして Modelupdated_atcreated_at は Carbon のインスタンスが入ってます。便利。みんな Laravel 使え。

使い方は Gist を参照してください。


Response::xml macro を作ったよ

Laravel って json を返すメソッド Response::json は用意されてるんだけど、XML を返すメソッドは用意されていない。API を作成しなければならない場合、いささか困る部分ではあります。なので作ってみた。

まず、 app の下に macros.php というファイルを作って下のコードをコピーしてください。

<?php
// macros.php
Response::macro('xml', function(array $vars, $status = 200, array $header = [], $xml = null)
{
    if (is_null($xml)) {
        $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><response/>');
    }
    foreach ($vars as $key => $value) {
        if (is_array($value)) {
            Response::xml($value, $status, $header, $xml->addChild($key));
        } else {
            $xml->addChild($key, $value);
        }
    }
    if (empty($header)) {
        $header['Content-Type'] = 'application/xml';
    }
    return Response::make($xml->asXML(), $status, $header);
});

gist も貼っておきますね。
Response::xml macro

Laravel 4.1 から Response クラスにも macro が定義できるようになりましたので、このように楽に作ることができます。

で、その次に作成した macros.php を使えるようにします。app/start/global.php を開いて一行追加します。

<?php
// global.php
require app_path() . '/macros.php';


準備は完了しました。使い方としてはこのようになります。

<?php
// routes.php
Route::get('api.{ext}', function()
{
    $data = ['status' => 'OK'];
    $ext = File::extension(Request::url());
    return Response::$ext($data);
})->where('ext', 'xml|json');

拡張子で判定して xml/json のどちらかを出力するコードです。where メソッドで拡張子xml/json のどちらかしか許可されません。これ以外の文字を入力すると HttpNotFoundException が発生します。

Laravel を楽しんでください。

NHK番組表API for PHP を書いたよ

非常によい試みだと思います。民放各局もぜひ追随していただきたいところです。

ものは試しということでさくっと書いてみました。

実装はかなり適当なので*1、まぁテスト程度に使っていただければと思います。

リクエストを投げるのに Guzzle を使ってます。よく使われるライブラリなので慣れてる人は多いのではないでしょうか。

使い方はテストをみていただければと思います。

追記

Packagist に登録したよ。
こんな感じで composer.json を書いてくれればインストールできます。

{
  "require": {
    "localdisk/nhk": "dev-master"
  }
}

*1:例外処理とか書いてないし、パラメータの受け渡しにもう一工夫できる

足りないインフラ力をAnsibleでまかなう

2014/01/22 追記あり

このエントリを書いたあと、いくつかアドバイスを頂いた。ありがとうございます。それを受けていくつか playbook を修正しています。


僕の感想。


みんなブログ書くといい。


Vagrant の使用には慣れたものの、年末 Chef に挫折。インフラ力の高まりは感じただけで終わってしまったのだった。いや、ここで諦めてはいけない。他の選択肢を検討に入れてみよう。Puppet …も難しそうだ。 Chef に挫折したことを考えると勝算は低い。
なので Ansible に挑戦してみた。

読むのが面倒な人のためのまとめ

Ansible は Chef/Puppet とくらべて

  • サーバーに Ansible をインストールする必要がない
  • サーバーの構成を定義する Playbook は YAML 形式で記述するので Ruby 力の低い人も安心

という利点があり、Chef に挫折した自分でも丸一日の学習でなんとかなるので学習コストはかなり低いと言える。
しかし、この手のツール全てに言えることだけど Chef/Puppet/Ansible を使ったからと言って低いインフラ力が高くなるわけではないし*1、実際は状況に合わせたこまめなメンテナンスが必要になる。こまめなメンテナンスや調整は高いインフラ力を必要とするわけで、結局のところVagrant 使って開発用のサーバを作って一人で開発するという使い方であればあんまり必要ないと思う。

結論:得意なやつに任せろ。

目標

Ansible を使用して速やかにサーバ環境を作成する。デプロイに関しては考えない。
実益を兼ねて以下のアプリケーションをインストールできるまで頑張りたい。

前提

以後の作業は以下のバージョンで進めていきます

インストール

インストールのページを見るにPythonは2.6以上が良いみたいだ。あと Windows は諦めろって書いてある。*2 もうほんと Windows では開発作業に支障がでるレベルになってて不憫である。主に仕事で使わざるを得ない自分が。

今回は Mac なので pip という Python のパッケージマネージャを使用してインストールする。

$ easy_install pip

自分のPCでは 1.4.1 がインストールされてた。

$ sudo pip install ansible

で、Enter。最初うっかり sudo 抜きでやってしまい

error: could not create '/usr/share/ansible': Permission denied

と怒られることになってしまったので注意が必要だ。うまくいくとコンソールにいろいろ出力されて最終的に

Successfully installed ansible paramiko jinja2 PyYAML httplib2 pycrypto ecdsa markupsafe

と出れば OK だと思う。

バージョンは 1.4.4 のようだ。

$ ansible --version
ansible 1.4.4

接続

まずは Vagrant での接続を試してみよう。というわけで Vagrantfile を用意する。

そして起動。

$ vagrant up

ssh に登録。

$ vagrant ssh-config >> ~/.ssh/config

すると ~/.ssh/configVagrant の情報が追記される。

Host node
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile ~/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

その後に Ansible のインベントリファイルというやつを作ってやる。Ansible を適用する接続情報だと思っておけばOKだと思う。多分。ここでは hosts というファイル名にする。

$ cat hosts

してこのような内容にしておこう

[vagrant]
192.168.33.10

そして Ansible から ping をうってみよう。

$ ansible -i hosts all -m ping

すると

192.168.33.10 | FAILED => SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue

繋がっ…てない。アルェー? とりあえず debug 情報を出すために -vvv オプションをつけてみる。

$ ansible -i hosts all -m ping -vvv

デバッグ情報が出てきたが…わからん。多分 ~/.ssh/config に追記された内容がおかしいような気がするので修正してみる。

Host 192.168.33.10
  HostName 192.168.33.10
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile ~/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

Host と HostName を書き換えてみると…。

$ ansible -i hosts all -m ping
192.168.33.10 | success >> {
    "changed": false,
    "ping": "pong"
}

うまくいった。理由はわからない。

playbook

Ansible では接続を定義するインベントリファイルとサーバの構成を管理する playbook というファイルが最低限必要になる。Best Practices という推奨された構成があるが、慣れてから手を付けたほうが良いように思う。
Chef をちょっと触っただけでわかった気になった自分の knife の切れ味に絶望して何もかもを放り出してしまったあの痛ましい事件を忘れてはならない。

簡単な playbook を作って実行する

playbook は yaml で記述する。ここが Ansible の良いところだと僕は思っている。Ruby 力が足りないプログラマでも優しいのだ。デザイナさんにもおすすめできるのもよい。

$ touch playbook.yml

で作って

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - debug: msg="Hello! Ansible!"

と書いてみよう。早速実行?いや、まずはチェックだ。

$ ansible-playbook -i hosts --syntax-check playbook.yml

を実行しよう。typo があった場合は怒られるぞ。では、エラーがなかったので実行しよう。

$ ansible-playbook -i hosts --syntax-check playbook.yml
PLAY [192.168.33.10] **********************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.33.10]

TASK: [debug msg="Hello! Ansible!"] *******************************************
ok: [192.168.33.10] => {
    "msg": "Hello! Ansible!"
}

PLAY RECAP ********************************************************************
192.168.33.10              : ok=2    changed=0    unreachable=0    failed=0

よし、では色々とインストールしていこう。

Nginx のインストール

まずは Nginx のインストールだ playbook.yml に追記してと…

  tasks:
    - name: install nginx
      apt:  pkg=nginx state=latest
      notify: restart nginx

うん?失敗する。apt-get update しろと言ってくる。そりゃそうか。では別に pre.ymlを用意して…

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: updates a server
      apt: update_cache=yes
    - name: upgrade a server
      apt: upgrade=full

そして実行。時間がかかるので少し待つべし。

$ ansible-playbook -i hosts pre.yml

終わったら再び

$ ansible-playbook -i hosts playbook.yml

OKだ。vagrant ssh してバージョンを調べてみると…

vagrant@precise64:~$ nginx -v
nginx version: nginx/1.1.19

古い…。仕方がないので Ubuntu 12.04 LTS に最新の Nginx をインストールする - xykの日記 を参考にして操作を playbook に落としていく。

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: update sources list1
      apt_repository: repo="deb http://nginx.org/packages/ubuntu/ precise nginx"
    - name: update sources list2
      apt_repository: repo="deb-src http://nginx.org/packages/ubuntu/ precise nginx"
    - name: PGP Key Add
      apt_key: url=http://nginx.org/keys/nginx_signing.key
    - name: install nginx
      apt:  pkg=nginx update_cache=yes
    - name: restart nginx
      service: name=nginx state=restarted enabled=yes

これで nginx の version が 1.4.4 になった。
sourceslist の追加が苦肉の策すぎてブログに載せるをためらうレベルだけど、誰かが教えてくれることを祈ってそのままにしておく。

(2014/01/22追記)
lineinfile モジュールの部分を apt_repository モジュールを使用するよう変更した。

MySQL のインストール

これはわりとすんなりと。今思ったけど、ファイル分けるより tag をつけたほうがいいね。

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: install mysql
      apt: pkg=mysql-server
    - name: start mysql
      service: name=mysql state=started enabled=yes

PHP のインストール

ppa を足すときに、python-pycurl のインストールを求められるくらいで特に引っかかるところはなかった。

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: install python-pycurl
      apt: pkg=python-pycurl
    - name: add php repository
      apt_repository: repo='ppa:ondrej/php5'
    - name: install php
      apt: pkg={{ item }} update_cache=yes
      with_items:
        - php5
        - php5-cli
        - php5-fpm
        - php-pear
        - php5-dev
        - php-apc
        - php5-mcrypt
        - php5-gd
        - php5-curl
        - php5-xdebug
        - php5-sqlite

Composer のインストール

command: curl -sS https://getcomposer.org/installer | php と書くと "|" を誤認識(?)するらしく以下の様なエラーになる。

failed: [192.168.33.10] => {"changed": true, "cmd": ["curl", "-sS", "https://getcomposer.org/installer", "|", "php"], "delta": "0:00:41.804834", "end": "2014-01-19 11:42:57.120323", "rc": 6, "start": "2014-01-19 11:42:15.315489"}
stderr: curl: (6) Couldn't resolve host '|'

仕方がないので vagrant-ansible-symfony2/devops/webserver.yml at master · tvieira/vagrant-ansible-symfony2 · GitHub から引用。

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: install composer
      shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin
    - name: move composer.phar to composer
      command: mv /usr/bin/composer.phar /usr/bin/composer

(2014/01/22追記)
command モジュールの部分を shell モジュールを使用するよう変更した。

Redis をインストールする

apt だと 2.2.2 とバージョンが古い。自分は詳しくないのだが、これでよいのだろうか?*3。ビルドするにはjprichardson/ansible-redis · GitHubが詳しい。
もう疲れてきた…。

HHVM のインストール

これで最後。この Playbook だけはぐぐっても見つからなかったので、このエントリでは唯一価値あるものだと思う。

- hosts: 192.168.33.10
  user: vagrant
  sudo: yes
  tasks:
    - name: Add HHVM Key
      apt_key: url=http://dl.hhvm.com/conf/hhvm.gpg.key
    - name: Add Sources List
      apt_repository: repo="deb http://dl.hhvm.com/ubuntu precise main"
    - name: Install HHVM
      apt: pkg=hhvm-fastcgi update_cache=yes

(2014/01/22追記)
lineinfile モジュールの部分を apt_repository モジュールを使用するよう変更した。

なんのことはない。簡単である。

*1:少しはましになるとは思う

*2:Windows isn’t supported for the control machine

*3:2014/01/19 現在の stable は 2.8.4

すべてのPOSTリクエストに対してCSRFフィルターを適用する

Laravel のフィルターって本来はこう書くわけですが

<?php
Route::post('user/register', ['before' => 'csrf', function()
{
    return 'You are over 200 years old!';
}]);

POST するたびにいちいち before… とか書きたくない。漏れがあったらやだし。なのでちょっとぐぐってみたところ stackoverflow の記事がひっかかりました。

php - Laravel 4 CSRF on all POST requests - Stack Overflow

…なんか苦肉の策感がすごい。いやいや、もうちょっとどうにかなるだろ、と思って調べてみたらどうにかなったよ。

こんな感じ。自力でフィルターを呼んでやるわけです。コメントにも書いてますが $request->getRealMethod() とすれば POST / PUT / DELETE / PATCH のリクエストの際にも CSRF のフィルターが呼ばれます。

追記

コントローラーでやりたい人はこうすればOK.
CSRF on all POST requests (Controller-based) | Laravel-Tricks.com

今年もよろしくお願いします

去年のふりかえり

脱ニートしてIT土方へとジョブチェンジしました。「ジョブチェンジ先ミスってんだろ!」というツッコミはまぁそのとおり*1。仕事ではほとんどJavaを書いてました。*2それも Struts2というこの先どこに活かせばいいのかよくわからないフレームワーク*3 にどっぷり浸かるというわけのわからないことに。
さらに通勤時間が現在往復4時間強と拷問というか苦行みたいな感じ*4になっており、大分県の片田舎からの引っ越しを現在真剣に考えています。

今年の目標

IT土方というか、まぁIT土方はどうでもよくて

  • 誰が見るんだそれ、というドキュメント
  • 無理やりカバレッジ 100% にするユニットテスト
  • どんなに好意的に解釈しても実現不可能な設計書

という闇から距離を置きたいというのが目標です。
あと、PHP 書きたい。レガシー状態からモダンに書きなおす案件とかやってみたい。

というわけで、今年は転職活動頑張ります。よろしくお願いします。

おみくじ

大吉でした。

*1:大分県って思ったより仕事なかった。あと SIer の闇が深すぎる

*2:PHP は一ヶ月だけ

*3:あと myBatis とか良さがよくわからない ORM

*4:副作用として遅刻しなくなった

Laravel でトランザクション

表題の通り。


こんな感じ。Model の中でできると楽という話もあるかもですが、Model 毎にトランザクションされると一つのアクションで複数の Model を更新するときに困るのでこれでいいんじゃないかな。

完結済の俺が好きな漫画+

完結済の俺が好きな漫画ベスト50を発表する!:わんこーる速報!
年末に良い記事だなぁと思ったのでご紹介。ついでに僕のおすすめも足してみようかと。一人の作者につき一作品でいきます。一応コメントを入れてますが、参考にならないです。でも、どれも面白いので機会があったら読んでみてください。

あずまんが大王 全3巻 完結セット (少年サンデーコミックススペシャル)

あずまんが大王 全3巻 完結セット (少年サンデーコミックススペシャル)

いわゆる萌4コマ漫画の金字塔。よつばと!も面白い。大阪と榊さんが好き。
EDEN(1)

EDEN(1)

8巻まで読め。8巻までは最高に面白い。すごい乾いた漫画です。現在作者はオールラウンダー廻を連載中。これも面白い。
河よりも長くゆるやかに (小学館文庫)

河よりも長くゆるやかに (小学館文庫)

吉田秋生はすごく好きな漫画家でカリフォルニア物語とこちらで随分迷ったが、こっちをおすすめ。現在作者は海街diaryを連載中。読め。
サーフサイドハイスクール 1 (レアミクス コミックス)

サーフサイドハイスクール 1 (レアミクス コミックス)

ここからはギャグ漫画を3作ほど紹介します。今は亡きヤングサンデーで連載されていたサーフサイドハイスクール。1997年に休載して長らく未完のままだったけど2010年に描きおろしの最終回を含んだ完全版をリリース。13年の時を経て完結しました。
デビューマン 1 (ヤングキングコミックス)

デビューマン 1 (ヤングキングコミックス)

面白いんだけどなー。知ってる人が少ない。作者は寡作な人で1巻と2巻の間が7年開いてる。正直2巻がでると思ってなかった。さすがに3巻はでないだろう。
この作品とサーフサイドハイスクールは稲中卓球部の「中学のバカ」じゃなくて「高校のバカ」。シモネタがより生々しくてよい。
ハルシオン・ランチ 1 (アフタヌーンKC)

ハルシオン・ランチ 1 (アフタヌーンKC)

「最近腹を抱えるくらい笑える漫画ないなー」とか思ってた時に、「無限の住人」と一緒に買ってきて笑い転げた。おひっこしも面白い。
フラワー・オブ・ライフ (1) (ウィングス・コミックス)

フラワー・オブ・ライフ (1) (ウィングス・コミックス)

BL臭がしないので人に薦めやすい。主要人物全員片思いという仕組みはうまいなぁと思う。3月のライオンいいですよ。おっさんばっかで。
ヨコハマ買い出し紀行(1) (アフタヌーンKC)

ヨコハマ買い出し紀行(1) (アフタヌーンKC)

SF好きでも好きじゃなくても面白いので読むべし。
攻殻機動隊 (1)    KCデラックス

攻殻機動隊 (1) KCデラックス

コマの外にある注釈みたいなのが好き。
最終兵器彼女(1) (ビッグコミックス)

最終兵器彼女(1) (ビッグコミックス)

泣いた。幼なじみのアケミが好きだっただけにあのシーンはショックだった。
GUNSLINGER GIRL 1 (電撃コミックス)

GUNSLINGER GIRL 1 (電撃コミックス)

どうせ萌えマンガだろ、と食わず嫌いしてた過去の僕を殴りに行きたい。
エマ (1) (Beam comix)

エマ (1) (Beam comix)

この人の書く女性ってなんかエロい。乙嫁語り面白いし、絵をみて溜息出る。
ぼくんち 上 (角川文庫)

ぼくんち 上 (角川文庫)

連載開始時、「西原理恵子が小学館とか大丈夫か?」って思ったけど、今は新聞に連載とかしててなんか不思議な感じ。
医龍 1 (小学館文庫 のB 1)

医龍 1 (小学館文庫 のB 1)

医局政治な話なんだけど、読後感そんなに悪くない。
羊のうた (第1巻) (バーズコミックス)

羊のうた (第1巻) (バーズコミックス)

あの、あの、お願いですからイエスタデイをうたってだけは完結させてください。
皇国の守護者 1 (ヤングジャンプ・コミックス・ウルトラ)

皇国の守護者 1 (ヤングジャンプ・コミックス・ウルトラ)

原作も完結していないことに絶望した。

眠いのでここで終わり。

余談

アカギの最新刊で鷲巣が鬼と鬼ごっこしてるんだけど、どういうことなの…。

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 作るだけ