Udemy「PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門」をガチでレビュー 後編

code

Sec4 PHPとMySQLを組み合わせて使う(メモアプリ)

“55”memo/index.phpを作るだけです。が、「memo」だけではわからなくなりそうなので1周目で作った「memo」を「Udemy.tomosuta.memo」にフォルダ名を変更したところ…MAMPの「MySQL Server」の方が緑ランプが付かない事態に。解決策…というわけではないのですが、URLにもう一度「localhost:8888/Udemy.tomosuta.memo/index.php」と入力しMAMPを再起動で緑ランプ付きました。う~ん…どういうこと??フォルダ名とドキュメント・ルートとブラウザのURL名は制作途中であまり変えないほうが良さそうです。

“56-57”tryで例外処理をcatchに投げるのは呪文ですので、「ここはこう書く決まり」と考えましょう。ずっとprintだったのにここはechoになってますね。結構厳密でutf8をいつものutf-8と書いただけでエラーが起こり、この時点では「host=localhost; port=8889;」に変更しただけでうまくいってました…

”58”$dbはPDO(php date object)の新しいインスタンスであり、$dbに入ってるテーブルを自由に操作可能、今は$db->exec(SQL)を使う。……のはいいんですが、1周目はここですっごい時間掛かりました。というのも「件のデータを挿入しました」しか表示されなかったのです。エラー文が無いため、どこで何が詰まっているのかわからない…「var_dump($count)ではbool(false)」と表示されたため、「0」が返ってきているんだな、くらいは推理できました。ここの質問も多い!!同じ症状の方もいましたがその回答は「入力されていない項目を埋めましょう、item_name_kana=”モモ”, sales=0」それはSec3で作れって言われたから作ったのにね……まぁこれでもダメでしたが。

結局全てを揃えて入力することに、原因は「mysql:dbname = mydb; をmysql:dbname=mydb;に」“空白を全く入れてはいけない”ということです。プログラミングで空白はあってもなくても動作に影響ないというイメージでしたが…とりあえずこれで「1件のデータを挿入しました」が表示できました。コードが悪いのか?MAMPの設定なのか?といった原因が複数考えられるのもバックエンド学習の辛さでしょうか。

“59”$db->query(SQL)でデータを取得、「execは影響を与えた行の数を返す、queryはSELECTで得られた値を受け取る」…は?まぁSELECTで取得する時はqueryで何か書き換えたい時はexec・executeなんでしょう。fetch()は“取り出す”foreachのような使い方です。

“60-61”フォームでデータのやり取り $_POST[‘memo’]で受け取りますが、これは送信側でmethod=”post”かつtextareaのname=”memo”なので。created_at=NOW()でデータを作成した日時が入ります。がこのままでは「ユーザーが入力した内容がSQLに直接書き込まれる」状態であり、悪意を持った人がSQLを攻撃できてしまうので危険です。

$statement = $db->prepare(‘INSERT INTO memos SET memo=?, created_at=NOW()’);
$statement->execute(array($_POST[‘memo’]));

に書き換えます。書けるより「まずは読める」ことが重要。

prepare()内のmemo = ?の段階ではまだわからないので「?」で

実行文executeが$_POST[‘memo’]でデータを持ってきて実行

と僕は読むことにしてます。“引数”みたいな考え方ですかね。ただ「?」が複数ある場合には

$statement->bindParam(1, $_POST[‘memo’]);

の型で1は~~,2は~~と書いていきます。「ガチ、フロントエンド」でthisを束縛するbindが出ましたよね、ProgateのRubyではparamsを見たような。

“62-63”データ一覧をphpMyAdminではなくプロジェクト画面で表示します。$memosが複数あるのでfetch()でそれぞれ取り出す作業をwhileで囲んでいます。print($memo[‘memo’])はわかりにくい「それぞれの$memoの中身のmemoをprintで表示」です。URLパラメーターをWHERE id=?に指定するには個別ページ側のmemo.phpでexecute(array($_REQUEST[‘id’]))

を使います。リンクを貼る側もaタグを

<a href=”memo.php?id=<?php print($memo[‘id’]); ?>”></a>

に書き換えると、<?php print($memo[‘id’]); ?>が1とか2に置き換わります。数字じゃないものとマイナスを弾きますが、「今ある件数以上は?1万とか」と思った方は賢い!!すぐ後で出てきます。

“64”共通ファイルに dbconnect.phpにtyr-catchの例外処理とまとめてrequire(‘dbconnect.php’);でそれぞれのファイルで読み込みます。1:32で何か“カタンッ!”と落ちます。

”65”ページネーション index.phpのSQLにLIMIT 0, 5追記し「最初の5件表示」ですが0の部分を?にして「?page=」のURLを作ることを目指します。?が入るのでqueryはprepareに変更しますが、execute()は型を指定できないのでその前に

$memos->bindParam(1, $_REQUEST[‘page’], PDO::PARAM_INT);

を指定、これではまだページ数ではなく投稿件数が変わるだけですので、5*($page-1)で2ページ目は5*(2-1)で5になり、これがLIMIT 5, 5となります。むっず… さらにさらに$_REQUEST[‘page’]がissetされてない時は$page=1で5*(1-1)でLIMIT 0, 5つまり1ページ目が表示されるように。

“66-67”次ページは<a href=”index.php?page=<?php print($page+1); ?>”></a> 前ページは-1ですがマイナスにならないように$page>=2の時だけに、大変なのは「次ページをいつまで表示するのか」ですが、登場するのは件数÷5で小数点を切り上げるceil

$counts = $db->query('SELECT COUNT(*) as cnt FROM memos');
$count = $counts->fetch();
$max_page = ceil($count['cnt'] / 5);
if ($page < $max_page)

となります。むっず… 投稿件数がcntに入ってます。6件なら6÷5で1.2になり切り上げで$max_pageは2ページになり「今1ページ目 < $max_page=2」の時に「次のページへ」が表示されます。

“68”投稿を変更・更新 難しいですがここまで来た人ならわかる内容です。新しいものはupdate.phpからupdate_do.phpに変数$memoを渡せないことで、ページを跨ぐ際に変数はリセットされてしまうのでしたね。そこで6:50~

<input type=”hidden” name=”id” value=”<?php print($id); ?>”>

を使って画面に表示されないがformとして送信されupdate_do.phpに変数を渡すことが出来ます。それはいいとして…update_do.phpでexecute(array($_POST[‘’memo], $_POST[‘id’]))って2つ受け取ってるじゃん!!「?」が複数はbindParamって言ってたのに…2つくらいなら「,」で区切って使えるのね、もっと多い時はbindParamなのかな?あとLIMITみたいにINT型指定の時

“69”削除 delete.phpを作り、$db->prepare()内のSQLをDELETEにします。こちらは編集画面などはなく「削除する」をクリックで消去するので_doなどはありません。CRUDシステムがシステムの基本になります。

Sec5 Twitter風ひとこと掲示板

複数のファイルを跨いでデータをやり取りするバックエンドの場合はこうして文字で起こすにはどうしても伝わらない部分(特にSec5)があるかと思いますが、本記事はあくまでも補助なのでご了承ください。セールなら1,500円以下で買えるので詳しくは動画本編を見てください。

“70”概要 ”71”データベース準備 新しいデータベースmini_bbsの中にはmembersとpostsテーブルがある。createdはDATETIME型で作成された日時、modifiedはTIMESTAMP型で変更・更新された日時、reply_message_idはどのメッセージに対しての返信メッセージか示しています。”72”formのaction=””が空の場合は自分自身のファイルに飛ぶ、method=”post”なので$_POST[]で受け取ります。$_POST[‘name’]が空だったら$error[‘name’] = ‘blank’で「$errorという配列を準備して~」と言われますが、$error=[]みたいなことはしないんですね。JavaScriptだけ?エラーメッセージを表示したい箇所でifで「$error[‘name’] = ‘blank’があったら」エラーメッセージの表示、となります。

Noticeエラーの消し方もすっごい時間かかりましたし、同じ質問してる人も多かった。本講座の良くない点として同じ所で躓く人が多いのでは?が挙げられます。つまりもっと別の解説の仕方があるのでは?ということ。MAMPとXAMMPの違いとかも原因としてあるんでしょうが…手元のphp.iniの内容がレッスン動画の内容と異なるので大変だったかな、以前どこかで見た左端に「,」が付いているのがコメントアウトというのを知っててよかった。「,」が無い行が変更箇所だろうと予測できました。

“73”エラーチェック 空白だった場合はエラーメッセージ、入力した欄には入力された内容を初期値としてvalue=””に入れていきます。

<?php print(htmlspecialchars($_POST[‘name’], ENT_QUOTES)); ?>

これで「入力してnameを表示してね」ができますのでemailとpasswordでも同じ処理をします。パスワードはstrlenで文字数を○文字以上に。10:03~は次のcheck.phpにどういう条件で飛ぶかを決める、この考え方はこの後も使うので重要です。if(empty($error))で「$errorが空の時」にheader(‘Location: check.php’)という仕組みで、かつエラーの判定は「確認するボタンが押されたら」にする必要がありますので、if(!empty($_POST))つまり「$_POSTが空でなかったら」エラー判定をスタートします。

“74”確認画面 1:57の「$_SESSIONの[‘join’]というキーに対して$_POSTの内容を保存しておきましょう」と言われますが、$_SESSION[‘join’]の扱いが初心者は迷いますよね、[‘join’]って何だっけ?いつ作った?$error[‘name’]もそうでしたが、こういった急に作られる変数をチェックする必要があります。$_SESSION[‘join’] = $_POSTで「送信された内容が$_SESSION[‘join’]に代入」されてcheck.phpでもsession_start()を書くと$_SESSION[‘join’]が使えるようになります。

“75-76”写真アップ inputにtype属性fileが必要でかつ、formにもenctype=”multipart/form-date”を書いておきます。

$image = date('YmdHis') . $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], '../member_picture/' . $image);

といきなりまったく見覚えないコードですが$_FILEに先ほどのinputのtype=”file”で送られた内容が入り、$_FILES[‘image’][‘tmp_name’]が今のデータの場所で’../member_picture/’ . $image)に保存しますのでmember_pictureフォルダを作成します。ファイル名の最後3文字がjpg.gif.png以外だったら弾く、

“77”データベースに保存 Sec4でも使ったnew PDO()ですがここはMAMPを使うとレッスン動画と入力内容が異なるので気を付けてください。登録なのでprepare(INSERT)内が長く、name=?, email=?, password=?, picture=?, created=NOW()となります。また確認画面にはformの内容がないので、ここでも「登録する」ボタンを押してSQLを実行したいので5:50~inputのtype=”hidden”とします。あらかじめhiddenにデータを入れておくことで何も入っていない送信を防ぐ目的があります。sha1でパスワードを暗号化し、unsetで使い終わった$_SESSIONを空にしておきます。

“78”重複登録を防ぐ 同じメールアドレスの人を弾きます。index.phpは<?php?>部分の記述が多いので「重複登録はここに書きます」と言われても「何で?」ってなりますよね。prepare(~~WHERE email=?)としてexecute()内に$_POST[‘email’]とすれば「入力されたemailの人探してね」になります。

“79-80”ログイン画面 パスワードはデータベースには暗号化されたものが登録されているので照会する際も暗号化つまりsha1($_POST[’password‘])を再び使います。「どこでログインに成功したの?」と僕も思ったので、

emailとpasswordを照会したものを$loginとしてSELECTしSQL発行

$login->fetch()で「データ1件が返って来てたら1が得られる」それを$memberに代入

if ($member) で0はfalseそれ以外はtrueなので$memberが1ならindex.phpに飛ぶログイン処理

流れはこういうカンジです。$error[‘login’]=’failed’;とログイン失敗の場合も書くのがプログラミングだなぁをしみじみ感じます。

“81”cookie メールアドレスが自動で入力されている状態にする、よく見る自動ログインに✔を付けるやつですが、

<input id="save" type="checkbox" name="save" value="on">
<label for="save">次回からは自動的にログインする</label>

$_POSTの[‘save’]が’on’になっていたらsetcookieの’emailに$_POST[‘email’]を保存します。それを呼び出す際には$_COOKIE[‘email’] !== ””つまり「cookieのemailが空でない時」に$email = $_COOKIE[‘email’];これで$emailで使い回すことができるように。

“82-83-84”投稿画面 ログインしているかどうかは①$_SESSION[‘id’] = $member[‘id’]; ②$_SESSION[‘time’] = time();がlogin.phpからindex.phpに渡って来たかで判定しますが、ファイルを跨ぐと文字では表せないですね…ただこの辺りまで来ると「またprepareね、それを…executeでしょ」と見えてくるのでは?

ここでトラブル、「投稿がpostsテーブルに保存されない」のです。これは他の方の質問で解決しましたので共有、postsテーブルの「構造」→「reply_message_id」のデフォルトを変更で「なし」となっていたので「ユーザ定義」で「0」にすると投稿を保存できました。ここで質問してる人も多かったです。一覧に表示は「入力されたデータを受け取るわけではないので$db->prepareでなく$db->query」ここで「あっ、そんなのあったね」と思い出せます。またリレーションでmembersとpostsテーブルをつなげてデータを取得するため今回はmembers.id=posts.member_idを結びます。print(htmlspecialchars~が増えすぎてコードが見づらくなります…

“85”返信機能 $_REQUEST[‘res’]が返信先の投稿idになります。この後は3段階…投稿idのデータを$responseに入れ、それをfetch()でそれぞれ$tableに入れ、さらに

$message = '@' . $table['name'] . ' ' . $table['message'];

でtextareaに表示させる内容をまとめます。むっず…どのメッセージに対するメッセージかもreply_message_id=?をprepareに追記、こういうパターンもあるんですね。

8:45~「hiddenって何だ?」mothod=”post”があるから値は送信される…んじゃないの
?質問にもありましたが、

<input type=”submit”>はPOST通信をするためのきっかけボタンとしての役割。

<input type=”hidden” name=”action” value=”submit” />は$_POSTに値を設定するためのもの。

とのこと、7:42~「textareaはvalue属性がありません」ここでは○番の投稿idのメッセージを表示しただけで、「投稿する」を押した時にプログラミング側は「どこに対しての返信?」となってしまうのでしょう。

“86”個別投稿ページ view.php 特に新しいものは無し

“87”削除 自分の投稿にだけ削除処理なので

<?php if ($_SESSION['id'] === $post['member_id']) : ?>
    [<a href="delete.php?id=<?php print(htmlspecialchars($post['id'])); ?>">削除</a>
<?php endif; ?>

「ログイン中のidと投稿のidが同じだったら投稿idのdelete.phpに飛んでね」と読めますね。「削除」を押したらalertで上から「本当に削除しますか?」のようなメッセージを表示してもいいかも。それはJavaScriptとの合わせ技なんでしょうか?

“88”ページネーション Sec4-65と大体同じです。LIMITは数字型で指定のためbindParamでしたね。$page = max($page, 1);では例えば$page=3ならそのまま3ですが、$pageが-2だったら-2と1を比較して大きい「1」が$pageに代入される。逆に$page = min($page, $maxPage);では$pageが100でも$maxPage(例99)の方が小さかったら$pageに99が代入され、つまり投稿のある最後のページが表示されます。

ただリンクがある・ないで「前のページへ」「次のページへ」リンクがクリック出来なくなるレッスン動画の表示より、そもそも文字を表示しないの方がいいと思ったので

<ul class="paging">
        <?php if ($page > 1) : ?>
          <li><a href="index.php?page=<?php print($page - 1); ?>">前のページへ</a></li>
        <?php endif; ?>
        <?php if ($page < $maxPage) : ?>
          <li><a href="index.php?page=<?php print($page + 1); ?>">次のページへ</a></li>
        <?php endif; ?>
 </ul>

としました。これで1ページ目では「次のページへ」だけが表示されるようになります。

“89”ログアウト $_SESSION = array();でsessionを空に上書き、VScodeではif (ini_set(‘session.use_cookies’))の部分が「間違ってますよ!」と赤線ひかれますが問題なく動きましたし、デベロッパーツールのConsoleにもエラーはありません。この後の$params[]の連続は特に解説も無くザーッと終わります。

以上でこの講座は修了です!!お疲れさまでした。簡単SNS掲示板としては機能を兼ね備えたものを取り敢えずは作れました。これを自分でゼロから作れと言われたら大変ですよね…バックエンドの仕事を体験でき、24.000円が1,380円という衝撃のセール価格で個人的には満足できる講座でした。

まとめてみて「hiddenがわからん…」になってしまいましたが現在は

if($_SERVER["REQUEST_METHOD"] != "POST"){
   ブラウザからHTMLページを要求された
} else {
   フォームからPOSTによって要求された
}

という書き方で紹介されているページがありましたので掲載しておきます

1周目は普通に学び、2週目で復習兼ねてレビュー記事としました。progateでサラッとしかPHPの経験ないので記述に誤りがあったかもしれませんが、参考になれば幸いです。

コメント

タイトルとURLをコピーしました