学習内容を備忘録としてまとめます。
メッセージ機能を実装しましたので、作成方法を記載します。

ユーザー同士でメッセージ機能を用いてやり取りすることができます。
実装方法
実装方法について記載していきます。
テーブル構成

messaegeテーブルとmessage_relationテーブルの2つを作成します。
各カラムは下記のような役割を担っています。
| カラム名 | 役割 | 
|---|---|
| id | メッセージID | 
| text | メッセージ内容 | 
| image | メッセージに添付される画像 | 
| user_id | メッセージをしたユーザーID | 
| destination_user_id | メッセージ送信先のユーザーID | 
| created_id | メッセージをした時刻 | 
メッセージ画面
実際にメッセージのやり取りが行われる、メッセージ画面について実装していきます。
<?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
$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関数で情報を取得しています。
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を引数にして情報を取得しています。
$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が取得できるようにしています。
$destination_user = get_user($_GET['user_id']);
送信先ユーザーについては、URLからIDを取得しています。
<a href='message.php?user_id=<?= $destination_user['id'] ?>'>
このようにURLから送信先ユーザーIDを取得しています。
続いてメッセージのやり取りを表示するブラウザ画面をみてきます。
        <?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 foreach ($messages as $message) :?>
先ほどget_messages関数で取得したメッセージ情報をforeach文でループ処理します。
<?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の各カラムをブラウザに表示させています。
メッセージのレイアウトは下記を参照させていただきました。
また、convert_to_fuzzy_time関数では取得した時刻情報から表示形式を渡しています。
詳細は下記の参照をお願いします。
メッセージ送信フォームについてみていきます。
<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をかけるような処理になっています。
※上記のコードは説明上不要な部分を省略しているので、トップの動作画面とは違いがあります。
<input type="hidden" name="destination_user_id" value="<?= $destination_user['id'] ?>">
送信先のユーザーIDを渡しています。
これでメッセージテーブルの中に相手側の情報をもつことができます。
では、先ほど説明した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しています。
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になります。
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テーブルにあるかどうか確認しています。
insert_message($user_id,$destination_user_id);
trueである場合には、insert_message関数で引数の自分のユーザーIDと送信先のユーザーIDを元にrelation_messageテーブルにINSERTしています。
後ほど説明しますが、message_relationテーブルとはメッセージ一覧画面で使用されるテーブルであり、
メッセージのやりとりがあったユーザーに関しては、このテーブルにIDが格納されます。
メッセージ一覧画面

上記動作画面のように、メッセージでやりとりのあったユーザーを表示し一覧化する画面となっています。
テーブルについてはテーブル構成で説明したmessage_relationテーブルを使用します。
メッセージ一覧画面で使用される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テーブルからメッセージのやりとりがあるユーザー情報を取得して、一覧画面にユーザーの名前、アイコン画像等を表示しています。
$message_relations = get_message_relations($current_user['id']);
get_message_relations関数で引数の自分のユーザーIDからメッセージのやりとりがあるユーザー情報を取得しています。
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から取り出します。
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文で条件分岐させています。
$bottom_message=get_bottom_message($current_user['id'],$destination_user['id']);
メッセージ一覧を表示する際に、やりとりの最後のメッセージを表示させるため、get_bottom_message関数でメッセージ情報を取得しています。
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から作成時間の一番古いメッセージを取得しています。
    <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>
あとは、一覧画面に必要なメッセージ情報をブラウザに表示させています。
