Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What is going on with this article?
@haruna-nagayoshi

PHPでblob型でデータベースに保存した画像をHTML(twig)に出力する

概要

blob型でデータベースに保存したpng画像のバイナリデータを、PHP + Symfony + twigで画像として出力する方法について、調査に半日くらいかかったので、記事にまとめました。

今回は外部に画像ファイルを置けないという特殊な都合によりこの方法を行いました。データベースに画像を保存したり、data URI schemeを使用することはよく考えて選ぶ必要があります。

動作環境

EC-CUBE4 独自プラグイン開発 ①Dockerで環境構築 + xdebug導入
この記事と同じ。

EC-CUBE 4.0.3
Symfony 3.4
PHP 7.3
MySQL 5.7

結論

結論からいうと、
①バイナリデータをstream_get_contents()base64_encode()で変換する。
②HTMLには、<img>タグのsrc属性にdata URI schemeとMIME-Type(どんな画像か、png or jpeg or etc...)と共に①を渡す。
を行えば、画像を表示できました。

<img src="data:image/png;base64,xxxxx..." />

※画像に合わせてMIME-Typeの部分を変更する。

サンプルコード

Entity

本来は他のプロパティ・メソッドが存在しますが、ここでは省略します。

<?php

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="products")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255)
 * @ORM\Entity
 */
class Product extends AbstractEntity
{
    /**
     * @return string
     */
    public function displayPngImageFromString(): string
    {
        // stream_get_contents() の offset を 0 にする必要がある。 
        // 初期値の -1 だと、 displayPngImageFromString() を呼び出したときにポインタが移動したままになると推測され、
        // 2回目以降に呼び出したときにencodeされた文字列が出力されない=画像が出力されない。
        // 詳しくはPHPのマニュアルを参照。
        return base64_encode(stream_get_contents($this->getPng(), -1, 0));
    }

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned"=true,"comment"="ID"})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var resource
     *
     * @ORM\Column(name="png", type="blob", length=16777215, nullable=false, options={"comment"="png画像"})
     */
    private $png;

    /**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set png.
     *
     * @param string $png
     *
     * @return Designtool
     */
    public function setPng($png)
    {
        $this->png = $png;

        return $this;
    }

    /**
     * Get png.
     *
     * @return resource
     */
    public function getPng()
    {
        return $this->png;
    }
}

twig

画像を出力する部分だけ抜粋します。
画像をクリックするモーダルとして拡大表示される、商品一覧ページと思ってください。

{% for product in pagination %}
    {% set imageString = product.displayPngImageFromString %}
    <div class="ec-imageGrid__img">
        <a onclick="openModal({{ product.id }})">
            <img src="data:image/png;base64,{{ imageString }}" alt="">
        </a>
    </div>
    <div class="background">
        <img src="data:image/png;base64,{{ imageString }}" alt="">
    </div>
{% endfor %}
<script>
$(function () {
  'use strict';

  const modalBackground = $('.background');
  modalBackground.on('click', function () {
    modalBackground.fadeOut();
  });
});

  const openModal = function (id) {
    const image = $('.background');
    image.fadeIn();
    image.children('img').fadeIn();
  }
</script>
<style type="text/css">
    .background {
        display: none;
        background-color: rgba(0, 0, 0, 0.5);
        width: 100%;
        height: 100%;
        z-index: 1;
        position: fixed;
        top: 0;
        left: 0;
     }

    .background img {
        width: 600px;
        height: auto;
        position: fixed;
        top: calc(50% - 290px);
        left: calc(50% - 300px);
        cursor: pointer;
    }

    @media screen and (max-width: 767px) {
        .background img {
            padding: 13px;
            width: auto;
            position: fixed;
            top: 20%;
            left: 0;
        }
    }
</style>

URI Schemeによって画像の取得方法が異なる

通常、画像を配置する場合はこのように<img>タグに画像のパスを指定する。このとき、ブラウザはHTMLを読み込んだだけでは画像を表示できません。HTMLを読み込んだ後、<img>タグに指定された画像を取得するために、画像の数だけもう一度サーバと通信して画像を取得します。

<img src="images/hoge.png" />

サーバと繰り返し通信を行うのは、CSSファイルなども同じです。

一方、このように Data URI schemeを指定した場合、画像は直接HTMLに埋め込まれます。その分、HTMLのファイルサイズは大きくなるためダウンロードに時間がかかりますが、ブラウザは画像の数だけ通信を行う必要がなくなります。

<img src="data:image/png;base64,xxxxx..." />

よって、この方法を選択する必要がある場合は、小さな画像に対してのみ行うべきかもしれません。

WikipediaにもData URI schemeの短所・長所について説明があります。
data URI scheme

以下、
画像をBase64で変換して埋め込めば、HTTPリクエストがなくなり高速表示できるより

しかし、Base64の扱いには注意が必要です。データサイズが37%も増加するのでダウンロードにかかる時間は増加するからです。そのため、データサイズの小さな画像に対してのみBase64を使うのが一般的です。

元のデータサイズが小さい場合は、データサイズ増加によるダウンロード時間の増加よりも、リクエストとレスポンスの時間を削減する方が効果が大きいが、データサイズが大きい場合は、削減したリクエストとレスポンスの時間以上にダウンロード時間が増加する可能性があるからです。

おまけ

blob型をもつテストデータをコピーする

今回、fugaテーブルにはblob型を含むテストデータが既に用意されていて、hogeテーブルにもblob型をもつテストデータが必要、という状況でした。
画像をhogeに保存する機能がまだ存在していなかったので、PhpStormやA5M2を使ってコピーしようとしたのですが、blob型のデータはコピーができませんでした。クリップボードに貼るにはデータ量が大きいからでしょうか・・・。
なので、このようなSQLを叩いてテストデータを用意しました。

insert into hoge
set file_data = (select png from fuga where id = 1),
    file_name = 'test.png',
    create_date = now();

参考

5
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
5
Help us understand the problem. What is going on with this article?