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

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

ただいまの
回答率

87.59%

画像をアップロードする方法がわかりません

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,008
退会済みユーザー

退会済みユーザー

PHPで掲示板を作っています。

【開発環境】
win10
xammp7.2.13
PHP7

画像をtmpフォルダからuser_pictureフォルダに移したいのですがうまくいきません。

<?php
session_start();
require('dbconnect.php');

if(isset($_SESSION['id']) && $_SESSION['time'] + 3600 > time()){
  $_SESSION['time'] = time();

  $members = $db->prepare('SELECT * FROM members WHERE id=?');
  $members->execute(array($_SESSION['id']));
  $member = $members->fetch();
}else{
  header('Location: login.php');
  exit();
}

if(!empty($_POST)){
  if($_POST['message'] !== ''){
    //メッセージをDBへ挿入
    $message = $db->prepare('INSERT INTO posts SET member_id=?, message=?');
    $message->execute(array(
      $member['id'],
      $_POST['message']
    ));
  }
  header('Location: index.php');
  exit();
}

if(!empty($_POST)){
  if($_POST['image'] !== ''){

    //ファイルの検査
    $filename = $_FILES['image']['name'];
    if(!empty($filename)){
        $exe = substr($filename, -3);
        if($exe != 'jpg' && $exe != 'gif' && $exe != 'png'){
           $error['image'] = 'type';
        }else{

           //ファイルをアップロード
           $image = date('YmdHis') . $filename;
           move_uploaded_file($_FILES['image']['tmp_name'], 'user_picture/' . $image);
           $images = $image;

           //写真をDBへ挿入
           $pic = $db->prepare('INSERT INTO posts SET member_id=?, picture=?');
           $pic->execute(array(
            $member['id'],
            $_POST['image']));
        }
    }
  }
  header('Location: index.php');
  exit();
}

$posts = $db->query('SELECT m.name, p.* FROM members m, posts p WHERE m.id=p.member_id ORDER BY p.id DESC');

?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>掲示板</title>

    <link rel="stylesheet" href="style.css" />
</head>

<body>
<div id="wrap">
  <div id="head">
    <h1>掲示板</h1>
  </div>
  <div id="content">
      <div style="text-align: right"><a href="logout.php">ログアウト</a></div>
    <form action="" method="post" enctype="multipart/form-data">
      <dl>
        <dt><?php print(htmlspecialchars($member['name'], ENT_QUOTES)); ?>さん、メッセージをどうぞ</dt>
        <dd>
          <textarea name="message" cols="50" rows="5"></textarea>
          <input type="hidden" name="reply_post_id" value="" />
        </dd>
        <dd>      
          <input type="file" name="image" size="35" value="">
          <?php if($error['image'] === 'type'): ?>
          <p class="error">* 画像は「.jpg」「.gif」「.png」を指定してください</p>
          <?php endif; ?>     
        </dd>
      </dl>
      <div>
        <p>
        <input type="submit" value="投稿する" />
        </p>
      </div>
    </form>

<!-- 投稿一覧 -->
<?php foreach ($posts as $post): ?>
    <div class="msg">
    <p><?php print(htmlspecialchars($post['message'], ENT_QUOTES)); ?><span class="name"><?php print(htmlspecialchars($post['name'], ENT_QUOTES)); ?></span>[<a href="index.php?res=">Re</a>]</p>
    <?php if($images !== ''): ?>
    <p><img src="user_picture/<?php print(htmlspecialchars($post['picture'], ENT_QUOTES)); ?>" width="48" height="48" alt="" /></p>
    <?php endif; ?>
    <p class="day"><a href="view.php?id="></a>
    <a href="view.php?id=">
    返信元のメッセージ</a>
    [<a href="delete.php?id="
    style="color: #F33;">削除</a>]
    </p>
    </div>
<?php endforeach; ?>

<ul class="paging">
<li><a href="index.php?page=">前のページへ</a></li>
<li><a href="index.php?page=">次のページへ</a></li>
</ul>
  </div>
</div>
</body>
</html>

データベースはbbsにmembersとpostsのテーブルがあります。

create table members(
id int,
name varchar(255),
email varchar(255),
password varchar(100)
);
create table posts(
id int,
message text,
member_id int,
replay_message_id int,
picture varchar(255)
);

なぜアップロードされたファイルが移動しないのかわかりません。
回答よろしくお願いします。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • 退会済みユーザー

    退会済みユーザー

    2019/01/15 10:34

    テスト環境のOS(Windowsだとか)、webサーバー(apache httpdだとか)とかも示してほしい。そういうの依存で点検するべきところが変わるので。

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2019/01/15 10:44

    >>テスト環境のOS(Windowsだとか)、webサーバー(apache httpdだとか)とかも示してほしい。そういうの依存で点検するべきところが変わるので。
    わかりました!

    キャンセル

  • m.ts10806

    2019/01/15 10:46

    >いえ、違います。例えば、メッセージだけ、写真だけ、または両方投稿できるようにしたいです。
    ご自身が「messageが未入力のときにしかimageに対する処理をしていない」「postsには常にmessageかimageしか入らない」ように組まれているため、そのようにコメントしました。コードを見直してみてください。
    あと「ファイル自身」なのか「拡張子」なのか「ファイルパス」なのかで違います。そのあたりは過去質問にも何度か出ているので確認してください。
    (※ファイルの実体を保存するのはあまり良しとされていません)

    キャンセル

回答 2

checkベストアンサー

+1

解説のため回答におこします。

messageが未入力のときにしかimageに対する処理をしていない

messageが入力された時点でmessageに対するinsert処理が実行されて、終わったらindex.phpにリダイレクトされるため、imageの処理にはいかないようになっています。

postsには常にmessageかimageしか入らない

messageが未入力のときにしかimageに対する処理をしていないの流れから、
どちらかに入るようにしか作られていないということです。

「メッセージだけ、写真だけ、または両方投稿」という仕様なのでしたら、
「送信されていない場合は空情報をinsert」という風にしないといけません。

また「ファイルがアップロードされているか」の検査は$_FILESからnameを取得してemptyかを見るのではなく
is_file_uploaded()という検査用の関数があります。
※上記のPHPマニュアルにもあるように引数はファイルの実体であるtmp_nameを渡さないといけません。

$_FILESのnameは「クライアントマシンの元のファイル名」が格納されています。
実際はuser_pictureの配下に日時をつけたファイルを格納するように作られていますから、クライアントマシンの元のファイル名を保持しても意味がありませんし、ファイル送信された情報を$_POSTで受け取ろうとしてもnullなので何も取得できません。

画像情報をDBに保存したい場合は下記の方法が主だと思います。

  1. ファイルの実体を取得してDBに直接入れる
    ->user_picture配下に設置した画像の実体をfile_get_contents()などで読み込んできて保存。
    (base64_encode()でバイナリ変換しても良い)
  2. ファイルのパスを保存
    ->user_picture/20190115111012.jpgのように保存先のパスそんまま
  3. ファイル名だけ保存
    ->保存フォルダが決まっている場合はファイル名だけ
  4. 拡張子だけ保存
    ->ファイル名がレコードのIDなどと紐付いている、且つ保存フォルダが決まっている場合は拡張子だけ保存しておけばOK
  5. DBには何もしない
    ->ファイル名がレコードのIDなどと紐付いていて、且つ保存フォルダが決まっていて、且つ利用できる拡張子まで決まっている場合は何も保存しなくても良い

1.以外は「画像保存先の情報と紐付くような情報を保存しておく」ですね。
もちろん気をつけなければならないのは画像の保存場所です。
もし、パスそのままをWeb上から参照するのであれば、画像をドキュメントルート配下(ブラウザから参照できる場所)に保存しなければなりません。
Web上から参照させず、必要に応じて(リクエストに応じて。例えばログインしないと見せないとか)表示するのであれば非公開のフォルダに保存しておいて、正しいリクエストが来たときだけ画像の実体を取得してきて表示する方法もあります(バイナリデータとか、GDライブラリとか)

画像を保存する際にDBを絡めたい場合の是非や考え方は過去質問にあります。

何がいいかは一長一短です。仕様も関係してくるのでご自身で決めてください。
どのようなやり方でも要件を満たせればそれでOKです。

ただし、問題があって、ファイルは偽装が可能です。
substr()で後ろから3つとってきたとしてもそれが画像であるという確証はありません。
ファイルが破損しているか、別のファイルの拡張子をかえただけのものではないか、といった検証(バリデーション)は必須です(試しにExcelファイルの拡張子を[jpg]にしてみてください)

下記のような記事は読んでおいたほうが良いです。

色々踏まえて枠だけ作るとこんな感じ。(未検証)

if($_SERVER["REQUEST_METHOD"] === "POST"){

    $message = null;
    $image = null;

    if($_POST['message'] !== ''){
        $message = $_POST['message'];
    }//本当にこれだけならfilter_input()で良い

    if(is_uploaded_file($_FILES["image"]["tmp_name"])){
        //ファイルアップロード処理

        //↑が成功したらDBに保存する画像情報取得
    }

    //メッセージをDBへ挿入
    $dbh = $db->prepare('INSERT INTO posts SET member_id=?, message=?, image=?');
    $dbh->execute(array(
        $member['id'],
        $message,
        $image,
    ));
}

本来は文字数のチェックなども必要です。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/01/15 16:16

    申し訳ありません。
    mts10806さんのコードを参考にして書いたら

    if($_SERVER["REQUEST_METHOD"] === "POST"){

    $message = null;
    $image = null;

    if($_POST['message'] !== ''){
    $message = $_POST['message'];
    }//本当にこれだけならfilter_input()で良い

    if(is_uploaded_file($_FILES["image"]["tmp_name"])){
    //ファイルアップロード処理
    $tempfile = $_FILES['image']['tmp_name'];
    $images = $_FILES['image']['name'];
    $image = date('YmdHis') . $images;
    $filename = 'user_picture/' . $image;
    move_uploaded_file($tempfile, $filename);
    //↑が成功したらDBに保存する画像情報取得

    }

    //メッセージをDBへ挿入
    $dbh = $db->prepare('INSERT INTO posts SET member_id=?, message=?, image=?');
    $dbh->execute(array(
    $member['id'],
    $message,
    $image,
    ));
    }

    このようになりました。
    ただ、ブラウザの再読み込みを押すと「フォーム再送信の確認」とDBと投稿にメッセージが反映されなくなり、私の元のコードを変えてみました。

    キャンセル

  • 2019/01/15 16:17

    後、DBに保存する画像情報取得というのがわかりませんでした。

    キャンセル

  • 2019/01/15 16:24 編集

    私の回答では質問者さんのコードではやりたいことができない理由をたくさん書いています。私の提示したサンプルコードの雛形をそのまま使わないまでも、まずはその理由をきちんと理解して組まないと回答が全く意味なくなりますし、コードは更にグチャグチャになります。

    > ブラウザの再読み込みを押すと「フォーム再送信の確認」とDBと投稿
    「フォーム再送信の確認」はF5でも押したのではないでしょうか。
    普通に送信ボタンを押していたら起きないはずです。
    気になるようでしたらご自身が入れられているindex.phpへのリダイレクトheaderを入れておけば良いとと思います。

    > 後、DBに保存する画像情報取得というのがわかりませんでした。
    ヒントを書いていますよ。
    「user_picture配下に設置した画像の実体をfile_get_contents()などで読み込んできて保存。」
    記事としては下記とか
    https://qiita.com/tadsan/items/bbc23ee596d55159f044

    「ファイルそのものを保存」というより「ファイルをバイナリデータ化して保存」が通例かと思います。あくまで文字列に変換する形ですね。

    キャンセル

0

ディレクトリuser_picture/はフルパスで記載されていませんが、このディレクトリが存在し、Apacheを実行しているユーザーに書き込む権限があることを確認してください。
よくわからない場合は対象ディレクトリにEveryoneに全ての権限をつければ移動できると思います。

別途セキュリティについてはしっかりと設計してください。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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