【自作】PHPでチャット機能の作り方!LINEのような会話形式のプログラムのコードを徹底解説!

この記事からわかること

  • phpで作るチャット機能作り方
  • チャット機能の仕組み
  • 一緒に作りながら学べるチャット機能の説明

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

実際に私が作ったチャット機能と同じものを真似して作れるように説明していきます。途中に出てくるコードの意味や挙動、仕組みなどPHPを学習しながら解説していきたいと思います!

ちなみに今回作成したのはこのような感じのチャット機能です。

自作したチャット機能(←クリック) phpで自作したチャット機能

自作したチャットの構造と機能

今回はデモページの作成のため1人でも楽しめ、誰でも発言可能なチャット機能にしています。とはいえチャット機能の仕組みは変わらないため実際のDMやLINEのように簡単に流用することもできます。

チャット機能

チャット構造(必要なファイル)

phpで自作したチャット機能のファイル構造

使用言語と実行環境

HTMLでフォーム部分を作る

まずはHTMLで文書を作成します。ファイル名は「chat.php」とします。のちのちphpファイルとして扱うので拡張子は「.php」でOKです。

HTMLのhead内はいつも通りのCSSやjsファイルの読み込み処理を書いておきます。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
  <title>チャット</title>
  <link rel="stylesheet" href="css/style.css">
  <link rel="stylesheet" href="css/fontawesome-free-5.15.3-web/css/all.min.css">
  <script src="js/main.js"></script>
</head>
<body>
  <main class="main">
    <div class="chat-system">
      <form class="send-box flex-box" action="chat.php#chat-area" method="post">
        <textarea id="textarea" type="text" name="text" rows="1" required placeholder="message.."></textarea>
        <input type="submit" name="submit" value="送信" id="search">
        <label for="search"><i class="far fa-paper-plane"></i></label>
      </form>
    </div>
  </main>
</body>
</html>

これでHTMLでの基礎部分が完成です。この時点で作成しているのはhead内の文書情報入力フォーム部分です。

mainの中にchat-systemがありその中にsend-boxでグループ分けしています。chat-systemの中には後で違うグループも入れますが今はsend-boxだけで進めていきます。

入力フォームはチャット入力用のtextareaと送信ボタン用のinputです。

input要素とlabel要素のリンク

input要素id属性を、label要素for属性を付与することで2つをリンクさせることができます。

↑例えばこのように紙飛行機をクリックしても入力値にフォーカスが当たります。↓コードはこうなります。

<input id="web-chat"><label for="web-chat">  <i class="far fa-paper-plane" style="cursor:pointer;"></i></label>

この状態ではinput要素も見えてしまっているのでCSSでdisplay:noneを指定してlabel要素だけにすればOKです。

今回はCSSは説明しないのでこの記事の最後にCSSの中身を全て記述しておくのでコピペして使用してください!

form要素でPOSTに値を送信

入力された値はPOSTで受け取りたいためform要素のmethod属性に「POST」を指定します。

飛ばすファイルを指定できるaction属性をchat.php#chat-areaとすることでid:chat-areaの場所に送信後の画面を移動させます。id:chat-area」はまた後でHTMLに組み込みます。

目次機能の見出しへジャンプするのと同じ仕組みですね!

それではここからはphpでの処理に移っていきます。

POSTで入力値を取得する

では先にやることを整理しておきます。ここでやるのは以下の内容です。

POSTで取得した値をchat配列に格納しておきます。POSTを取得する時は最初に$_POST['submit']が存在し、なおかつ中身が「送信」であるかを確認しておきましょう。


<?php   
$J_file = "chatlog.json"; // ファイルパス格納
date_default_timezone_set('Asia/Tokyo'); // タイムゾーンを日本にセット

if(isset($_POST['submit']) && $_POST['submit'] === "送信"){ // #1
    $chat = [];
    $chat["person"] = "person1";
    $chat["imgPath"] = "image/person1.png"; //画像ファイル名は任意
    $chat["time"] = date("H:i");
    $chat["text"] = htmlspecialchars($_POST['text'],ENT_QUOTES);

    // 次はここに記述していきます。
} // #1
?>
<!DOCTYPE html>

画像は好きな画像を2枚用意しimageフォルダの中に入れておいてください。

chat配列の中には連想配列で人、画像パス、時間、チャット本文を格納します。

date関数「H:i」形式を指定することで24時間表示(例16:20)の時間と分数を取得してくれます。「h:i」(小文字)だと12時間表示(例4:20)になります。

入力値が直接入る「チャット本文」はhtmlspecialchars関数でエスケープ処理を施しておきます。

関連記事:クロスサイトスクリプティングの対策コード【htmlspecialchars関数】

この4つの項目をフォーマットとしてデータを蓄積していきます。

チャットログをJSONファイルに格納する

今回チャットログの蓄積先はJSONファイルです。JSONファイルはデータの管理、操作が非常に楽なテキストファイルの1種です。データベースを使うより簡単にデータを蓄積、取得することができます。JSONファイルのことをもっと知りたい方は↓こちらの記事をご覧ください!

関連記事:JSONファイルの構造とは?配列と連想配列の違いを理解して正しい記法を覚えよう!


  $chat["text"] = htmlspecialchars($_POST['text'],ENT_QUOTES);

    // 入力値格納処理
    if($file = file_get_contents($J_file)){ // #2
      // ファイルがある場合 追記処理
      $file = str_replace(array(" ","\n","\r"),"",$file);
      $file = mb_substr($file,0,mb_strlen($file)-2);
      $json = json_encode($chat);
      $json = $file.','.$json.']}';
      file_put_contents($J_file,$json,LOCK_EX);
    }else{ // #2
      // ファイルがない場合 新規作成処理
      $json = json_encode($chat);
      $json = '{"chatlog":['.$json.']}';
      file_put_contents($J_file,$json,FILE_APPEND | LOCK_EX);
    } // #2

    // 次はここに記述していきます。

  } // #1

phpでのJSONファイルの扱い方と挙動はだいぶ長くなるのでこちらの記事にまとめてあります。

関連記事:PHPでJSONファイルの扱い方を徹底解説!作成、追記、読込などの基本的な動作と注意するべきポイントとは?

ここでの処理の流れを確認してみましょう!

phpでjson_encode関数を使用することでデータを自動でJSON形式にエンコード(符号化)してくれます。エンコード処理の前に連想配列に入れたいのでファイルがある時(追記処理)とない時(新規作成処理)で前準備を工夫しています。

追記処理と言っていますが行っているのはファイルデータを取得してそのデータにチャットログを追加→既存のファイルに上書きです。


// 上のコードを訳すとこのような感じです
  // 行番号は同じなので照らし合わせてみてください
    // 入力値格納処理
    if(ファイルを読み込む){ // #2
      // ファイルがある場合
      ファイルデータの中の空白や改行を削除
      ファイルデータ末尾の']}'の2文字を削除
      chat配列のデータをエンコード
      連想配列に格納する前準備(後側を付与)ファイルデータ+新チャットログ
      ファイルを上書き&データを格納
    }else{ // #2
      // ファイルがない場合
      chat配列のデータをエンコード
      連想配列に格納する前準備(前側を付与)
      ファイルを作成&データを格納
    } // #2

    // 次はここに記述していきます。

  } // #1

チャットログJSONファイルの構造

JSONファイルのここでの構造はやや複雑になっています。

取り出す際も階層が深い分ややこしくなるので注意してください!

⇩{"連想配列1"}
【1階層】⇩連想配列1→{"chatlog":"[配列1]"}
【2階層】⇩[配列]1→["連想配列2","連想配列2","連想配列2"....]
【3階層】連想配列2(チャットログ)→{"person":"person1","imgPath":"image/person1.png","time":"16:20","text":"チャットの本文"}

3階層目にチャットログを蓄積していきます。

{"chatlog":
  [
    {"person":"person2","imgPath":"..\/image\/person2.png","time":"22:26","text":"\u30c1\u30e3\u30c3\u30c8\u6a5f\u80fd\u3092\u81ea\u4f5c\u3057\u3066\u3044\u307e\u3059\u3002"},
    {"person":"person1","imgPath":"..\/image\/person1.png","time":"22:27","text":"\u30c1\u30e3\u30c3\u30c8\u30ed\u30b0\u306fJSON\u30d5\u30a1\u30a4\u30eb\u3067\u683c\u7d0d\u3057\u3066\u3044\u307e\u3059\u3002"}
  ]
}

POSTの二重送信対策

POSTで入力値を扱う時に適切な処理をしないと,ページをF5やcommand+Rを押してリロードすると先ほど入力した内容がもう一度送信されてしまいます。

これを防ぐにはPOSTでの操作を終えた後に自分自身にリダイレクトさせるとPOSTの値をリセットすることができます。

詳しくはこちらの記事をご覧ください。

関連記事:【php】リロード対策!postを重複しないように自分自身にリダイレクトさせて解決しよう!


    } // #2
    // header('Location:https://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/chat.php');
    header('Location:./chat.php');
    exit;   
  } // #1
  
  // 次はここに記述していきます。
  

header関数のLocationヘッダにURLを指定することでリダイレクトさせることができます。サーバーやホスト名に依存しないように現在のホスト名やディレクトリを自動で取得できる関数を使って保守性を高めておきます。

※MAMPでこのコードを使うと一度表示されませんが、そのままURLをもう一度読み込めば問題なく動きます。。。

一応保守性の高いコードはコメントアウトしておきローカル環境での動作確認用に相対パスのリダイレクトも載せておいたので切り替えてご使用ください。

チャットログを取得し表示する

続いてJSONファイルに格納してあるログをページ上に表示していきます。ログがあれば表示なければ何も表示しないように分岐できるようにしておきます。

file_get_contents関数は中身があればそのデータがなければfalseを返す特性を利用して分岐処理させます。


} // #1
if($file = file_get_contents($J_file)){
    $file = json_decode($file);
    $array = $file->chatlog;
    foreach($array as $object){

        if(isset($result)){
            // 第二回目以降
            $result =  $result.'<div class="'.$object->person.'"><p class="chat">'.str_replace("\r\n","<br>",$object->text).'<span class="chat-time">'.$object->time.'</span></p><img src="'.$object->imgPath.'"></div>';
        }else{
            // 第一回目
            $result = '<div class="'.$object->person.'"><p class="chat">'.str_replace("\r\n","<br>",$object->text).'<span class="chat-time">'.$object->time.'</span></p><img src="'.$object->imgPath.'"></div>';
        }


    } 
}

格納時にデータをエンコードしていたので取得する際はデコード(複合化)して使えるデータに変換します。phpのオブジェクトを扱う時のようにデータの下層へと掘り進め、必要なデータを元に変数$resultの中にHTML文書を組み立てておきます。

変数$resultに格納する際はisset($result)で中身が空かどうかを確認します。ここで7行目のif文を無くすと初回のみ変数$resultが空の状態のまま参照しようとしてNotice: Undefined variable: resultのようなエラーが出てしまいます。

JSONファイルからデータを取り出す時のコツもこちらの記事にまとめてあります。

関連記事:PHPでJSONファイルの扱い方を徹底解説!作成、追記、読込などの基本的な動作と注意するべきポイントとは?

あとは好きなところに$resultを表示させるだけなので入力フォームの上に表示させておきます。

chat-systemの中に新しいグループchat-boxを作りその中にチャットを表示するエリア(chat-area)と最初に作成したフォームを入れ込んでおきます。



<div class="chat-system">
  <div class="chat-box">
    <div class="chat-area" id="chat-area">
      <?php echo $result; ?>
    </div>
    <!-- 最初の入力フォーム -->
    <form class="send-box flex-box" action="chat.php#chat-area" method="post">
      <textarea id="textarea" type="text" name="text" rows="1" required placeholder="message.."></textarea>
      <input type="submit" name="submit" value="送信" id="search">
      <label for="search"><i class="far fa-paper-plane"></i></label>
    </form>
    <!-- 最初の入力フォーム -->
  </div>
</div>

ここまで作成できたら一度ファイルを開いてみましょう。そのまま開いてもphpは動作しないのでローカル環境でURLを指定して開きましょう!MAMPでいうと「htdocs」の中に今回作成しているchatファイルを置いて「http://localhost/chat/chat.php」と指定すれば開けるはずです。

CSSファイルを読み込んでいれば下画像のようになるはずです。この時点でもチャット機能は正常に動作するので試してみてください。

phpで自作したチャット機能の途中経過

チャットを送信するともしかしたら「このサイトにアクセスできません」と出るかもしれません。先ほども説明した通り$_SERVERを使ったheader関数が上手く動作しないことがありますがもう一度URLを読み込めば正常に動きます。またこれはローカルのみでの挙動で実際にサーバーにアップロードしたファイルではこの現象は起きませんでした。

今回やったこと

長くなってきたので続きは次回にしていきます。その前に今回やったことをおさらいしておきましょう!

  1. HTMLでの文書基盤作成
  2. CSSでのデザイン
  3. 入力値をPOSTで処理
  4. JSONファイルに格納
  5. JSONファイルから取得し表示

ここまでで半分ほど作業が完了しました!次にやらなければいけないことは以下の通りです!

  1. ユーザーを切り替えたフラグの生成
  2. ユーザーを切り替えた時のデータ格納の分岐
  3. チャットログのリセット機能
  4. Topページに戻るときにセッションを破棄

第2回:【第2回】PHPでチャット機能の作り方!ユーザー切り替えの分岐と適切な終了処理

第3回:【Ajax】チャットログを自動更新して表示させる方法!phpとjavascriptで実装

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

CSSファイルの中身

いかにCSSを載せておきます。コピペしてご使用ください。


@charset  "utf-8";
html {
    font-size: 62.5%;
}

body {
    font-size: 1.7rem;
    margin: 0;
}

.text-center {
    text-align: center;
}

.chat-box {
    border: 5px solid;
    width: 80%;
    margin: 30px auto;
    background-color: rgba(51, 51, 51, 0.208);
}

.chat-box #textarea {
    resize: none;
    width: 100%;
    height: 40px;
    line-height: 40px;
    border-width: 2px;
    margin: 30px 0;
    font-size: 2.0rem;
}

.send-box {
    text-align: center;
    padding: 0 40px;
}

.chat-area {
    max-height: 60vh;
    padding: 30px 60px;
    overflow-y: scroll;
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.chat-area::-webkit-scrollbar {
    display: none;
}

.chat-system input[type="submit"] {
    display: none;
}

.change-person {
    text-align: center;
}

.change-person .on {
    background-color: rgb(230, 196, 83);
}

.change-person i:hover {
    font-size: 6.0rem;
    transition: all 0.3s;
    color: rgb(230, 196, 83);
}

.change-person img,
.change-person i {
    transition: all 0.3s;
    width: 80px;
    height: 80px;
    font-size: 5.0rem;
    padding: 10px;
    border: 5px solid #333;
    border-radius: 50%;
    line-height: 80px;
    text-align: center;
    cursor: pointer;
    margin: 20px;
    overflow: hidden;
}

.send-box label {
    margin: 30px 0;
}

.send-box .far.fa-paper-plane::before {
    font-size: 3.5rem;
    line-height: 44px;
    cursor: pointer;
    display: inline-block;
    width: 200px;
    height: 44px;
    border: 2px solid;
    background-color: #fff;
    text-align: center;
    vertical-align: bottom;
    transition: all 0.3s;
}

.far.fa-paper-plane:hover::before {
    background-color: rgb(58, 157, 124);
    transition: all 0.3s;
    color: #fff;
}

.second .far.fa-paper-plane:hover::before {
    background-color: rgb(194, 97, 97);
}

.chat-area .person1 {
    text-align: right;
    vertical-align: top;
}

.chat-area .person2 {
    text-align: left;
    position: relative;
}

.second .chat-area .person1 {
    text-align: left;
    position: relative;
}

.second .chat-area .person2 {
    text-align: right;
    position: initial;
}

.chat-area .chat {
    position: relative;
    margin: 20px;
    background-color: #fff;
    border-radius: 5px;
    padding: 15px;
    display: inline-block;
    max-width: 300px;
    text-align: left;
    word-wrap: break-word;
}

.chat-area .chat::after {
    content: '';
    display: inline-block;
    width: 0;
    border-top: #fff solid 15px;
    border-left: transparent solid 6px;
    border-right: transparent solid 6px;
    position: absolute;
    transform: rotate(-120deg);
    right: -9px;
    bottom: 20px;
}

.chat-area .person2 .chat::after {
    border-top: #fff solid 15px;
    border-left: transparent solid 6px;
    border-right: transparent solid 6px;
    position: absolute;
    transform: rotate(120deg);
    right: initial;
    left: -9px;
}

.second .chat-area .chat::after {
    transform: rotate(120deg);
    right: initial;
    left: -9px;
}

.second .chat-area .person2 .chat::after {
    transform: rotate(-120deg);
    left: initial;
    right: -9px;
    bottom: 20px;
}

.chat-area .chat-time {
    position: absolute;
    left: -60px;
}

.chat-area .person2 .chat-time {
    left: initial;
    right: -60px;
}

.second .chat-area .chat-time {
    left: initial;
    right: -60px;
}

.second .chat-area .person2 .chat-time {
    right: initial;
    left: -60px;
}

.chat-area img {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    border: 5px solid #fff;
    margin: 20px 0;
    vertical-align: bottom;
}

.chat-area .person1 img {
    background-color: aquamarine;
}

.chat-area .person2 img {
    background-color: rgb(194, 97, 97);
}

.chat-area .person2 .chat {
    margin-left: 80px;
}

.second .chat-area .chat {
    margin-left: 80px;
}

.chat-area .person2 img {
    position: absolute;
    left: -10px;
    bottom: 0px;
}

.second .chat-area img {
    position: absolute;
    left: -10px;
    bottom: 0px;
}

.second .chat-area .person2 img {
    position: initial;
}

.btn {
    display: block;
    width: 200px;
    margin: 20px auto;
    padding: 15px;
    font-size: 1.4rem;
    cursor: pointer;
    border: none;
    background-color: #f5f2f0;
}

.btn:hover {
    opacity: 0.8;
}

.flex-box {
    display: flex;
    justify-content: center;
}

form {
    text-align: center;
}

@media (max-width:768px) {
    .chat-box {
        width: 100%;
        margin: 30px auto;
        padding: 20px 0;
        border-left: none;
        border-right: none;
    }
    .chat-area {
        height: 100vh;
        padding: 0px 15px;
    }
    .send-box {
        padding: 0 5px;
    }
    .send-box label {
        margin: 0;
    }
    .chat-system .send-box.flex-box {
        width: 100%;
        padding: 10px 0px 30px;
        flex-direction: row;
        align-items: center;
        gap: 0 !important;
    }
    .chat-box #textarea {
        width: 70%;
        margin: 0;
        font-size: 1.3rem;
        line-height: 18px;
        min-height: 18px;
        padding: 4px;
    }
    .send-box .far.fa-paper-plane::before {
        width: 60px;
        height: 30px;
        margin: 0;
        line-height: 30px;
        font-size: 1.6rem;
    }
    .chat-area .chat {
        margin: 10px 15px;
        padding: 10px;
        max-width: 200px;
        word-wrap: break-word;
        font-size: 1.3rem;
    }
    .chat-area img {
        width: 30px;
        height: 30px;
        margin: 10px 0;
        border-width: 2px;
    }
    .chat-area .chat::after {
        bottom: 10px;
    }
    .second .chat-area .person2 .chat::after {
        bottom: 10px;
    }
    .chat-area .person2 .chat {
        margin-left: 45px;
    }
    .second .chat-area .chat {
        margin-left: 45px;
    }
    .chat-area .chat-time {
        left: -45px;
    }
    .chat-area .person2 .chat-time {
        right: -45px;
    }
    .second .chat-area .chat-time {
        right: -45px;
    }
    .second .chat-area .person2 .chat-time {
        left: -45px;
    }
}

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index