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

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

ただいまの
回答率

87.59%

画像投稿掲示板が上手く動作しません

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,073

score 470

質問させてください。
今、画像とコメントを投稿できるシンプルな掲示板を作成している途中なのですが、送信ボタンを押したときの動作がうまくいかなくて困っております。

画像投稿部分はこちらを、コメント投稿部分はこちらの記事を参考にしまして、この2つを合体させるようなイメージで作成しました。(それぞれはうまく動作しました。)

送信されたときに、データをcsvファイルへ書き込むタイミングがおかしいのが問題のような気するのですが、1日かけていろいろ試してみたものの原因がどうしても分からず途方に暮れております。

もし解決方法がお分かりの方がいらっしゃいましたら、ご教授いただけないでしょうか。
また、ソースの書き方をこうした方がいいという部分がありましたら、ご指摘いただけると助かります。

ソースを下に掲載させていただきます。
かなり長くなってしまったのと、コメントがいろいろ書いてあり見にくくて申し訳ありません。

よろしくお願いいたします。

発生している問題・エラーメッセージ

Uploader.php の39行目に「exit;」がある場合、成功・エラーメッセージは表示されますが、画像・コメントが表示されずcsvに書き込みができない状態です。
また exit を「return;」にしますと、今度はcsvに書き込みできて画像・コメントは表示されますが、成功・エラーメッセージが表示されず、送信を何度も押すと2重投稿になってしまいます。

ディレクトリ構成は以下のようになっております。
・index.php
・Uploader.php
・imagesフォルダ
・thumbsフォルダ

サーバーはCentOSのvagrantを使用しています。
またPHPのバージョンは5.6になります。 

該当のソースコード

index.php

<?php

// 成功・失敗メッセージ格納用
session_start();

ini_set('display_errors', 1);
define('MAX_FILE_SIZE', 1 * 1024 * 1024); // ファイルサイズ(1MB)
define('THUMBNAIL_WIDTH', 400); // サムネイルを作る際の敷地となる幅(400px)
define('IMAGES_DIR', __DIR__ . '/images'); // 元画像用ディレクトリのパス
define('THUMBNAIL_DIR', __DIR__ . '/thumbs'); // サムネイル用ディレクトリのパス

// 画像処理に必要なGDプラグインがあるかどうかチェック
if (!function_exists('imagecreatetruecolor')) {
  echo 'GDがインストールされていません';
  exit;
}

// エスケープ
function h($s) {
  return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// 外部ファイル読み込み
require 'Uploader.php';

// 画像アップローダーのインスタンス生成
$uploader = new \MyApp\Uploader();

// CSVファイルを開く
$fp = fopen('data.csv', 'a+b');

// 何かが投稿されたときの処理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  // コメントを変数に格納
  $post_data = [$_POST['comment']];

  // 画像アップローダーのuploadメソッド(画像を保存)を実行
  $uploader->upload();
}

// セッションに保存してある成功・失敗メッセージを取得
list($success, $error) = $uploader->getResults();

// img要素のsrc属性に入れるパスを取得
$images = $uploader->getImages();

// CSVに書き込み
if (isset($images[0]) && isset($post_data[0])) {
  fputcsv($fp, [$images[0], $post_data[0]]);
  rewind($fp); // CSVのポインタを先頭に戻す
}

// fgetcsv関数を使って、CSV1行を配列として取り出し、一時的に$rowに代入する
while ($row = fgetcsv($fp)) { 
  $rows[] = $row;
}

// CSVファイルを閉じる
fclose($fp);

?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>

<!-- 成功・失敗メッセージ -->
<?php if (isset($success)) : ?>
<div class="msg success"><?php echo h($success); ?></div>
<?php endif; ?>
<?php if (isset($error)) : ?>
<div class="msg error"><?php echo h($error); ?></div>
<?php endif; ?>

<!-- フォーム -->
<form action="" method="post" enctype="multipart/form-data" id="my_form">
  <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo h(MAX_FILE_SIZE); ?>">
  <p><input type="file" name="image"></p>
  <p><textarea name="comment"></textarea></p>
  <p><input type="submit" value="送信する"></p>
</form>

<!-- 表示部 -->
<?php if (!empty($rows)): ?>
<?php foreach ($rows as $row) : ?>
<div>
  <p><a href="<?php echo h(basename(IMAGES_DIR)) . '/' . h(basename($row[0])); ?>"><img src="<?php echo h($row[0]); ?>" alt=""></a></p>
  <p><?php echo h($row[1]); ?></p>
</div>
<?php endforeach; ?>
<?php else: ?>
<p>投稿はまだありません</p>
<?php endif; ?>

</body>
</html>


Uploader.php

<?php

namespace MyApp;

class Uploader {

  private $_imageFileName; // 保存するファイル名
  private $_imageType; // 画像タイプ

  /* 画像の保存
 --------------------------------------------- */

  public function upload() {
    try {
      //エラーチェック
      $this->_validateUpload();

      // 画像タイプのチェック
      $ext = $this->_validateImageType();

      // 画像の保存
      $savePath = $this->_save($ext);

      // サムネイル作成
      $this->_createThumbnail($savePath);

      // 成功メッセージをセッションに保存
      $_SESSION['success'] = 'アップロードに成功しました!';

    // エラーがあった場合の処理
    } catch (\Exception $e) {
      // エラーメッセージをセッションに保存
      $_SESSION['error'] = $e->getMessage();
    }

    // index.phpにリダイレクトさせる(phpを再読み込みすると2重投稿になるのを防ぐ)
    header("Location: " . $_SERVER['PHP_SELF']);

    // exit; // これだと成功・エラーメッセージは出るけど、画像・コメントが表示されない、csvに書き込みができない
    return; // これだとcsvに書き込みできて画像・コメントは表示されるけど、成功・エラーメッセージが表示されない、送信を何度も押すと2重投稿になってしまう
  }

  /* セッションに保存してある成功・失敗メッセージを取得
 --------------------------------------------- */

  public function getResults() {
    // 変数を初期化
    $success = null;
    $error = null;

    // もしセッションに中身があったら
    if (isset($_SESSION['success'])) {
      // 中身を取り出して変数に格納
      $success = $_SESSION['success'];

      // セッションのデータを削除
      unset($_SESSION['success']);
    }

    if (isset($_SESSION['error'])) {
      $error = $_SESSION['error'];
      unset($_SESSION['error']);
    }

    return [$success, $error];
  }

  /* img要素のsrc属性に入れるパスを取得
 --------------------------------------------- */

  public function getImages() {
    $images = []; // パスをソートして最終的に返す配列
    $files = []; // ファイル名だけを入れる配列(ソート用)

    // opendir でディレクトリハンドルをオープンし、readdir でディレクトリ内のファイル一覧を取得(あるフォルダの中にあるファイルを精査)
    $imageDir = opendir(IMAGES_DIR);

    // 1つ1つのファイルに対して行う処理
    while (false !== ($file = readdir($imageDir))) {
      // もし $file の中身がカレントディレクトリを示す「.」、もしくは親ディレクトリを示す「..」の場合は、ファイルじゃないのでループを次に回す
      if ($file === '.' || $file === '..') { 
        continue;
      }

      // $files配列にファイル名を入れて後でソートできるようにしておく
      $files[] = $file;

      // サムネイルがあった場合の処理
      if (file_exists(THUMBNAIL_DIR . '/' . $file)) {
        // ディレクトリ名をつけたパスをimages配列に格納
        $images[] = basename(THUMBNAIL_DIR) . '/' . $file;

      // サムネイルがない場合の処理
      } else {
        // ディレクトリ名をつけたパスをimages配列に格納
        $images[] = basename(IMAGES_DIR) . '/' . $file;
      }
    }

    // images[]配列をソートする
    array_multisort($files, SORT_DESC, $images);

    return $images;
  }

  /* サムネイル作成
 --------------------------------------------- */

  private function _createThumbnail($savePath) {
    // 画像サイズを取得
    $imageSize = getimagesize($savePath);
    $width = $imageSize[0];
    $height = $imageSize[1];

    // 設定したサイズより大きかった場合
    if ($width > THUMBNAIL_WIDTH ) {
      $this->_createThumbnailMain($savePath, $width, $height);
    }
  }

  /* 画像が大きかったときの処理
 --------------------------------------------- */

  private function _createThumbnailMain($savePath, $width, $height) {
    // 拡張子別に元画像のリソースIDを取得
    switch ($this->_imageType) {
      case IMAGETYPE_GIF:
        $srcImage = imagecreatefromgif($savePath);
        break;
      case IMAGETYPE_JPEG:
        $srcImage = imagecreatefromjpeg($savePath);
        break;
      case IMAGETYPE_PNG:
        $srcImage = imagecreatefrompng($savePath);
        break;
    }

    // サムネイルの高さを算出
    $thumbHeight = round($height * THUMBNAIL_WIDTH / $width); 

    // サムネイル用のリソースIDを取得
    $thumbImage = imagecreatetruecolor(THUMBNAIL_WIDTH, $thumbHeight);

    // 元画像をコピーしてリサイズする
    imagecopyresampled(
      $thumbImage, // コピー先の画像リンクリソース
      $srcImage, // コピー元の画像リンクリソース
      0, // コピー先の x 座標
      0, // コピー先の y 座標
      0, // コピー元の x 座標
      0, // コピー元の y 座標
      THUMBNAIL_WIDTH, // コピー先の幅
      $thumbHeight, // コピー先の高さ
      $width, // コピー元の幅
      $height // コピー元の高さ
    );

    // サムネイルファイルを出力
    switch ($this->_imageType) {
      case IMAGETYPE_GIF:
        imagegif($thumbImage, THUMBNAIL_DIR . '/' . $this->_imageFileName);
        break;
      case IMAGETYPE_JPEG:
        imagejpeg($thumbImage, THUMBNAIL_DIR . '/' . $this->_imageFileName);
        break;
      case IMAGETYPE_PNG:
        imagepng($thumbImage, THUMBNAIL_DIR . '/' . $this->_imageFileName);
        break;
    }
  }

  /* 画像の保存
 --------------------------------------------- */

  private function _save($ext) {
    // プライベートプロパティに保存するファイル名を設定
    $this->_imageFileName = sprintf(
      '%s_%s.%s',
      time(),
      sha1(uniqid(mt_rand(), true)),
      $ext
    );

    // 保存するためのパスを設定
    $savePath = IMAGES_DIR . '/' . $this->_imageFileName;

    // move_uploaded_file 命令で、一時フォルダに入っているファイルを正しいパスで動かせる
    $res = move_uploaded_file($_FILES['image']['tmp_name'], $savePath);
    if ($res === false) {
      throw new \Exception('画像のアップロードに失敗しました');
    }

    return $savePath;
  }

  /* 画像タイプのチェック(拡張子の取得)
 --------------------------------------------- */

  private function _validateImageType() {
    // 画像の種類を取得
    $this->_imageType = exif_imagetype($_FILES['image']['tmp_name']);
    switch ($this->_imageType) {
      case IMAGETYPE_GIF:
        return 'gif';
      case IMAGETYPE_JPEG:
        return 'jpg';
      case IMAGETYPE_PNG:
        return 'png';

      // それ以外の拡張子だった時
      default:
        throw new \Exception('JPGE・PNG・GIF形式の画像をアップロードしてください');
    }
  }

  /* エラーチェック
 --------------------------------------------- */

  private function _validateUpload() {
    // var_dump($_FILES);
    // exit;

    // $_FILES['image'] がきちんとセットされているかチェック → !isset($_FILES['image'])
    if (!isset($_FILES['image']) || !isset($_FILES['image']['error'])) {
      // セットされていなかった場合、例外を投げる
      throw new \Exception('画像のアップロードに失敗しました');
    }

    // $_FILES['image']['error'] の種類によって、例外の種類を変える
    switch ($_FILES['image']['error']) {
      // UPLOAD_ERR_OK という定数が返ってきた場合は処理を終了
      case UPLOAD_ERR_OK:
        return true;

      // PHPの設定ファイルで設定されたサイズを超えている、もしくはフォームで指定されたサイズを超えている場合
      case UPLOAD_ERR_INI_SIZE:
      case UPLOAD_ERR_FORM_SIZE:
        throw new \Exception('ファイルサイズが大きすぎます');

      // それ以外の何らかのエラーがあった場合
      default:
        throw new \Exception('何らかのエラー: ' . $_FILES['image']['error']);
    }
  }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/02/03 22:43

    あ、結構それ大事なので質問本文に追記してください。

    キャンセル

  • m.ts10806

    2019/02/03 22:43

    Windowsのバージョンは基本的にあまり関係ないですね。

    キャンセル

  • takopo

    2019/02/03 23:03

    サーバーの情報を追記させていただきました。

    キャンセル

回答 1

check解決した方法

0

自己解決しました。csvに書き込みできなかったのは、$post_data = [$_POST['comment']];でコメントを取得してfputcsvで書き込もうとしたところ、fclose($fp)で閉じる前に、upload 関数の中でリダイレクトしてしまうので、コメントがきちんと取得できていなかったのが原因でした。
なので、upload 関数の中でリダイレクトする前にfputcsvを行うようにしまして、画像表示もgetImagesで行うのではなく、csvから流し込むような形で表示させるとうまくいきました。大変お騒がせしました。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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