【愛犬情報を共有】写真シェア & 掲示板 Webサービスを制作

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/4ce12212-f31f-50f0-1167-43aee8d0641d.jpeg)

プログラミングでWEBサービスを作りました。
<blockquote class="twitter-tweet">
<p dir="ltr" lang="ja">プログラミングでWEBサービスを作成。
愛犬の写真を投稿して、他の飼い主さんと会話のやりとりをします。うちのラブラドールがめっちゃかわいいので、作ってみました笑
ダミーのメールアドレスでも良いので、ぜひ遊んでみてください！（PHP・MySQL・Ajaxなど使用）<a href="https://t.co/Tv35jtKAa4">https://t.co/Tv35jtKAa4</a> <a href="https://t.co/lg7d5ncMOQ">pic.twitter.com/lg7d5ncMOQ</a></p>
— いどはる　idoharu (@idoharu_com) <a href="https://twitter.com/idoharu_com/status/1236462700466565122?ref_src=twsrc%5Etfw">March 8, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote>プログラミングでWEBサービスを作成。
愛犬の写真を投稿して、他の飼い主さんと会話のやりとりをします。うちのラブラドールがめっちゃかわいいので、作ってみました笑
ダミーのメールアドレスでも良いので、ぜひ遊んでみてください！（PHP・MySQL・Ajaxなど使用）<a href="https://uchinoinnu.don-do.com">https://uchinoinnu.don-do.com</a></blockquote>
ユーザー登録かんたんなので、良ければいじってみてください。

<span class="b">サービス「ウチのイッヌ」のURLと、コード（Github）</span>
<div class="surrounded">
<ul>
 	<li>URL：<a href="https://uchinoinnu.don-do.com" target="_blank" rel="noopener noreferrer">https://uchinoinnu.don-do.com</a></li>
 	<li>Github：<a href="https://github.com/don-do/uchinoinnu.git" target="_blank" rel="noopener noreferrer">https://github.com/don-do/uchinoinnu.git</a></li>
</ul>
</div>
愛犬の元気な姿を残しておきたい、また、他の愛犬家さんとも交流できるようなサービスを作ってみたいと考えました。

「イヌ派・ネコ派」と言うように、動物の中でもイヌやネコはコアなファンが多いペット。

そして少子高齢化や、コロナによる新しい生活様式の中、ペット需要が伸びています。
<blockquote>新型コロナウイルスの感染拡大に伴う「巣ごもり生活」の影響で、ペット需要が拡大している。

ペットショップでは売り上げが前年を上回るケースが相次いでいるほか、あるペット用品の通販サイトでは飼い始めに購入することが多い犬用ケージの5月の売上高が前年同月の3倍に急増した。

外でのレジャーが楽しめずペットに喜びを求める人が増えているようだ。
<p class="cmn-article_title cmn-clearfix"><span class="cmnc-middle JSID_key_fonthln m-streamer_medium"><a href="https://www.nikkei.com/article/DGXMZO60335110T10C20A6AM1000/">ペット需要拡大、在宅のお供に　安易な購入には懸念も</a>　</span>2020/6/13 11:30　日本経済新聞　電子版</p>
</blockquote>
そんな中、イヌを飼っている私としては、イヌに特化した情報共有サービスを作りたいと思い作成しました。

全体像は、こんな感じ（<a href="https://cacoo.com/ja/home">Cacoo</a>にて画面全体の構成を作成）。

トップページ・サインイン・サインアウト・マイページ。イッヌ登録ページ・プロフィール編集ページやパスワードリマインダーなど、WEBサービスに必須な機能を備えています。


![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/303f0d00-1f9e-8ba3-9658-f421f73cb846.jpeg)


当記事では、以下７点について書いています。
<div class="surrounded">
<ul>
 	<li><span class="b">サービス概要：</span>ユーザー &amp; 愛犬を登録。掲示板や、お気に入り登録など</li>
 	<li><span class="b">サービスを作った理由：</span>愛犬との思い出を残し、共有できたら楽しそう</li>
 	<li><span class="b">使用技術</span></li>
 	<li><span class="b">サービスの作成にどのように取り組んだか：</span>大まかなワイヤーフレームをもとに、PHP・MySQLでベースを作成</li>
 	<li><span class="b">工夫したところ・難しかったところ：</span>表示画面の切り替え・セキュリティ対策・Ajaxの遷移画面のやりとり</li>
 	<li><span class="b">やってみて感じたこと、得られたこと：</span>動作テストをしながら、不具合に対応していくこと</li>
 	<li><span class="b">今後の課題として取り組んでいきたいこと：</span>toBや業務システムを意識したサービスの作成</li>
</ul>
</div>
<h2>サービス概要：ユーザー &amp; 愛犬を登録。掲示板、お気に入り登録など</h2>
サービス概要は、以下の通り。
<div class="surrounded">
<ul>
 	<li>ユーザー登録（サインイン・サインアウト・退会機能）プロフィール登録・編集</li>
 	<li>パスワード変更・パスワードリマインダー機能</li>
 	<li>愛犬（イッヌ）登録（編集・削除、一覧・詳細表示、検索機能）</li>
 	<li>お気に入り登録（Ajax）</li>
</ul>
</div>
<h3>ユーザー登録（サインイン・サインアウト・退会機能）プロフィール登録・編集</h3>


![image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/30a9f381-777d-cc61-dfec-4ff40d57bb36.gif)


ユーザー管理の一連の処理。
<h3>パスワード変更・パスワードリマインダー機能</h3>
パスワードを忘れた場合、再発行できます。

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/24a16f9c-70f4-7cbf-c3cc-7194509ff215.jpeg)


メールアドレス宛に発行された認証キーを、画面に入力。

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/c0f9c8db-a1a3-8b54-493e-16024c68588b.jpeg)

パスワードが再発行されます。

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/76a804fc-8a92-e81f-fd90-c67f3fa97ce9.jpeg)

<h3>愛犬（イッヌ）登録（編集・削除、一覧・詳細表示、検索機能）</h3>
![image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/b0379346-d148-a71e-efbb-5b89a93be4d6.gif)


コンテンツ管理の一連の処理。
<h3>お気に入り登録（Ajax）</h3>
お気に入り登録すると、マイページに反映されます。
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/2eea6114-7d68-de7e-f2d3-1fb78ac34d32.jpeg)
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/6df04325-76b8-8fe7-4ad8-07945d0326d7.jpeg)
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/8cb2a95d-ad1d-97ed-8478-81bdcc0a7afd.jpeg)
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/22eb787a-3f37-414c-07bd-aac20b5f4d05.jpeg)


なお、サインインしたときはAjax処理を使えるようにし、サインアウト時はdisabledにして使えないようにして、分岐を分けています。
<h2>サービスを作った理由：愛犬との思い出を残し、共有できたら楽しそう</h2>

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/402cbdc8-e442-3497-abd1-36c402915398.jpeg)



自分の愛犬は家族だなぁと思います。

朝はあいさつに来てくれるし、表情を見ていろいろ察して動いてくれる（飼い主の都合の良い解釈しすぎ？^ ^;）。思い出がたくさんあります。

イヌに愛情を持つ人が多いと思ったので、共有できたらなあと思ったためです。

また、少子高齢化とコロナの影響によってペットの需要は増えています。<span class="b">コミュニケーションの手段として、オンラインで愛犬の情報交換ができるものを作りたい</span>と考え、制作しました。
<h2>使用技術</h2>
以下、６点
<ul>
 	<li>PHP（フルスクラッチ）・MySQL。セキュリティ・PHPのロジックを意識</li>
 	<li>Ajax</li>
 	<li>jQuery：レスポンシブ時、ハンバーガーメニュー</li>
 	<li>外部設計〜内部設計を意識。DBは正規化しつつ、テーブル・カラムを作成</li>
 	<li>単体テスト・連結テスト・総合テスト</li>
 	<li>実機テスト、開発ツール確認（Chrome・Safari・Edge）、開発ツールにてレスポンシブ縦横幅を確認（iPhone6/7/8系・Plus系・X系）</li>
</ul>
<h3>PHP（フルスクラッチ）・MySQL。セキュリティ・PHPのロジックを意識</h3>
<span class="b">セキュリティで意識したこと：</span>以下２点
<ul>
 	<li>プリペアドステートメントを使用</li>
 	<li>htmlspecialchars関数によるサニタイズ</li>
</ul>
（詳細は、記事半ばの「工夫したところ・難しかったところ」にて書いています）

<span class="b">PHPのロジック：</span>ファイル冒頭にてPHPを記述・HTML側では変数を出力

PHPとHTMLの住み分けを意識。

（画面切り替えの都合上、完全に住み分けていない部分もあります）
<h3>Ajax</h3>
お気に入りボタンをクリック時に、非同期でDB内のレコードを編集
<h3>jQuery：レスポンシブ時、ハンバーガーメニュー</h3>
レスポンシブ時（ブレークポイント：〜767px）の、ハンバーガーメニュー設置
<h3>外部設計〜内部設計を意識。DBは正規化しつつ、テーブル・カラムを作成</h3>
サービスの要件・概要を考え、ワイヤーフレームを作成。そこから、プログラムの全体像を意識しつつ、作成していきます。

サービスに必須の<span class="b">外部設計〜内部設計を意識</span>して作りました。

なお、DBの設計では、<span class="b">正規化</span>を行っています。
<h3>単体テスト・連結テスト・総合テスト</h3>
単体テスト、連結テスト（状況に応じて、<span class="b">同値クラスや境界値のテスト</span>など）を行いつつ、コードを作成・編集。

ある程度作ったら、サービスを使いつつ総合テストを行いました。

厳密にシナリオ作成やテストケースの作成をしたわけではありませんが、バグが出そうな重要な箇所を重点的にテスト。
<h3>実機テスト、開発ツール確認（Chrome・Safari・Edge）、レスポンシブ縦横幅を確認（iPhone6系・Plus系・X系）</h3>
スマホの<span class="b">実機は、iPhoneXとAndroid</span>にて確認。

開発ツールでの確認は、<span class="b">Chrome・Safari・Edge</span>にて、見え方や動き方を確認しました。

なお、開発ツールの<span class="b">レスポンシブは、iPhone6/7/8系・Plus系・X系にて、縦横幅の違い</span>を確認しています（タブレットには対応していないので、一部崩れます。時間の関係で未改修）。
<h2>サービスの作成にどのように取り組んだか：大まかなワイヤーフレームをもとに、PHP・MySQLでベースを作成</h2>

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/6b577870-5e23-8d7c-edf0-331d6dd14601.jpeg)

大まかにワイヤーフレームを作成（全１４ページ）。

機能とモノを洗い出し、DB・テーブル設計。

PHPのロジックを作りつつ、変更しては動かし、都度修正していきました。
<h2>工夫したところ・難しかったところ：表示画面の切り替え・セキュリティ対策、Ajaxの遷移画面のやりとり</h2>
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/b8b84f94-94fc-47c8-4fbb-1e15b9aa6d62.jpeg)


工夫したところ、難しかったところなど４点紹介します。
<div class="surrounded">
<ul>
 	<li>PHPのロジック：表示画面の切り替え</li>
 	<li>SQLインジェクション・XSS対策：プレースホルダ・htmlspecialchars関数の使用</li>
 	<li>Ajax：画面遷移と、Ajaxの値のやりとりが難しかった</li>
 	<li>PHPのロジックとHTMLの分担を意識：冒頭PHPと後半HTML（インデント を揃え、コメントを書きなど）</li>
</ul>
</div>
では、それぞれ見ていきます。
<h3>PHPのロジック：表示画面の切り替え</h3>
ユーザーの使い勝手を考えつつ、分岐を作るのが大変さを感じつつ、<span class="b">達成感を得られた</span>ところ。

たとえば、コンテンツ（イッヌ）詳細ページにて。

コンテンツ（イッヌ）の<span class="b">ユーザー</span>がアクセスしたら、編集ページへ。

コンテンツ（イッヌ）の<span class="b">ユーザーでない人</span>がアクセスしたなら、掲示板画面へ遷移するボタンが表示されるように組みました。

```php
  <div class="btn-container">
  <?php
    // ユーザーによる、場合分け遷移ボタン
    // ユーザーのイッヌの場合、編集画面へ遷移するボタンを表示
    if(!empty($_SESSION) && $_SESSION['user_id'] === $viewData['user_id']){
  ?>
    <a href="registDog.php<?php echo '?d_id='.$d_id; ?>">
      <input value="編集する！" class="btn btn-primary">
    </a>
  <?php
    // ユーザーのイッヌでない場合、掲示板画面へ遷移するボタンを表示
    }elseif(!empty($_SESSION) && $_SESSION['user_id'] !== $viewData['user_id']){
  ?>
      <input type="submit" value="声をかけてみる！" name="submit" class="btn btn-primary">
  <?php
    // サインインしていない場合、サインインページへ遷移するボタンを表示
    }else{
  ?>
    <a href="signin.php">
      <input value="サインインページへ" class="btn btn-primary">
    </a>
  <?php
    // 場合分け遷移ボタン終了
    }
  ?>
  </div>
```


あと、コンテンツ編集ページのみ、コンテンツ<span class="b">削除ページへのボタンを出現させる処理</span>を行いました

（コンテンツ登録ページには、削除ページへの<span class="b">ボタンを出現させない</span>）。

また、掲示板も、状況に応じて掲示板画面の出現を切り替えています。
```php
<div class="area-board" id="js-scroll-bottom">
  <?php
    // メッセージがある場合
    if(!empty($viewData['msg'])){
      // メッセージ情報を展開
      foreach($viewData['msg'] as $key => $val){
        // 送信者メッセージかつ、送信者メッセージが相手のものの場合
        if(!empty($val['from_user']) && $val['from_user'] == $partnerUserId){
  ?>
          <!-- 掲示板左側のメッセージ情報 -->
          <div class="msg-cnt msg-left">
            <div class="avatar">
              <img src="<?php echo sanitize(showImg($partnerUserInfo['pic'])); ?>" alt="" class="avatar">
            </div>
            <p class="msg-inrTxt">
              <span class="triangle"></span>
              <?php echo sanitize($val['msg']); ?>
            </p>
            <div style="font-size:.5em;"><?php echo sanitize($val['send_date']); ?></div>
          </div>
      <?php
        // 上記以外。送信者メッセージが自分のものの場合
        }else{
      ?>
          <!-- 掲示板右側のメッセージ情報 -->
          <div class="msg-cnt msg-right">
            <div class="avatar">
              <img src="<?php echo sanitize(showImg($myUserInfo['pic'])); ?>" alt="" class="avatar">
            </div>
            <p class="msg-inrTxt">
              <span class="triangle"></span>
              <?php echo sanitize($val['msg']); ?>
            </p>
            <div style="font-size:.5em;text-align:right;"><?php echo sanitize($val['send_date']); ?></div>
          </div>
  <?php
        }
      }
    // 1と合致。つまり、掲示板が無い場合
    }elseif($viewData === 1){
  ?>
      <p style="text-align:center;line-height:20;">掲示板はありません。</p>
  <?php
    // 掲示板があり、メッセージが無い場合。
    }else{
  ?>
      <p style="text-align:center;line-height:20;">メッセージ投稿はまだありません</p>
  <?php
    }
  ?>
</div>
```


<h3>SQLインジェクション・XSS対策：プレースホルダ・htmlspecialchars関数の使用</h3>
SQL発行時は<span class="b">プレースホルダ</span>を使用。以下は、ユーザー情報取得時の例。


```php
<?php
function getUser($u_id){
  debug('ユーザー情報を取得します。');
  try {
    $dbh = dbConnect();
    // usersテーブルから、レコードを取得
    $sql = 'SELECT id, username, age, tel, zip, addr, email, password, signin_time, pic, delete_flg, create_date, update_date FROM users WHERE id = :u_id AND delete_flg = 0';
    // プレースホルダ割り当て
    $data = array(':u_id' => $u_id);
    // クエリ実行
    $stmt = queryPost($dbh, $sql, $data);

    // クエリ結果のデータを、１レコード返却
    if($stmt){
      return $stmt->fetch(PDO::FETCH_ASSOC);
    // クエリ成功しなかった場合
    }else{
      return false;
    }

  } catch (Exception $e) {
    error_log('エラー発生:'.$e->getMessage());
  }
}
```

また、XSS攻撃対策として、<span class="b">ユーザー情報を出力する部分で</span>htmlspecialchars関数を使用。
```php
<?php
// サニタイズ
function sanitize($str){
  return htmlspecialchars($str,ENT_QUOTES);
}
```


ポスト送信内容に害のあるコードが入っていても、スクリプトを無害化するようにしています。
<h3>Ajax：画面遷移と、Ajaxの値のやりとりが難しかった</h3>
Ajaxの値のやりとりって、PHPとちょっと雰囲気が違いますからね^ ^;

コンテンツ（イッヌ）詳細ページdogDetail.phpにて、クリックしたhtmlのdata属性から<span class="b">商品IDを取得</span>。
```php
<?php
  echo '<i class="fa fa-paw icn-recommend js-click-recommend ';
    // DBにてお気に入り登録している場合、お気に入りアイコンに色をつける
    if(isRecommend($_SESSION['user_id'], $viewData['id'])){ echo 'active'; }
    // data属性からイッヌのIDを取得
  echo '" aria-hidden="true" data-dogid="'.sanitize($viewData['id']).'">お気に入り登録！</i>';
```


商品IDを<span class="b">AjaxでDBへ登録</span>。もしくは、すでに登録されていたら、<span class="b">DBから削除</span>。

```php

<script>
    // お気に入り登録・削除
    var $recommend,
        recommendDogId;
    $recommend = $('.js-click-recommend') || null; // null値にて、変数の中身が空と明示（undefinedが入らないようにする）
    // dogDetail.phpのdata属性・キーdogidから、イッヌのIDを取得
    recommendDogId = $recommend.data('dogid') || null;
    // !== null を判定することで、0もtrueとする。数値の0がfalseと判定されてしまわないため。例外的ではあるが、dogsテーブルのidカラム（イッヌのid）が0の場合の想定をしたため、undefinedとnullの両方を判定した
    if(recommendDogId !== undefined && recommendDogId !== null){
      $recommend.on('click',function(){
        var $this = $(this);
        $.ajax({
          type: "POST",
          url: "ajaxRecommend.php", // 送信先ファイル
          data: { dogId : recommendDogId} // キーdogId : 値recommendDogId(イッヌのid)
        }).done(function( data ){
    //          console.log('Ajax Success'); // 開発・動作確認用コード
          $this.toggleClass('active'); // お気に入りの色を変える
        }).fail(function( msg ) {
    //          console.log('Ajax Error'); // 開発・動作確認用コード
        });
      });
    }
</script>
```


data-に値を持たせます。Ajax処理のファイルで受け取り、処理。

あとは、データがあるか無いかで、INSERT　INTOとDELETEの処理を分けています。

複数のファイルで、データをやりとりし、AjaxとPHPの処理が行われるので理解に時間がかかりました。
<h3>PHPロジックは画面の上部に書いて、HTMLは下部</h3>
PHPで書く前半部分と、HTMLで書く後半部分との<span class="b">役割分担を意識</span>しました。

HTML内で、PHPを書くような感じで。HTML内でのPHPは、なるべく表示させるのみにすることを意識しました。

そして、インデントで整理。コメントは多めに書き、<span class="b">自分でも見直した時に分かりやすいよう心がけています</span>。

ページ最後の空行も、意識しつつ作成。
<h2>やってみて感じたこと、得られたこと：動作テストをしながら、不具合に対応していくこと</h2>
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/6788749f-5930-c596-6c9e-ac6c1548d220.jpeg)


コードを作り、編集して都度動かすと、予想していない不具合が出て来ます。新たな発見があって楽しかったです。

<span class="b">単体テスト、連結テスト</span>を行いつつ作り、最後に実際に<span class="b">サービスを使いつつ総合テスト</span>を行いました。

（厳密にシナリオ作成やテストケースの作成をしたわけではありませんが、バグが出そうな重要な箇所を重点的にテスト）

ユーザーにとってどうなんだろうと考えながらの作業を通じて、いろいろ調べることによって、新たなコードの使い方を気づかせるものにもなります。
<h2>今後の課題として取り組んでいきたいこと：toBや業務システムを意識したサービスの作成</h2>

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675914/0a80eaa1-dafe-2d15-01c1-9d0e46a6511c.jpeg)

今回、toC用として遊んでもらえるよう意識しました。

使い勝手を考えつつサービスを作ると、いろいろな発見があり楽しいです。

今後はサービス利用者の対象を少し変更し、<span class="b">toBを意識しつつ、業務システムにも使えそうなサービスを作っていきたい</span>です。

以上となります。最後まで読んでいただきありがとうございます。