CMSなどで画像をアップロードする機能を実装する際にその画像をDBに保存すると、サーバーを冗長化する際やサムネールを作成する機能を実装する際にいろいろと便利になるので、そのやり方をご紹介します。

DBに保存するメリット

  1. サーバーを冗長化する際に、画像をファイルとして保存するとそれぞれのサーバー間でアップロードデータの同期がひつようとなったり、NASを導入する必要があったりするが、DBに保存してしまえばその必要がなくなる。
  2. サムネール画像を作成するなどの機能で、途中で仕様変更等によりサムネール画像のサイズを変更したい場合などの、画像の出力フォーマットの変更などにも柔軟に対応できる。

DBに保存するデメリット

  1. DBに保存してそれを読み出すプログラムを経由する分、処理が多くなり必然的に負荷の増大につながる。
  2. DBのメンテナンス性の低下につながりやすい。
  3. レンタルサーバーなどで制限が出てくる。(たとえばMySQLのMAX_ALLOWED_PACKETなど)

DBに保存するときのコツ

上記のデメリットのうち付加が増大する点については、mod_rewriteを使用することで解決できます。

  1. 画像を出力する際にキャッシュとして出力結果を保存する。
  2. mod_rewriteでキャッシュファイルがある場合はPHPスクリプトにアクセスさせないで画像に直接アクセスするようにする。

DBスキーマサンプル

以下のようなテーブルを作成する。

画像のアップロードプログラムであらかじめ、mime_typeや画像の高さや幅を取得しておいてDBに保存しておくと、出力時になにかと都合がいいことが多いです。

画像のコンテンツは、m_contentに保存します。
base64でフォーマットするというような例も多いようですが、ここでは生で保存します。

CREATE TABLE `tbl_bin` (
 `m_id` varchar(32) NOT NULL,
 `m_content` longblob NOT NULL,
 `mime_type` varchar(100) NOT NULL,
 `width` smallint(5) unsigned NOT NULL,
 `height` smallint(5) unsigned NOT NULL,
 `m_modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
 PRIMARY KEY  (`m_id`),
 KEY `m_modified` (`m_modified`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

保存用PHPプログラム

以下はあくまでもサンプルです。
画像ファイルのコンテンツは以下のような感じでSQL文に挿入します。

$image = mysql_real_escpa_string(file_get_contents( $_FILE['image']['tmp_name'] ));

画像の幅や高さmime-typeは、getimagesize()で取得してください。

m_idには拡張子つきのファイル名を保存するのがベターです。
重複を避けるためにユーザーがつけたファイル名はさけて、自動的にファイル名を生成するようにしたほうがいいとおもいます。

出力用PHPプログラム

以下の例では、プログラム名はmedia.phpで、キャッシュを保存するディレクトリ名はmedia/であることを前提にしています。
これらを変更したい場合は、必要に応じて読み替えてください。

もっとも簡単な例では、以下のようなプログラムで画像の出力が可能です。
(あらかじめDBに接続する構文を記述してください。)

// URLからファイル名の部分を取得
$url= parse_url($_SERVER['REQUEST_URI']);
$id = basename($url['path']);

// DBから画像データを取得
$sql = "select m_content, mime_type from tbl_bin
  where m_id='".mysql_real_escape_string($id)."' limit 0,1";
$result = mysql_query($sql);

if (mysql_num_rows($result)) {
  $data = mysql_fetch_assoc($result);
    // キャッシュを保存
    $fp = fopen(dirname(__FILE__).'/media/'.$id, 'w');
    fwrite($fp, $data['m_content']);
    fclose($fp);
    // 画像を出力
    header('Content-type: '.$data['mime_type'].';');
    print $data['m_content'];
}else{
    header("HTTP/1.0 404 Not Found");
    print 'file not found';
}

.htaccessを設置

上述のPHPスクリプトで、アクセスがあった際に画像を保存するようにしたので、次はmod_rewriteでキャッシュがあればそのキャッシュを使用するようにする。

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule media/ media.php [L]

テスト

実際に画像(media.php)にアクセスしてみてキャッシュができるかどうかを確認したあと、media.phpをあえてリネームするなどして、同じURLで画像が再び表示されればOK。

以降は画像にアクセスがあってもDBへ接続は発生しません。

画像が増えすぎてディスク容量を圧迫するようなら、Cronで定期的に古い画像を削除するなどの処理を行うことで緩和されます。