忍者ブログ

STEP UP BLOG

Home > ブログ > 記事一覧

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

Laravelのマイグレーションロールバック

LaravelはRuby on Railsの精神をPHPでも!という方針で作られたフレームワークです。
したがってRuby on Railsの機能をいくつも受け継いでいます。
そのひとつがDBのテーブル定義に使われるマイグレーションです。
マイグレーションを利用すると、テーブル定義をPHPで記述出来るので、
特定のDBの仕様に依存せずに汎用的な定義をすることが出来ます。
また、テーブル定義の変更を履歴として持っているので、
特定の時間のテーブル定義に戻すこともロールバックで簡単に出来ます。
今回はそのLaravelのロールバックについてです。


Laravelのマイグレーションの基本はmigrationsテーブルです。
マイグレーションを実行すると、migrationsテーブルに実行したマイグレーションファイルが登録されます。
migrationsテーブルのカラムはmigrationとbatch。
migrationがファイル名で、batchが何回目のマイグレーションで実行したかの実行順となっています。
ロールバックするために
$ php artisan migrate:rollback

を実行すると、
batchが最も大きい、すなわちいちばん新しく実行したmigrationファイルのdown()を実行します。
そしてmigrationsテーブルからもそのデータが削除されます。
原理としてとてもシンプルです。


そのLaravelのマイグレーションについて、あるとき問題に気付きました。
Laravelのマイグレーションはmirationsテーブルとマイグレーションファイルの2つが揃っていることが前提です。
もしロールバックしようとして該当するマイグレーションファイルが存在しなかったらどうでしょう。
ロールバック出来なくてエラーとなります。
えー、でもファイルが無いとかあるの??
だって、ロールバックするには既にマイグレーション実行してなきゃいけないわけですよね。
しかしです。
しかし、gitで管理していてブランチをたくさん作っていると、
いろんなブランチにマイグレーションファイルが分散してしまう可能性があります。
でも他のブランチでわざわざロールバックすることなんてないでしょ。。
まあ、ないですよね。。
でも万が一が考えられます。
本当に万が一ですが。。
どのブランチでもmigrationsテーブルの内容とマイグレーションファイルを一致させておきたい。
その解決のひとつとして、
マイグレーション専用のブランチを作って、
どのブランチもそのマイグレーション専用ブランチを取り込むなどはどうでしょうか。
でもこのようにロールバックに失敗するシチュエーションなんてないですよねえ。。。
PR

readfile()での巨大ファイルの出力

むかーしむかし、あるところにおじいさんとおばあさんが住んでいました。
おじいさんは山へ芝刈りに、おばあさんは川へ洗濯に行きました。
おばあさんが川で洗濯をしていると、どんぶらこ、どんぶらこ、と大きなCSVファイルが流れてきました。
それはそれは大きく見事なCSVファイルだったので、おばあさんはダウンロードしようとしました。
しかし、ダウンロードしようとしたらブラウザが真っ白になってしまいました。
おやおや、困ったものだとおばあさんは途方に暮れました。
・・・・・・


というわけで巨大なファイルをダウンロード出来なかったという話です。
あらかじめ大きなファイルだということは認識していたので、
サーバ側PHPのファイル出力にはreadfile()を用いています。

http://php.net/manual/ja/function.readfile.php
注意: readfile() 自体にはメモリに関する問題はなく、 巨大なファイルを送ってもかまいません。out of memoryエラーが出る場合は、 ob_get_level() で出力バッファリングを無効にしてください。

マニュアルの注意書きにも、巨大なファイルを送ってもかまわないと書いてあります。
しかし、実際はエラー。
エラーログを確認するとメモリーのエラーです。
おやおや、とマニュアルの注意書きの"out of memoryエラーが出る場合は、 ob_get_level() で出力バッファリングを無効にしてください。"がなんだか怪しそうです。
試しにソースコードのファイル出力部分でob_get_level()を実行してみると、2と出ました。

http://php.net/manual/ja/function.ob-get-level.php
返り値 ¶ 出力バッファリングハンドラのネストレベルを返します。 バッファリングがアクティブでない場合はゼロを返します。

によると、この関数は出力バッファリングのネストレベルを返し、無効の場合は0を返すとのこと。
ということは、readfile()で巨大なファイルを出力するためにはob_get_level()が0でないといけない。
出力バッファリングを無効にするためにはob_end_clean()を実行すればよいとのことで、
実行してob_get_level()で確認したら0になるわけではなく2から1減ってネストレベルが1になっただけ。
デクリメントかい。
ob_get_level()が0になるまでob_end_clean()を実行します。
while (ob_get_clean()) {
    ob_end_clean();
}

このようにして出力バッファリングを無効にした後にreadfile()することで、
やっと巨大ファイルのダウンロードに成功しました。
めでたしめでたし。

PHPの参照渡しにハマる

ゲームに課金しすぎて依存症になるといった意味ではなくハマったので備忘録がてらブログに書こうと思います。
仕事柄PHPを書く機会が多く、というよりもほとんどPHPしか書いていないのですが、
そうなってくるとなんでもPHPで書きたくなってきます。
これシェルで書いたほうがいいのでは? という処理でも、
とりあえずPHPで出来るか考えてしまいます。
挙句の果てに、シェルコマンド便利だからPHPからシェル叩きましょうみたいな思考になっていき、本日の本題に入ってくるわけです。
PHPで外部プログラムを実行する手段としてはexec()やsystem()があります。
先日私が使ったのはexec()で、そのときにハマった箇所を再現出来そうな例を以下に示します。
<?php

exec('ls .', $output);

foreach ($output as $file) {
    exec('wc -m '.$file, $wc);
    echo ($wc[0]."\n");
}

やりたいこととしてはファイルの一覧を取ってきて、ファイル毎に文字数を出力するというものです。
exec('ls .', $output);

の第2引数に実行結果が配列で格納されます。
で、実行すると、、、
$ php wc.php 
8 a.txt
8 a.txt
8 a.txt
8 a.txt

なんだか同じファイルの文字数が出力されますね。。
少し変えて、
<?php

exec('ls .', $output);

foreach ($output as $file) {
    exec('wc -m '.$file, $wc);
    echo ($file.':'.$wc[0]."\n");
}

としてみると、
$ php wc.php 
a.txt:8 a.txt
b.txt:8 a.txt
c.txt:8 a.txt
wc.php:8 a.txt

wcはループでちゃんと実行されているっぽいけど、結果の出力がおかしい??
ここまできたらvar_dump()しちゃいましょう。
<?php

exec('ls .', $output);

foreach ($output as $file) {
    exec('wc -m '.$file, $wc);
    var_dump($wc);
}

えい!!
$ php wc.php 
array(1) {
  [0]=>
  string(7) "8 a.txt"
}
array(2) {
  [0]=>
  string(7) "8 a.txt"
  [1]=>
  string(8) "12 b.txt"
}
array(3) {
  [0]=>
  string(7) "8 a.txt"
  [1]=>
  string(8) "12 b.txt"
  [2]=>
  string(7) "4 c.txt"
}
array(4) {
  [0]=>
  string(7) "8 a.txt"
  [1]=>
  string(8) "12 b.txt"
  [2]=>
  string(7) "4 c.txt"
  [3]=>
  string(10) "192 wc.php"
}

配列に実行結果が追加されている。。
そうか。参照渡しだからこうなっているのか。。
というところで原点に立ち戻ってマニュアルを確認してみると、

http://php.net/manual/ja/function.exec.php
配列に既に何らかの要素が 含まれる場合は、exec() は配列の最後に追加されることに注意してください。関数が要素を追加することを望まないのなら、それが exec() に渡される前に、配列の unset() を呼び出してください。

パラメータの章でちゃんと注意がされていますね。。。
unset()しなさいという話。
というわけで最終的には以下のようなソースになって希望通りの動きとなりました。
<?php

exec('ls .', $output);

foreach ($output as $file) {
    unset($wc);
    exec('wc -m '.$file, $wc);
    echo ($wc[0]."\n");
}

$ php wc.php 
8 a.txt
12 b.txt
4 c.txt
190 wc.php

結論としては、
・まずはマニュアルをしっかり読みましょう。
・参照渡しはバグが入り込みやすいので、なるべく使わないにこしたことはないですよね。

再帰的にファイルだけ権限を変えたいとき

よくある作業で、あるディレクトリ以下のファイルの権限を全部変えたいときがあります。
そんなときに例えば以下のように
$ chmod -R 777 .

としてしまいがちです。
Enterキーを押してしまった人、ごめんなさい〜
これだと、ディレクトリの権限まで変わってしまいます。
私はファイルだけ権限を変えたかったんだ…
再帰的といいつつ、ファイルだけにchmodを適用したい。
そんなときはUnixお得意のパイプ処理です。
というわけで、ファイルだけ再帰的に権限を変更したい場合は以下。
$ find . -type f | xargs chmod 777

findの-typeオプションでファイルのみを絞って、それをxargsでchmodに渡します。
xargsを使うところが肝です。
man xargsしてみると、
名前
       xargs - 標準入力からコマンドラインを作成し、それを実行する

書式
       xargs   [-0prtx]   [-e[eof-str]]   [-i[replace-str]]   [-l[max-lines]]   [-n   max-args]   [-s   max-chars]  [-P  max-procs]  [--null]  [--eof[=eof-str]]
       [--replace[=replace-str]] [--max-lines[=max-lines]] [--interactive] [--max-chars=max-chars] [--verbose] [--exit] [--max-procs=max-procs] [--max-args=max-
       args] [--no-run-if-empty] [--version] [--help] [command [initial-arguments]]

説明
       このマニュアルページは GNU 版 xargs に関して記述したものである。 xargs はまず標準入力から空白または改行で区切られた文字列群を読み込む (空白はダブルクォー
       テーション・シングルクォーテーション・バックスラッシュによってプロテクトできる)。そして command (デフォルトは /bin/echo) に文字列群を続けたコマンドライン
       を実行する。 initial-arguments が指定されていれば、 command と標準入力から渡された文字列の間に、 command への引き数として渡される。標準入力の空行は無視さ
       れる。

つまり今回の場合はパイプで送られてきたファイル一覧を引数としてchmodを実行してくれます。
なるほど!
パイプを使うとunix使いこなせてる感マシマシになるので頭の片隅にでも留めておいてはいかがでしょうか。

ちなみに
$ find . -type d

だと、ディレクトリのみが抽出されます。

Laravel4.2でのファイル入出力

毎年、9月の終わり頃は季節の変わり目なのか体調を崩すのですが、今年も例に漏れず体調を崩してしまいました。
というわけでLaravelはWebアプリケーションのためのフレームワークでありますが、ファイル入出力ももちろんできます。
場合によってはDBに保存するだけでなく、ファイルに出力したり、ファイルからデータを取り込んだりするシーンも出てくると思います。
そのときに役に立つのが、Filesystemクラスです。

http://laravel.com/api/4.2/Illuminate/Filesystem/Filesystem.html

ファイル関連の処理をすべてラッピングしてくれて、煩雑な処理を見通しの良いコードに変えてくれます。
いくつか挙げていくと、
File::append('./abc.txt', 'hello!');

モードを指定して開くとかしなくてもappendで追記できます。
File::move('./abc.txt', './new_abc.txt');

ファイルの移動も
File::copy('./abc.txt', './new_abc.txt');

複製も簡単です。
File::extension('./abc.txt');
拡張子だって、Filesystemなら、ほらこの通り。
PHPのファイル入出力で検索しても出てくるのはネイティブのPHP関数を使ったものが多く、
確かにそれは基本中の基本なんですが、新しいフレームワークを使っているのだからそこもモダンに実装したいところ。
LaravelではそのためのFilesystemなので、ガシガシ使っていきましょう。

PAGE TOP