質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.80%

PHPでMySQLに保存されている画像データが表示されない

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 4,079

score 12

ラージオブジェクト (LOB) を参考に、
MySQLでmediumblob型の画像データが保存されているのですが、
うまく取得し表示することができません。

ログにはfpassthru()の第一引数にresouceではなくてstringが指定されていますと言われているので、
型変換みたいなのが必要なのでしょうか?

参考元のコメント欄を見ても同じエラーになった人がいるようです。

MySQL Workbench から登録したバイナリデータをダウンロードすると
正しく画像が表示されているので、登録時の処理には問題ないと思っています。

どこに問題があるのか指摘していただけませんでしょうか。

ログ

Warning:  fpassthru() expects parameter 1 to be resource, string given in /app/php/show_image.php

ソース

show_image.php

$pdo = new PDO('mysql:*****');
  $stmt = $pdo->query("SELECT type, imgdat FROM test_table LIMIT 1");
  $stmt->bindColumn(1, $typ, PDO::PARAM_STR, 256);
  $stmt->bindColumn(2, $dat, PDO::PARAM_LOB);
  $stmt->fetch(PDO::FETCH_BOUND);

  header("Content-Type: $typ");
  fpassthru($dat);

regist_image.php

$db = new PDO('mysql:*****');
  switch($_SERVER['REQUEST_METHOD']){
    case 'POST' :
      if($_FILES['upImage']){
        $fp = fopen($_FILES["upImage"]["tmp_name"], "rb");

        $extension = pathinfo($_FILES["upImage"]["name"], PATHINFO_EXTENSION);

        $stt1 = $db->prepare("INSERT INTO test_table ( imgdat, extension, error, type, size) select ?, ?, ?, ?, ?");
        $stt1->bindValue(1, $fp, PDO::PARAM_LOB);
        $stt1->bindValue(2, $extension);
        $stt1->bindValue(3, $_FILES["upImage"]["error"] );
        $stt1->bindValue(4, $_FILES["upImage"]["type"] );
        $stt1->bindValue(5, $_FILES["upImage"]["size"] );

        $db->beginTransaction();
        $stt1->execute();
        $db->commit();
        fclose($fp);
      }
      break;

    default :
      break;
  $db = null;
  }
} catch (PDOException $e){
    die($e->getMessage());
}

TABLE

CREATE TABLE test_table (
  imgdat mediumblob ,
  extension varchar(5),
  error int ,
  type varchar(50),
  size varchar(50)
);
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+1

本来はbindColumnで指定した変数にstreamが返ってくる仕様のはずなのですがデータそのものが返却されてしまうというPDO_MYSQLのバグで、2007年に報告されているのですがいまだに直されていません。

Bug #40913  PDO_MYSQL: PDO::PARAM_LOB does not bind to a stream for fetching a BLOB

2012-11-07 12:1のjohannes@php.netさんのコメントによれば簡単に直せるものではなさそうですから、当面は今の動作に合わせて記述するしかないと思われます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/07 12:55 編集

    回答ありがとうございます。バグなんですね。。
    ということは画像データを取得して表示したいときは、PDOは使わず、mysqliを使用した方がいいということでしょうか?
    それとも画像はmysqlに保存しない方がいいということでしょうか?

    キャンセル

  • 2016/02/08 09:49

    mysqliは使ったことがないので詳しくないのですがざっと見たところstreamで受け取るような機能は見つからないので、それだったらPDOで通常のやり方で受け取るのと一緒ですから、あえて使うメリットはないような気がします。

    streamで受け取ってfpassthruで送るやり方が使えない場合、一度画像データを変数に格納することになるのでメモリ使用量は増えます。画像をDBに保存するデメリットはDBが肥大化するのと処理能力的に不利というのが一般的ですが、メモリ使用量が増えることで処理能力面のデメリットがさらに大きくなったと言えるでしょう。DBに置くことは扱いが楽であるとか整合性をとりやすいメリットもあるのでどちらを重視するかになってくると思います。

    キャンセル

  • 2016/02/09 21:40 編集

    herokuというSaaSを使っているのですが、そこは画像を保存できないみたいなのでDBに保存するしかなさそうですね。。

    そして、↓のソースのようにメモリーをfopenして文字列を書き込んでみたのですが、うまくいきませんでした。

    もしかしたらherokuに問題があるかもしれません。
    DBの容量が5MBしかないなど(画像は4MBと少し)...。

    画像は別のところに保存した方がいいのかもと考えています。

    show_image.php
    ```php
    $pdo = new PDO('mysql:*****');
    $stmt = $pdo->query("SELECT type, imgdat FROM test_table LIMIT 1");
    $stmt->bindColumn(1, $typ, PDO::PARAM_STR, 256);
    $stmt->bindColumn(2, $dat, PDO::PARAM_LOB);
    $stmt->fetch(PDO::FETCH_BOUND);

    $fp = fopen('php://memory', 'r+');
    $result = fwrite($fp, $dat);

    if($result == strlen($dat)){
    header("Content-Type: $typ");
    rewind($fp);
    fpassthru($fp);
    }
    fclose($fp);
    $pdo = null;
    ```

    キャンセル

+1

ベストアンサー後ですが、無事に画像を表示することができました。

文字コードを「UTF-8(BOM)」→「UTF-8」に変更したら出力することができました。
ブラウザの文字コードも「UTF-8」です。
コードにもherokuにも問題はありませんでした。

ベストアンサーはすごく迷いましたが、crhgさんにさせていただきます。
hirohiroさんも何回も回答してくださったり、サイトを検索していただいたりしてありがとうございました。m(__)m


自分用にまとめると、画像をherokuでmysqlに保存→出力するまでに気を付けることとして、

  • .user.ini に upload_max_filesize の値をファイルサイズ以上に設定したファイルをアップロードすること
  • max_allowed_packet もファイルサイズ以上に設定すること
  • DBのカラムの型をmediumblobにすること
  • mysql で使う場合は PDO::PARAM_LOB という型を使うこと
  • PDO::PARAM_LOB はストリームで返されないこと
  • 文字コードを統一すること
    かなぁと。。

show_image.php

<?php
  $pdo = new PDO('mysql:*****);

  $stmt = $pdo->query("SELECT extension, imgdat FROM test_table LIMIT 1");
  $stmt->bindColumn(1, $ext, PDO::PARAM_STR, 256);
  $stmt->bindColumn(2, $dat, PDO::PARAM_LOB);

  $result = $stmt->fetch(PDO::FETCH_BOUND);

  $contents_type = array(
    'jpg'  => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'png'  => 'image/png',
    'gif'  => 'image/gif',
    'bmp'  => 'image/bmp',
  );

  $fp = fopen('php://memory', 'r+');

  $result = fwrite($fp, $dat,strlen($dat));

  if($result == strlen($dat)){
    header("Content-Type: $contents_type[$ext]");
    rewind($fp);
    fpassthru($fp);
  }else{
    echo("miss");
  }
  fclose($fp);

  $pdo = null;
?>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

ひょっとしてfpassthru();にバイナリを直接流し込んだからではないでしょうか?
phpマニュアルのページ
>>fpassthru — ファイルポインタ上に残っているすべてのデータを出力する
$datはファイルポインタではなくて、恐らくデータそのもの?

後、エラーには直接関係ないかもしれませんが、Content-Typeの指定は、「"Content-Type: $typ"」では$typが文字のまま出てしまいそうです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/07 00:37 編集

    当初書式のミスでデータにアクセスできていないのでは無いかと思いそのように指摘する内容を書きましたが、失礼しました。質問欄の書式で問題ないみたいでした。(公式のマニュアルに同じような書式がありました)
    後はこれもあまり自信が無いのですが、fpassthru();にバイナリを直接流し込んだからではないでしょうか?
    >>fpassthru — ファイルポインタ上に残っているすべてのデータを出力する
    http://php.net/manual/ja/function.fpassthru.php
    $datはファイルポインタではなくて、データそのものですよね?多分

    というわけで、こちらの内容で書き直しました。

    キャンセル

  • 2016/02/07 12:53

    回答ありがとうございます。
    $datはバイナリを取得しているのでfpassthruにデータそのものを流し込んでいます。
    マニュアルのページにはラージオブジェクトという型を指定をしてあげれば、勝手にポインタになってくれているとあったので、サンプルみたいにfpassthruを使えるのではと思っていました。
    が、crhgさんが回答してくださっているようにバグみたいです。

    echo($dat)とやってみてもうまくいかなかったのですが、データそのものをそのまま表示する方法はないのでしょうか?

    キャンセル

  • 2016/02/07 19:42 編集

    >>echo($dat)とやってみてもうまくいかなかった
    なるほど、このような書式でだめだったということなのですね?
    header("Content-Type: $typ");
    echo $dat;
    これはどのようにうまくいかなかったのでしょう?バイナリデータが文字で書き出されてしまったのでしょうか?
    もしかするとstrlen()か何かで大きさを確認してみると$datは空かも知れませんね。
    (crhgさんの書き込みからはバイナリデータは受け渡しされるようですが)

    もしbindColumnに拘りが無ければ使わない方法を試してみてはどうでしょう?
    $stmt = $pdo->query("SELECT type, imgdat FROM test_table LIMIT 1");
    $img = $stmt->fetch();
    header("Content-Type: ".$img->type);
    echo $img->imgdat;
    mysqlからの画像出力で多く紹介されているのはこちらのパターンかと
    参考:http://www.kantenna.com/pg/2010/04/phpmysql.php

    キャンセル

  • 2016/02/08 22:12

    このサイトを参考にしたりフェッチの仕方を変えたりもしてみましたがなかなかうまくいきませんでした。
    strlen()も調べてみましたが画像サイズ数ありましたね。。
    なかなか詰んでます(笑)
    今は文字列をストリームで読み込む方法を探してます

    サイトなど調べてくださってありがとうございます。

    キャンセル

0

コメントを間違えて回答にしてしまったので削除

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る