0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PHP, JavaScript】画像を並び替えてアップロードする(送信先編)

0
Last updated at Posted at 2023-12-02

これまでの実装

今回はPHPのみです。送信された位置情報・配列photoOrderを使って、プレビューと同じ並びで画像を表示します。

目次

ファイル完成
upload.php【完成】

ポイント

  • $_FILES['photo'](送信した全ファイルの全情報)を使う
    ['photo']とは、<input type="file">に指定したname属性の値

今回やること

  • 送信されたphotoOrder = [1, 3, 2, 0];を受け取る(並び順は例です)
  • 配列photoOrderの、インデックス0から順番に画像を指定
    1つずつ 「3つのエラーチェック」 を行い、表示していく
    ・問題なし : 画像を表示
    ・問題あり : エラーメッセージを表示
スクリーンショット 2026-03-06 105334.png

この図では、$_FILES['photo']['name'][$photo_order[$i]]をしています($iはループのカウンタ変数で、0から)。

つまり、b.jpgd.jpgc.jpga.jpgの順に、処理(3つのエラーチェックと表示)が行われます。こうして、プレビューと同じ並びで画像を表示しています。

$_FILES['photo']について詳しく見ていきます。

$_FILES['photo']

先ほどの図で、$_FILES['photo']['name']を出しましたが、他にも様々な項目(赤字部分)があります。
スクリーンショット 2023-11-29 161938.png

  • 項目の内容は、nameのようにファイル名の昇順で格納
  • しかし、項目の内容を取得するのは[$photo_order[$i]]の順
    b.jpgd.jpgc.jpga.jpg)。取得したらその場で処理に使う

(例)カウンタ変数$i = 0の場合。[$photo_order[0]] = 1
$_FILES['photo']['name'][1]
$_FILES['photo']['full_path'][1]
$_FILES['photo']['type'][1]
………
これらは、全てb.jpgにまつわる情報です。

['項目'] の部分を変えることで、その画像にまつわる様々な情報を得ることができます。必要な情報を得たらその場で処理(3つのエラーチェックと表示)に使います。なので、結果的に処理と表示がb.jpgd.jpgc.jpga.jpgの順となります。

完成図は以下です。エラーチェックで「問題なし」と「問題あり」の時、表示はこうなります(例 : 画像4枚)。

完成図

スクリーンショット 2026-03-08 155336.png

実装していきます。

(7)PHPファイル作成

HTMLと、PHPのコードが2か所(★1)(★2)です。

upload.php
<?php
$photo_order = json_decode($_POST['photo-order'], true); // (★1)
$count_files = count($_FILES['photo']['name']);
?>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="css/preview.css"/>
  </head>
  <body>
    <div id="preview">
      <div id="all-photos" class="layout-<?=$count_files;?>"><!-- (★2) -->
         <?php
           for($i = 0; $i < $count_files; $i++){ // 画像の枚数分
             // 3つのエラーチェック
             //「<div><img>」または「<div><p>」を生成  
           }
         ?>
      </div>
    </div>
  </body>
</html>

(★1)

  • POST送信した配列photoOrderはJSON形式。デコードして取得
  • 画像の枚数を取得

(★2)<div id="all-photos">について

  • ここに全ての表示が入る。各画像に「3つのエラーチェック」を行い、
    ・問題なし : <div><img>を生成(画像を表示)
    ・問題あり : <div><p>を生成(エラーメッセージを表示)
  • プレビューの並び順で表示。上記処理に必要な、各画像の情報は[$photo_order[$i]]の順に取得(b.jpgd.jpgc.jpga.jpg)。取得してその場で処理に使う
  • CSSで表示を整える。画像の枚数に応じた、「layout-枚数」というクラス属性を付与
    (例)画像4枚→layout-4preview.cssは、こちらと同じものを読み込む

(8)3つのエラーチェック

①アップロード時
②拡張子
③コンテンツタイプ(②より信頼性あり)

【出典】『独習PHP 第4版』 山田 祥寛(著)

こちらの書籍を参考にさせていただきました。実装に合わせて一部変更をしております。

①アップロード時

  • $_FILES['photo']['error']

アップロード時に発生したエラーのコード(p.399)

さきほどの完成図では、エラーなし / ありの場合の表示を見てもらいました。このエラーは、ここで発生しています。

print_r($_FILES['photo']['error']);
出力
 Array ( [0] => 0 [1] => 0 [2] => 2 [3] => 2 )
 
          ↑        ↑        ↑        ↑      
 //      a.jpg     b.jpg    c.jpg、  d.jpg
  • 0~8(5は除く)の数字が入る(以下の表)

項目の内容は、ファイル名の昇順に並んでいるのでした。これを、[$photo_order[$i]]の順(b.jpgd.jpgc.jpga.jpg)にチェックします。つまり、エラーコードは「0、2、2、0」。2は、エラーメッセージを表示します。CSSにより、最終的にこのような表示となります。

定数 概要
UPLOAD_ERR_OK 0 アップロード成功
UPLOAD_ERR_INI_SIZE 1 php.iniのupload_max_filesizeで指定サイズを超えた
UPLOAD_ERR_FORM_SIZE 2 HTMLのmax_file_sizeで指定したサイズを超えた
UPLOAD_ERR_PARTIAL 3 ファイルが途中で切れている
UPLOAD_ERR_NO_FILE 4 ファイルが存在しない
UPLOAD_ERR_NO_TMP_DIR 6 一時保存のためのフォルダーが存在しない
UPLOAD_ERR_CANT_WRITE 7 ディスクへの書き込みに失敗した
UPLOAD_ERR_EXTENSION 8 拡張モジュールによってアップロードが中断された

for文に、以下の処理を追加します。

  • 0(アップロード成功)の場合、次のエラーチェックに進む
  • 0以外の場合、<div><p>を生成し、エラーメッセージを表示して終了
upload.php
<?php
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
?>
<!-- 省略 -->
  <body>
    <div id="preview">
      <div id="all-photos" class="layout-<?=$count_files;?>">
        <?php
          for($i = 0; $i < $count_files; $i++){
            // ここから追加
            if($_FILES['photo']['error'][$photo_order[$i]] !== UPLOAD_ERR_OK){
              $msg = [
                UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesizeを超えています。',
                UPLOAD_ERR_FORM_SIZE => 'HTMLのmax_file_sizeを超えています。',
                UPLOAD_ERR_PARTIAL => 'ファイルが一部分しかアップロードされていません。',
                UPLOAD_ERR_NO_FILE => 'ファイルが存在しません。',
                UPLOAD_ERR_NO_TMP_DIR => '一時保存のためのフォルダが存在しません。',
                UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。',
                UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。',
              ];
              $error_msg = $msg[$_FILES['photo']['error'][$photo_order[$i]]];
              print '<div><p>'.$error_msg.'</p></div>';
            }else if(){ // 次のエラーチェックへ
              // 処理
            }
          }
        ?>
      </div>
    </div>
  </body>
</html>

②拡張子

おおまかに、こんな処理です。

  • 配列に、許可する拡張子のリストを用意する。['gif', 'jpg', 'jpeg', 'png']
  • pathinfo関数で、各ファイルの拡張子(例 : jpg)を取得。strtolower関数で小文字に変換
  • in_array関数を使い、この拡張子が配列のリストの中にあるかチェック
    ・ある(許可する拡張子)→次のエラーチェックに進む
    ・なし(許可しない拡張子)→エラーメッセージを表示して終了
upload.php
  }else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION)), ['gif', 'jpg', 'jpeg', 'png'])){
     $error_msg = '拡張子が画像ではありません。';
     print '<div><p>'.$error_msg.'</p></div>';
  }else if(){ // 次のエラーチェックへ
    // 処理
  }

if文の関数を確認していきます。

pathinfo関数

見やすいように、抜き出します。

pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION)
  • pathinfo($path, $flags)
    $path(ファイル)について4つの情報を返す。「dirname」「basename」「extension」「filename」。$flagsは、この中からほしい情報を定数で指定。ファイルの拡張子「extension」のみ欲しいので、PATHINFO_EXTENSIONを指定

実際に確認していきます。まずは、$path = $_FILES['photo']['name'][$photo_order[$i]]について。

  • $_FILES['photo']['name']
print_r($_FILES['photo']['name']);
出力
Array ( [0] => a.jpg [1] => b.jpg [2] => c.jpg [3] => d.jpg )

項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpgd.jpgc.jpga.jpg)に、pathinfo関数で使います。$flagsを省略すると、

  for($i = 0; $i < $count_files; $i++){
    print_r(pathinfo($_FILES['photo']['name'][$photo_order[$i]]));
  }
出力
Array ( [dirname] => . [basename] => b.jpg [extension] => jpg [filename] => b )
Array ( [dirname] => . [basename] => d.jpg [extension] => jpg [filename] => d ) 
Array ( [dirname] => . [basename] => c.jpg [extension] => jpg [filename] => c )
Array ( [dirname] => . [basename] => a.jpg [extension] => jpg [filename] => a )

各ファイルについて、dirnamebasenameextensionfilenameの4つの情報が出力されます。$flagsには、この4つの中でほしい情報を定数で指定します。今回の実装では、extension(ファイルの拡張子)が欲しいです。$flagsPATHINFO_EXTENSIONを指定します。

  // 先ほどのコードをこうする
  print pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION);
出力
jpg jpg jpg jpg

この拡張子をstrtolower関数で小文字に変換しています。

strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION))
// jpg

ここでは元から小文字ですが、仮に大文字JPGだとすると、リスト['gif', 'jpg', 'jpeg', 'png']の中から探すときにjpgと一致しません。in_array関数は、大文字と小文字を区別するので、「許可されていない拡張子」と判断されてしまいます。

in_array関数

取得した拡張子(小文字)が、配列のリストの中にあるかチェック

!in_array(strtolower(省略), ['gif', 'jpg', 'jpeg', 'png']))
  • in_array($needle, $haystack)
    配列$haystack内(['gif', 'jpg', 'jpeg', 'png'])から$needlejpg)を探す。見つかればtrue、見つからなければfalseを返す。

「見つからない場合に」エラーメッセージを表示するので、if文は!in_array()です。

【参考】

③コンテンツタイプ(②より信頼性あり)

おおまかに、こんな処理です。

  • 配列に、許可するコンテンツタイプ(MIMEタイプ)のリストを用意する。['image/gif', 'image/jpg', 'image/jpeg', 'image/png']
  • finfo_file関数で、各ファイルの内容を解析し、コンテンツタイプを取得
  • in_array関数を使い、このコンテンツタイプが配列のリストの中にあるかチェック
    ・ある(許可するコンテンツタイプ)→画像表示
    ・なし(許可しないコンテンツタイプ)→エラーメッセージを表示して終了
upload.php
else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
    $error_msg = 'コンテンツタイプが画像ではありません。';
    print '<div><p>'.$error_msg.'</p></div>';
}else{ // 画像表示
  // 処理
}

if文の関数を確認していきます。

finfo_file関数

見やすいように、抜き出します。

finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]); 
  • finfo_file($finfo, $filename) : $finfoに応じた、ファイルの情報を返す
    $finfo : FileInfoインスタンス。finfo_open関数で作成(★1)
    $filename : ファイルパス(★2)

(★1)

  • finfo_open($frags)
    $frags : 「ファイルについて欲しい情報」を定数に指定。ファイルのMIMEタイプが欲しいので、FILEINFO_MIME_TYPE

(★2)

  • $_FILES['photo']['tmp_name']

一時的なファイルの保存先(省略)(PHPでは、クライアントからファイルを受け取ると、暫定的に仮のフォルダーに保存しています)。(p.400)

print_r($_FILES['photo']['tmp_name']);
出力
Array ( [0] => /tmp/phpXXXXXX [1] => /tmp/phpXXXXXX [2] => /tmp/phpXXXXXX …… )
         ↑                     ↑                     ↑
 //     a.jpg                  b.jpg                c.jpg                 ……

各ファイルが保存されている、フォルダ名が表示されます。

項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpgd.jpgc.jpga.jpg)に、finfo_file関数で使います。

指定したFILEINFO_MIME_TYPE(MIMEタイプ)が返されるので、確認します。

  for($i = 0; $i < $count_files; $i++){
    print finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]);
  }
出力
image/jpeg image/jpeg image/jpeg image/jpeg
    ↑           ↑         ↑           ↑
// b.jpg      d.jpg      c.jpg      a.jpg   

in_array関数はこちらと同じです。

upload.php
!in_array(finfo_file(省略),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])

['image/gif', 'image/jpg', 'image/jpeg', 'image/png']からimage/jpegを探します。見つかればtrue、見つからなければfalseを返します。

「見つからない場合に」エラーメッセージを表示するので、if文は!in_array()です。

3つのエラーチェックを全てクリアしたら、画像を表示します。

【参考】

画像の扱い

  • どこにも保存せず、そのまま表示する(今回はこれをします)
    fopen関数で一時的な保存先を開き、ファイルの内容をfread関数で読み取る
  • サーバー側(ここではローカル環境・現在作業中のディレクトリ下)に保存&表示
    move_uploaded_file関数を使って、画像ファイルを一時的な保存先→サーバー側へ移動

こちらの記事に詳しく書かれています。参考にさせていただきました。

【出典】

(9)画像を表示(どこにも保存せず、単に表示するだけ)

2つに分けて説明します。

upload.php
}else{
  // ファイルの読み取り
  $fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
  $get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
  fclose($fp);
  // 表示処理
  $encode_photo = base64_encode($get_photo);
  $photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
  print '<div><img src="data:' . $photo_info['mime'] .';base64,' . $encode_photo . '"></div>';
}

ファイルの読み取り

  • 一時的なファイルの保存先$_FILES['photo']['tmp_name'] から、ファイルをファイルサイズ分(= 全て)読み込む
upload.php
 $fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
 $get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
 fclose($fp);

fopen関数

 $fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
  • fopen($filename, $mode) : ファイルを開き、ファイルハンドルを返す
    $filename : 開くファイルのパス
    $mode : 開く際のモード

ファイルをノートにたとえるならば、実際にノートを手に取り、表紙を開くようなイメージです(p.239)

ファイルのオープンに成功すると、戻り値としてファイルハンドルを返します。ファイルハンドルとは、名前のとおり、そのファイルを操作(handle)するためのキーとなる情報です。(省略)resouce型に属する値です。今後、ファイルに対する読み書きは、このファイルハンドルに対して行うことになります。(p.239)

$filename$_FILES['photo']['tmp_name']は、一時的なファイルの保存場所(フォルダ名)。

  print_r($_FILES['photo']['tmp_name']);
出力
Array ( [0] => /tmp/phpXXXXXX [1] => /tmp/phpXXXXXX [2] => /tmp/phpXXXXXX …… )
         ↑                     ↑                     ↑
 //     a.jpg                  b.jpg                c.jpg                 ……

項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpgd.jpgc.jpga.jpg)に、rbモードで開きます。

  • rbモード : 保存先からファイルを読み込む。その際、ファイル内の改行は勝手に変換しない
    r : 読み込み専用。この後ろに、t(テキストモード)かb(バイナリモード)を指定する。省略するとbになる
    t : ファイル内の改行を、OSに合わせて変換する
    b : 何もしない。tに頼らず正しい改行コードを書き、bを使うのが強く推奨。

fread関数

 $get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
  • fread($stream, $length) : 読み込んだバイト列(=生データ)を返す
    $stream : fopen()で得たファイルハンドル
    $length : 読み込むサイズ(バイト)。ファイルサイズ分を指定する(= 全て読み込む)。ファイルサイズは、filesize関数で調べる。

filesize関数

  • filesize($filename) : ファイルのサイズを取得
    $filename : ファイルパス

$filename$_FILES['photo']['tmp_name']は、一時的なファイルの保存場所(フォルダ名)です。項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpgd.jpgc.jpga.jpg)に指定し、各ファイルサイズを取得します。

fclose関数

ファイルを閉じる

ファイルに対する読み書きを追えたら、fclose関数でファイルをクローズします。ファイルのクローズとは、開いていたノートを閉じて、元あった場所に戻す行為とイメージしておけばよいでしょう。(p.240)

【参考】

表示処理

  • <img>を完成させます。src属性は、データURLです
  • データURLは、「data:MIMEタイプ;base64,データ本体」という文字列です
    MIMEタイプ : 現在ここだけ不明なので、これから取得
    データ本体 : 先ほど取得した生データを、データURLで使える形にします(※)

(※)取得した生データは「文字として読む前提がないもの(画像)」= バイナリデータです。base64でエンコードします。

base64 を指定して、base64 エンコードされたバイナリーデータを埋め込むことができます。
(出典 : data: URL

upload.php
$encode_photo = base64_encode($get_photo);
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
print '<div><img src="data:' . $photo_info['mime'] . ';base64,' . $encode_photo . '"></div>';

関数を確認します。

base64_encode関数

  • base64_encode($string) : $string(データ)をbase64でエンコードして返す

Base64 の一般的な用途としては、以下のものがあります。(省略)バイナリーデータをエンコードして data: URL に含めることができるようにする場合
(出典 : Base64

getimagesize関数

現在、<img>のsrc属性(データURL)を書くために必要な、MIMEタイプが不明です。エラーチェックの3つめでMIMEタイプを調べましたが、変数に保存していないので再度調べる必要があります。getimagesize関数を使って調べますが、これもまた引数にデータURLを使います。

$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);

MIMEタイプを調べたいのに、MIMEタイプを書かなくてはいけません。何を書けばいいのか分からないですよね。MIMEタイプapplication/octet-streamは、まさにそのような状況で使用できます。

どのMIMEタイプを使うべきか分からないとき
(出典 : わわわIT用語辞典 - application/octet-stream【MIMEタイプ】

クライアントソフトなどでは、データ形式不明の単なるバイト列として取り扱われる
(出典 : IT用語辞典 e-Words - application/octet-stream

getimagesize関数が取得した情報を見てみます。(出力は画像1枚とします)

print_r($photo_info);
出力例
Array ( [0] => 1200                           ...幅
        [1] => 800                            ...高さ
        [2] => 2                              ...画像の形式。PHPに定義されている定数の値
        [3] => width="1200" height="800",     ...<img>で使える
        [bits] => 8                           ...色のbit
        [channels] => 3                       ...RGB画像は3、CMYK画像は4 
        [mime] => image/jpeg )                ...画像のMIMEタイプ

[mime]に、MIMEタイプがあります。これだけが必要です。

print $photo_info['mime'];
出力
image/jpeg

<img>のsrc属性(データURL)の、MIMEタイプ部分に当てはめます。

upload.php
print '<div><img src="data:' . $photo_info['mime'] . ';base64,' . $encode_photo . '"></div>';

完成です。

【参考】

upload.php【完成】

upload.php
<?php
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
?>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="css/preview.css"/>
  </head>
  <body>
    <div id="preview">
      <div id="all-photos" class="layout-<?=$count_files;?>">
        <?php
          for($i = 0; $i < $count_files; $i++){
            if($_FILES['photo']['error'][$photo_order[$i]] !== UPLOAD_ERR_OK){
              $msg = [
                UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesizeを超えています。',
                UPLOAD_ERR_FORM_SIZE => 'HTMLのmax_file_sizeを超えています。',
                UPLOAD_ERR_PARTIAL => 'ファイルが一部分しかアップロードされていません。',
                UPLOAD_ERR_NO_FILE => 'ファイルが存在しません。',
                UPLOAD_ERR_NO_TMP_DIR => '一時保存のためのフォルダが存在しません。',
                UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。',
                UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。',
              ];
              $error_msg = $msg[$_FILES['photo']['error'][$photo_order[$i]]];
              print '<div><p>'.$error_msg.'</p></div>';
            }else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]])['extension']), ['gif', 'jpg', 'jpeg', 'png'])){
              $error_msg = '拡張子が画像ではありません。';
              print '<div><p>'.$error_msg.'</p></div>';
            }else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
              $error_msg = 'コンテンツタイプが画像ではありません。';
              print '<div><p>'.$error_msg.'</p></div>';
            }else{
              $fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
              $get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
              fclose($fp);
              $encode_photo = base64_encode($get_photo);
              $photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
              print '<div><img src="data:' . $photo_info['mime'] .';base64,' . $encode_photo . '"></div>';
            }
          }
        ?>
      </div>
    </div>
  </body>
</html>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?