【PHP】メッセージ機能実装

学習内容を備忘録としてまとめます。
メッセージ機能を実装しましたので、作成方法を記載します。
![message.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597913/a10721d7-22e1-24e4-d53f-d3cd98df286e.gif)

ユーザー同士でメッセージ機能を用いてやり取りすることができます。

#実装方法
実装方法について記載していきます。

##テーブル構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597913/baaf7044-f5f8-e8e7-4cc8-6c1b80f33e12.png)
`messaegeテーブル`と`message_relationテーブル`の２つを作成します。
各カラムは下記のような役割を担っています。


| カラム名 | 役割 |
|:-:|:-:|
|id|  メッセージID |
|  text | メッセージ内容  |
|  image | メッセージに添付される画像  |
|  user_id | メッセージをしたユーザーID  |
| destination_user_id  | メッセージ送信先のユーザーID  |
|  created_id |  メッセージをした時刻 |

##メッセージ画面
実際にメッセージのやり取りが行われる、メッセージ画面について実装していきます。

```php:message.php
<?php
$current_user = get_user($_SESSION['user_id']);
$destination_user = get_user($_GET['user_id']);
$messages = get_messages($current_user['id'], $destination_user['id']);
?>

<body>
　<div class="message">
    <h2 class="center"><?= $destination_user['name'] ?></h2>
    <?php foreach ($messages as $message) :?>
        <div class="my_message">
        <?php if ($message['user_id'] == $current_user['id']) : ?>
            <div class="mycomment right">
            <span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span><p><?= $message['text'] ?></p><img src="../user/image/<?= $current_user['image'] ?>" class="message_user_img">
            </div>
        <?php else : ?>
            <div class="left"><img src="../user/image/<?= $destination_user['image'] ?>" class="message_user_img">
            <div class="says"><?= $message['text'] ?></div><span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span>
            <?php endif; ?>
            </div>
    <?php endforeach ?>

    <div class="message_process">
    　<h2 class="message_title">メッセージ</h2>
    　<form method="post" action="../message/message_add.php" enctype="multipart/form-data">
        <textarea class="textarea form-control" placeholder="メッセージを入力ください" name="text"></textarea>
        <input type="hidden" name="destination_user_id" value="<?= $destination_user['id'] ?>">
        <div class="message_btn">
            <div class="message_image">
                <input type="file" name="image" class="my_image" accept="image/*" multiple>
            </div>
            <button class="btn btn-outline-primary" type="submit" name="post" value="post" id="post">投稿</button>
        </div>
    　</form>
    </div>
　</div>
</body>
```
23行目の`<?php endforeach ?>`までが、メッセージのやり取りが表示されている箇所で、
それ以降はメッセージを入力するフォーム等が記載されています。

```php
<?php
$current_user = get_user($_SESSION['user_id']);
$destination_user = get_user($_GET['user_id']);
$messages = get_messages($current_user['id'], $destination_user['id']);
?>
```
こちらはメッセージをやり取りする上で必要な情報を取得しています。
それぞれ下記のような役割を担っています。

| 変数名 | 役割 |
|:-:|:-:|
|$current_user|  現在ログインしているユーザー情報 |
|  $destination_user | メッセージ送信先のユーザー情報  |
|  $messages | やり取りされるメッセージ情報  |

`get_user`、`get_messages`関数で情報を取得しています。

```php
function get_user($user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT id,name,password,profile,image
            FROM user
            WHERE id = :id AND delete_flg = 0 ";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':id' => $user_id));
    return $stmt->fetch();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}

function get_messages($user_id,$destination_user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM message
            WHERE (user_id = :id and destination_user_id = :destination_user_id) or (user_id = :destination_user_id and destination_user_id = :id)
            ORDER BY created_at ASC";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':id' => $user_id,
                         ':destination_user_id' => $destination_user_id));
    return $stmt->fetchAll();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}
```
それぞれテーブルから`ユーザーID`と`送信先ユーザーのID`を引数にして情報を取得しています。

```php

$sql = "SELECT *
        FROM message
        WHERE (user_id = :id and destination_user_id = :destination_user_id) or (user_id = :destination_user_id and destination_user_id = :id)
        ORDER BY created_at ASC";
```
ユーザーによってはユーザー情報が互い違いになっているため、
`WHERE`条件でIDが取得できるようにしています。

```php
$destination_user = get_user($_GET['user_id']);
```
送信先ユーザーについては、URLからIDを取得しています。

```html
<a href='message.php?user_id=<?= $destination_user['id'] ?>'>
```
このようにURLから`送信先ユーザーID`を取得しています。

続いてメッセージのやり取りを表示するブラウザ画面をみてきます。

```php
        <?php foreach ($messages as $message) :?>
          <div class="my_message">
            <?php if ($message['user_id'] == $current_user['id']) : ?>
              <div class="mycomment right">
              <span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span><p><?= $message['text'] ?></p><img src="../user/image/<?= $current_user['image'] ?>" class="message_user_img">
              </div>
            <?php else : ?>
              <div class="left"><img src="../user/image/<?= $destination_user['image'] ?>" class="message_user_img">
                <div class="says"><?= $message['text'] ?></div><span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span>
              <?php endif; ?>
              </div>
        <?php endforeach ?>
```
自分のメッセージと送信先のメッセージが表示されるようになっています。

```php
<?php foreach ($messages as $message) :?>
```
先ほど`get_messages`関数で取得したメッセージ情報をforeach文でループ処理します。

```php

<?php if ($message['user_id'] == $current_user['id']) : ?>
  <div class="mycomment right">
  <span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span><p><?= $message['text'] ?></p><img src="../user/image/<?= $current_user['image'] ?>" class="message_user_img">
  </div>
<?php else : ?>
  <div class="left"><img src="../user/image/<?= $destination_user['image'] ?>" class="message_user_img">
  <div class="says"><?= $message['text'] ?></div><span class="message_created_at"><?=  convert_to_fuzzy_time($message['created_at']) ?></span>
<?php endif; ?>
```
メッセージ表示部分をみていきます。
自分と送信先のユーザーメッセージによって、左右に表示させるためにif文で分岐させています。
メッセージテーブルには送信したユーザーIDのカラムがあるので、そちらで判断しています。

messageの各カラムをブラウザに表示させています。
メッセージのレイアウトは下記を参照させていただきました。
>[CSSで作る！吹き出しデザインのサンプル19選](https://saruwakakun.com/html-css/reference/speech-bubble)

また、`convert_to_fuzzy_time`関数では取得した時刻情報から表示形式を渡しています。
詳細は下記の参照をお願いします。
>[投稿日時をTwitterのように「★分前」「★時間前」という文字列変換するユーザ定義PHP関数](http://phpcode.w4c.work/php/status/160)

メッセージ送信フォームについてみていきます。

```html
<div class="message_process">
    <h2 class="message_title">メッセージ</h2>
    <form method="post" action="../message/message_add.php" enctype="multipart/form-data">
    <textarea class="textarea form-control" placeholder="メッセージを入力ください" name="text"></textarea>
    <input type="hidden" name="destination_user_id" value="<?= $destination_user['id'] ?>">
    <div class="message_btn">
    <div class="message_image">
        <input type="file" name="image" class="my_image" accept="image/*" multiple>
    </div>
        <button class="btn btn-outline-primary" type="submit" name="post" value="post" id="post">投稿</button>
    </div>
    </form>
</div>
　</div>
```
コメント内容の記載と画像を添付できるようになっており、
`message_add.php`に遷移しコメントテーブルにINSERTをかけるような処理になっています。
※上記のコードは説明上不要な部分を省略しているので、トップの動作画面とは違いがあります。

```html
<input type="hidden" name="destination_user_id" value="<?= $destination_user['id'] ?>">
```
送信先のユーザーIDを渡しています。
これでメッセージテーブルの中に相手側の情報をもつことができます。

では、先ほど説明した`message_add.php`についてみていきます。
##メッセージ処理

```php:message_add.php
<?php

try
{
$date = new DateTime();
$date->setTimeZone(new DateTimeZone('Asia/Tokyo'));
    
$message_text=$_POST['text'];
$message_image=$_FILES['image'];
$user_id=$_SESSION['user_id'];
$destination_user_id = $_POST['destination_user_id'];

if($message_text=='')
{
    set_flash('danger','メッセージ内容が未記入です');
    reload();
}

if($message_image['size']>0)
{
    if($message_image['size']>1000000)
    {
        set_flash('danger','画像が大きすぎます');
        reload();
    }
    else
    {
        move_uploaded_file($message_image['tmp_name'],'./image/'.$message_image['name']);

    }
}

$message_text=htmlspecialchars($message_text,ENT_QUOTES,'UTF-8');
$user_id=htmlspecialchars($user_id,ENT_QUOTES,'UTF-8');

$dsn = 'mysql:dbname=db;host=localhost;charset=utf8';
$user = 'root';
$password = '';
$dbh = new PDO($dsn,$user,$password);
$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'INSERT INTO message(text,image,user_id,destination_user_id,created_at) VALUES (?,?,?,?,?)';
$stmt = $dbh -> prepare($sql);
$data[] = $message_text;
$data[] = $message_image['name'];
$data[] = $user_id;
$data[] = $destination_user_id;
$data[] = $date->format('Y-m-d H:i:s');
$stmt -> execute($data);
$dbh = null;

if(!check_relation_message($user_id,$destination_user_id)){
insert_message($user_id,$destination_user_id);
}
set_flash('sucsess','メッセージを送信しました');
header('Location:../message/message.php?user_id='.$destination_user_id.'');

}   
catch (Exception $e)
{
print'ただいま障害により大変ご迷惑をお掛けしております。';
exit();
}

?>

<a href="post_index.php">戻る</a>
```
メッセージ内容と添付されている画像を確認して、メッセージテーブルにINSERTしています。

```php
if(!check_relation_message($user_id,$destination_user_id)){
insert_message($user_id,$destination_user_id);
}
```
こちらでは`check_relation_message`関数で、`message_relation`テーブルに自分のIDと送信先ユーザーのIDがあるかどうか確認しています。
今回は関数の前に`!`マークがついているので、あれば`false`なければ`ture`になります。

```php
function check_relation_message($user_id,$destination_user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT user_id,destination_user_id
            FROM message_relation
            WHERE (user_id = :user_id and destination_user_id = :destination_user_id)
                  or (user_id = :destination_user_id and destination_user_id = :user_id)";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $user_id,
                         ':destination_user_id' => $destination_user_id));
    return $stmt->fetch();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}
```
このように引数の`自分のユーザーID`と`送信先ユーザーID`の組み合わせが`message_relation`テーブルにあるかどうか確認しています。

```php
insert_message($user_id,$destination_user_id);
```
`true`である場合には、`insert_message`関数で引数の`自分のユーザーID`と`送信先のユーザーID`を元に`relation_message`テーブルに`INSERT`しています。

後ほど説明しますが、`message_relation`テーブルとはメッセージ一覧画面で使用されるテーブルであり、
メッセージのやりとりがあったユーザーに関しては、このテーブルにIDが格納されます。

##メッセージ一覧画面
![message_list.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597913/60b1f966-b72c-da97-316f-72debefe0830.gif)
上記動作画面のように、メッセージでやりとりのあったユーザーを表示し一覧化する画面となっています。
テーブルについては`テーブル構成`で説明した`message_relationテーブル`を使用します。

メッセージ一覧画面で使用される`message_top.php`をみていきます。

```php:message_top.php
<?php
$current_user = get_user($_SESSION['user_id']);
$message_relations = get_message_relations($current_user['id']);
foreach ($message_relations as $message_relation) :
if($message_relation['destination_user_id']==$current_user['id']){
$destination_user=get_user($message_relation['user_id']);
}else{
$destination_user=get_user($message_relation['destination_user_id']);
}
$bottom_message=get_bottom_message($current_user['id'],$destination_user['id']);
?>

<body>
    <div class="row">
    <div class="col-8 offset-2">
<a href='message.php?user_id=<?= $destination_user['id'] ?>'>
    <div class="destination_user_list">
    <img src="../user/image/<?= $destination_user['image']?>" class="message_user_img">
<div class='destination_user_info'>
        <div class="destination_user_name"><?= $destination_user['name']?></div>
        <span class="destination_user_text"><?= $bottom_message['text'] ?></span>
        </div>
        <span class="bottom_message_time"><?= convert_to_fuzzy_time($bottom_message['created_at']); ?></span>
    </div>
    </a>
</div>
</div>
<?php endforeach ?>
</body>
```
`message_relation`テーブルからメッセージのやりとりがあるユーザー情報を取得して、一覧画面にユーザーの名前、アイコン画像等を表示しています。

```php
$message_relations = get_message_relations($current_user['id']);
```
`get_message_relations`関数で引数の`自分のユーザーID`からメッセージのやりとりがあるユーザー情報を取得しています。

```php
function get_message_relations($user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM message_relation
            WHERE user_id = :user_id";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $user_id));
    return $stmt->fetchAll();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}
```
引数のユーザーIDから取り出します。

```php
foreach ($message_relations as $message_relation) :
if($message_relation['destination_user_id']==$current_user['id']){
$destination_user=get_user($message_relation['user_id']);
}else{
$destination_user=get_user($message_relation['destination_user_id']);
}
```
`送信先のユーザーID`を取得しています。
メッセージを送信するときに`message_relation`テーブルに`INSERT`されるので、
ユーザーによっては`message_relation`テーブルの`自分のユーザーID`と`送信先のユーザーID`が互い違いになっている可能性があるため、if文で条件分岐させています。

```php
$bottom_message=get_bottom_message($current_user['id'],$destination_user['id']);
```
メッセージ一覧を表示する際に、やりとりの最後のメッセージを表示させるため、`get_bottom_message`関数でメッセージ情報を取得しています。

```php
function get_bottom_message($user_id,$destination_user_id){
  try {
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM message
            WHERE (user_id = :user_id and destination_user_id = :destination_user_id)
                  or (user_id = :destination_user_id and destination_user_id = :user_id)
            ORDER BY created_at DESC";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $user_id,
                         ':destination_user_id' => $destination_user_id));
    return $stmt->fetch();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}
```
引数の`自分のユーザーID`と`送信先のユーザーID`から作成時間の一番古いメッセージを取得しています。

```html
    <div class="row">
    <div class="col-8 offset-2">
<a href='message.php?user_id=<?= $destination_user['id'] ?>'>
    <div class="destination_user_list">
    <img src="../user/image/<?= $destination_user['image']?>" class="message_user_img">
<div class='destination_user_info'>
        <div class="destination_user_name"><?= $destination_user['name']?></div>
        <span class="destination_user_text"><?= $bottom_message['text'] ?></span>
        </div>
        <span class="bottom_message_time"><?= convert_to_fuzzy_time($bottom_message['created_at']); ?></span>
    </div>
    </a>
</div>
</div>
```
あとは、一覧画面に必要なメッセージ情報をブラウザに表示させています。