あーる学習帳

自分が勉強したことや気になることなど、99%自分用です。コードを書いてるのでPCから閲覧を推奨。

PHPまとめ③:会員登録の仕組みを作る

第13章-03

今回編集するのは以下の3ファイル。

f:id:R_de_aru:20180708181807p:plain

singup.php

  • POSTデータを取得し、バリデーション、登録までを行う 

signup_view.php

  •  会員登録画面。入力値のエラーも出力する

db_helper.php

メールアドレスのバリデーションやデータ挿入機能を作る

 「メールアドレスは重複させない」「パスワードは暗号化する」の2つを必ず守ること。

 

まずはコントローラであるsignup.phpを作成する。

// signup.php

<?php
 //必要ファイル読み込み
 require_once('config.php');
 require_once('./helper/db_helper.php');
 require_once('./helper/extra_helper.php');

 if($_SERVER['REQUEST_METHOD']==='POST'){
  $name=get_post('name');
  $email=get_post('email');
  $password=get_post('password');

  $dbh=get_db_connection(); //データベース接続
  $errs=array();

  //入力値バリデーション
  //エラーがなければデータ挿入
 }

 include_once('./views/signup_view.php'); //ビューファイル読み込み

必要ファイルを先に読み込むことで、それ以降定数や関数が使用できるようになる。

 

次に入力フォームを作る。デザインは極力排除する。

//signup_view.php

<html>
 <head>
  <meta charset="UTF-8">
  <title>新規ユーザー登録</title>
 </head>
 <body>
  <h1>新規ユーザー登録</h1>
  <form action="signup.php" method="POST">
   <p>お名前:<input type="text" name="name"><?php echo $errs['name']; ?></p>
   <p>メールアドレス:<input type="text" name="email"><?php echo $errs['email']; ?></p>
   <p>パスワード:<input type="password" name="password"><?php echo $errs['password']; ?></p>
   <input type="submit" value="登録する">
  </form>
 </body>
</html>

 

メールアドレスの重複を調べる関数をextra_helper.phpに作成する。

まず、入力するメールアドレスが何件存在しているかを調べる。取得する値は0か1なので、カウント後の値が0より大きければtrueを返すようにする。

//db_helper.php

<?php

 //中略

  function email_exists($dbh,$email){
  $sql="select count(id) from members where email=:email";
  $stmt=$dbh->prepare($sql);
  $stmt->bindvalue(':email',$email,PDO::PARAM_STR);
  $stmt->execute();
  $count=$stmt->fetch(PDO::FETCH_ASSOC); //結果を配列で取得
  if($count['count(id)']>0){ //件数を取得
   return true;
  }else{
   return false;
  }
 }

count(id)を取得する。PDOでは$stmt->rowCount()というコードを使うことで行数を取得できるが、これはすべてのデータベースで動作が保証されない。

次に、入力データを挿入するための関数を作る。データベースへの挿入前にパスワードは暗号化しなければならない。

 //db_helper.php

<?php

//中略

function insert_member_data($dbh,$name,$email,$password){
 $password=password_hash($password,PASSWORD_DRFAULT); //パスワード暗号化
 $data=data('Y-m-d H:i:s');
 $sql="insert into members (name,email,password,created) values (:name,;email,:password,'{$data}')";
 $stmt->bindvalue(':name',$name,PDO::PARAM_STR);
 $stmt->bindvalue(':email',$email,PDO::PARAM_STR);
 $stmt->bindvalue(':password',$password,PDO::PARAM_STR);
 if($stmt->execute()){
  return true;
 }else{
  return false;
 }
}

 password_hash()はPHPで用意された暗号化用の組み込み関数である。password_verify()を使えば、暗号化後のパスワードと入力したパスワードが一致するか確認できる。

入力値を検証するコードをsignup.phpに加える。

//signup.php

<?php

 //中略

 //入力値バリデーション
 if(!check_words($name,50)){
  $errs['name']='お名前欄は必ず50文字以内で入力してください(必須項目)。';
 }
 if(!filter_var($email,FILTER_VARIDATE_EMAIL)){
  $errs['email']='メールアドレスの形式が正しくありません。';
 }elseif(email_exists($dbh,$email)){
  $errs['email']='このメールアドレスは既に登録されています。';
 }elseif(!check_words($email,100)){
  $errs['email']='メールアドレスは100文字以内で入力してください(必須項目)。'
 }
 if(!check_words($password,50)){
  $errs['password']='パスワードは50文字以内で入力してください(必須項目)。'
 } 

 エラー文はinputごとに別々に表示する。$errsにキー名を設定し、エラーがあった場合に代入する。filter_var($email,FILTER_VARIDATE_EMAIL)は組み込み関数で、メールアドレスの形式に一致しなかった場合にfalseが返ってくる。

最後に、データ挿入コードを追加する。

//signup.php

<?php

 //中略

 //エラーがなければデータ挿入
 if(empty($errs)){
  if(insert_member_data($dbh,$name,$email,$password)){ //データ挿入
   header('Location:'.SITE_URL.'login.php'); //ログイン画面へ移動
   exit();
  }
  $errs['password']='登録に失敗しました。';
 }

自作した関数を使ってデータを挿入する。このまま同じ画面には留まらせず、ログイン画面に遷移するようにする。

signup.phpをindex.phpへ名前変更してテスト。というか、必ずindex.phpを開こうとするけどmain.phpとかsignup.phpとかに変更できないんだろうか…。

PHPまとめ②:設定ファイルや関数ファイルを用意する

第13章-02

設定ファイルを作っていく。ここで設定した値はすべて他のファイルに反映される。これにより、後から修正ファイルが必要になった際は効率よく変更することが可能となる。今回は以下のような値を設定する。

//config.php

<?php
define('DSN','mysql:dbname=user;host=localhost;charset=utf8');
define('DB_USER','root');
define('DB_PASSWORD','');
define('SITE_URL','http://192.168.33.11:8000');

//E_NOTICE以外のエラーをすべて出力

error_reporting(R_ALl & ~E_NOTICE);

//セッションの設定
session_set_cookie_param(1440,'/'); 

変数にはスコープといって、参照できる範囲に制限があった。これでは後に変更する可能性がある値が複数のファイルに書かれていた場合、修正が非常に難しくなる。define()を使って定数として定義しておくことにより、すべてのファイルで同じ値を参照できるようになる。例えば一度定義した「DSN」は文字列として扱うのではなく、echo DSN;という形でシングルクォーテーションなしで使用する。

error_reporting()では、エラーの出力をコントロールできる。開発時はすべてのエラーを出力し、公開時にはerror_reporting(0)としてエラーを出力しないといった設定が可能。

session_set_cookie_param()は第1引数がセッションの有効期限、第2引数が有効範囲となる。第2引数を「/」としておけば全範囲でセッションが有効になる。

 

次にhelperファイルを作成する。データベース関連はdb_helper.phpに、それ以外はextra_helper.phpに書いておく。

//db_helper.php

<?php
 function get_db_connect(){
  try{
   $dsn=DSN;
   $user=DB_USER;
   $password=DB_PASSWORD;
   $dbh=new PDO($dsn,$user,$password);
  } catch(PDOException $e){
   echo ($e->getMessage());
   die();
  }
 $dbh->setAttribute(PDO::ATTR_ERRMODE,ERRMODE_EXCEPTION);
 return $dbh;

 // extra_helper.php

<?php
 function html_escape($word){
  return htmlspecialchars($word,ENT_QUOTE,'UTF-8');
 }

 function get_post($key){
  if(isset($_POST['key'])){
   $var=trim($_POST['key']);
   return $var;
  }
 }

 function check_word($word,$length){
  if(mb_strlen($word)===0){
   return false;
  }elseif(mb_strlen($word)>$length){
   return false;
  }else{
   return true;
  }
 }

 今後必要な関数があれば順次追加していく。

PHPまとめ①:ログイン認証と会員専用ページの構成

今回からは現在使っている教材のまとめとして、ログイン認証と会員専用ページの仕組みを作成する。本では簡易的ではあるが実務に沿って…とあるので、今回もその流れでいきます。

 

第13章-01

要件定義をする。今回はログイン機能だけを作るので以下のようになる。

  • emailとパスワードを入力して会員登録できる
  • ログイン後は他の会員の名前を検索できる
  • 同じemailで再度登録することはできない

この他、実際の定義書には「運用要件」というものもあり、「データは毎日バックアップし、1年間分保管する」など実際の運用者用の決まりを定めたものもある。

次に画面をイメージする。図そのものは本の図を使うので割愛。会員登録画面、ログイン画面、ログイン後の会員専用画面を作成する。

テーブルの構造を本に沿って作成する。今回はuserデータベースに「members」というテーブルを作成して管理する。

f:id:R_de_aru:20180708171640p:plain

今回初めてパスワードをMySQLで管理する。パスワードについてはデータベースが攻撃されたことを想定し、そのままの形でデータを挿入するのではなく変化させるので長さを255にしておく必要がある。

簡単なログイン認証の仕組みと流れは以下のようになる。

  1. 会員登録時はすでにメールアドレスが登録されていないか調べる必要がある。データ挿入前にデータベースからデータを引き出して調べるバリデーションが必要となる。
  2. セキュリティ面では、登録時からパスワードの扱いには注意を払う必要がある。入力されたパスワードを暗号化してからデータ挿入する。
  3. ログイン画面からはメールアドレスとパスワードの入力後、メールアドレスが登録されているか、パスワードは一致しているかを確認する。
  4. メールアドレスとパスワードが一致したら、セッションに本人のデータを格納する。
  5. セッションが存在する場合、ログイン中であると判断して会員専用のアクセスを許可する。ログインしていない場合はheader()でログインページに飛ばす。

最後に、ファイル構成を確認する。

f:id:R_de_aru:20180708173958p:plain

「13」、「views」はフォルダ、他の拡張子phpのファイルを作っていく。

config.phpにはWebサービスに関わる設定のみを書く。こうすることで、データベースやドメインを変更したときに1か所だけ書き直せばすべてのファイルに反映するようになる。helperというのは関数ファイルのこと。実際の制作では関数を1つのfunctions.phpにまとめるということはせず、役割ごとに細分化していく。今回はデータベース関連をdb_helper.phpに、それいがいをextra_helper.phpにまとめていく。

HTML関連はviewファイルとして管理する。ログアウトしたときは強制的にログインページへ飛ぶので、必要なのは会員登録ページ、ログインページ、メンバー専用ページの3つになる。処理の流れを書き込むファイルをコントローラと呼んだが、コントローラとビューの対応が明確になっていたほうがわかりやすい。コントローラであるmember.phpのビューファイルはmember_view.phpにするなど、名前をそろえておくとよい。

PHP実習:ショッピングカートを作ろう

この本も終わりが見えてきました…!!

 

第12章-03 実習

今回は通販サイトで使われるようなショッピングカートを作る。

制作の流れは以下の通り。

  1. 商品情報を表示するphpファイルを作る
  2. カートに入れたデータはセッションのキーを商品IDに設定し、個数を代入する
  3. カートの中身を見るためのphpファイル、カートの中身を一括削除するためのphpファイルをそれぞれ用意する

要件定義は以下の通り。

  • カートに追加した商品には「追加ボタン」の代わりに「追加済み」と表示する
  • カートページには「変更」と「削除」の2種類のボタンを用意し、それぞれのボタンに対する処理を分岐させる
  • カートで表示する商品はIDではなく商品名にする

では、商品一覧画面を作っていく。

<?php
 session_start();
 //POSTデータをカート用のセッションに保存
?>
<html>
 <body>
  <h1>商品一覧</h1>
  <a href="cart.php">カートを見る</a>
  <table style="text-align=center">
   <tr><th>商品</th><th>数量</th><th>ボタン<th></tr>
   <form action="" method="POST">
   <tr>
    <td>業務用デスク</td>
    <td>
     <select name="num">
      <?php for($i=1;$i<10;$i++): ?>
       <option value="<?php echo $i; ?>"><?php echo $i; ?></option>
      <?php endfor; ?>
     </select>
    </td>
    <td>
     <input type="hidden" name="product" value="desk_01">
     <?php if(isset($cart['desk_01'])===true): ?>
      <p>追加済み</p>
     <?php else: ?>
      <input type="submit" value="カートに入れる">
     <?php endif; ?>
    </td>
   </tr>
  </form>

  <form action="" method="POST">
   <tr>
    <td>快適いす</td>
    <td>
     <select name="num">
      <?php for($i=1;$i<10;$i++): ?>
       <option value="<?php echo $i; ?>"><?php echo $i; ?></option>
      <?php endfor; ?>
     </select>
    </td>
    <td>
     <input type="hidden" name="product" value="chair_07">
     <?php if(isset($cart['chair_07'])===true): ?>
      <p>追加済み</p>
     <?php else: ?>
      <input type="submit" value="カートに入れる">
     <?php endif; ?>
    </td>
   </tr>
  </form>

  </table>
  </body>
</html>

 「カートに入れる」ボタンが2つ出てくるが、飛び先は同じページとする。一度決定した個数を変更するのはcart.phpで行うものとする。商品のセレクトボックスをforを使って組み立てる。optionのvalueは実際に渡される値、<option></option>の間は表示される値である。今回はどちらも$iを出力する。$cart['desk_01']は商品をカートに入れた場合のみ存在するキーなので、今回はこのキーをisset()でチェックして商品を追加済みかどうか判定していく。

次に、「カートに入れる」を押した後の商品追加の機能を作成する。

 <?php
 session_start();
 //POSTデータをカート用のセッションに保存
 if($_SERVER['REQUEST_METHOD']==='POST'){
  $product=$_POST['product'];
  $num=$_POST['num'];
  $_SESSION['cart'][$product]=$num; //セッションにデータを格納
 }
 $cart=array();
 if(isset($_SESSION['cart'])){
  $cart=$_SESSION['cart'];
 }
 var_dump($cart);
?>

 POSTデータとして$productと$numの2つが渡されているので取得しておく。$_SESSION['cart'][$product]=$numのタイミングで$_SESSION['cart']['desk_01']などのキーが新たに出来上がり、個数が値として格納される。実はこの1行こそ、カートシステムの根幹ともいえる部分である。セッションに値を格納した後で$cartにセッションの配列データを入れておく。これで商品一覧画面は完成。

次に、カートの中身の一覧画面を作成する。

<?php
 session_start();
 $cart=array();
 if(isset($_SESION['cart'])){
  $cart=$_SESSION['cart'];
  }
 var_dump($_SESSION);
?>
<html>
 <body>
  <h1>ショッピングカート</h1>
  <p><a href="index.php">商品一覧へ</a><p>
  <p><a href="delete.php">カートをすべて空に</a></p>
  <table style="text-align=center">
   <tr><th>商品</th><th>個数</th><th>数量</th><th>変更ボタン</th><th>削除ボタン<th><tr>
   <?php foreach($cart as $key=>$var): ?>
    <tr>
     <td>
      <?php
       switch($key){
        case 'desk_01':
         echo '業務用デスク';
         break;
        case 'chair_07':
         echo '快適いす';
         break;
       }
      ?>
     </td>
     <td>
      <?php echo $var; ?>
     </td>
     <form action="" method="POST">
      <td>
       <select name="num">
       <?php for($i=1;$i<10;$i++): ?>
        <option value="<?php echo $i;?>"><?php echo $i; ?></option>
       <?php endfor; ?>
       </select>
      </td>
      <td>
       <input type="hidden" name="kind" value="change">
       <input type="hidden" name="product" value="<?php echo $key;?>">
       <input type="submit" value="変更">
      </td>
     </form>
     <form action="" method="POST">
      <td>
       <input type="hidden" name="kind" value="delete">
       <input type="hidden" name="product" value="<?php echo $key;?>">
       <input type="submit" value="削除">
      </td>
     </form>
    </tr>
   <?php endforeach; ?>
  </table>
 </body>
</html> 

 cart.phpではまずカートデータの取得を行う。セッションデータに$_SESSION['cart']が存在していないこともあるので、取得用の変数$cartを配列として初期化した後、isset($_SESSION['cart'])でキーの存在を確認している。カートデータには商品IDをキー、個数を値とした配列が格納されている。通常商品IDはデータベース挿入用の値として用いられるが、今回はswitch()を使って正式な商品名を表示させている。実際には商品名もデータベースに登録しておいて、そこからデータを引っ張ってくるのが一般的。今回は<form>が複数あるので、hiddenを使って処理の内容(変更or削除)と商品IDを渡す。

hiddenでkindと名付けた値を取得し、その後の処理を分岐させる仕組みを作る。

cart.phpの最初のphp部分を以下のように書き換え。

 <?php
 session_start();
 $cart=array();

 if($_SERVER['REQUEST_METHOD']==='POST'){
  $product=$_POST['product'];
  $kind=$_POST['kind'];
  if($kind==='change'){
   $num=$_POST['num'];
   $_SESSION['cart'][$product]=$num;
  } elseif($kind==='delete'){
   unset($_SESSION['cart'][$product]);
  }
 }

 if(isset($_SESION['cart'])){
  $cart=$_SESSION['cart'];
 }
 var_dump($_SESSION);
?>

$kindには各種ボタンの役割(change or delete)が格納される。まずif文でそれぞれの処理を分岐させる。変更ボタンを押した場合は単純に既存のキーの値を上書きし、削除ボタンではunset()でキーの値そのものを削除する。

最後に、カートの中身を一括削除するdelete.phpを作成する。

<?php
 session_start();
 $session_name=session_name();
 $_SESSION=array();
 if(isset($_COOKIE[$session_name])===true){
  setcookie($session_name,'',time()-3600);
 }
 session_destroy();
 header('Location:cart.php');
 exit; 

header()では、「Location:」の後にURLを指定することで強制的にページをジャンプさせることができる。その後ろのプログラムが実行されないよう、exitをつけて明示的に処理を終了させる。このような処理のことを「リダイレクト」という。

これで完成。

f:id:R_de_aru:20180708164617p:plain

f:id:R_de_aru:20180708164631p:plain

f:id:R_de_aru:20180708164643p:plain

セッションを用いて各ページに商品情報を受け渡ししている。

今回はここまで。次回からはいよいよ本のまとめ。

PHP実習:クッキーとセッション

クッキーとセッションの章が終わればあとはこの本の総まとめとその他実務的な話が出てくるだけということがわかったので、もう少し続けようと思います。

 

第12章-01

クッキーにデータを保存すれば、ページを移動してもデータを保持し続けることが可能になる。以下のコードを作成する。

<?php
 // クッキーに保存
 setcookie('email','sample@sample.com',time()+(60*60*24*30));
?>
<html>
 <body>
  <h1>クッキーの練習</h1>
  <a href='cookie_check.php'>次のページへ</a>
  <a hrel='cookie_delete.php'>クッキーの削除</a>
 </body>
</html> 

 よくあるログイン用のアドレスを覚えておく仕組みはこれだけ。

setcookie()は引数を3つ必要とする。第1引数は取り出すときに指定するキー名、第2引数は保存する値、第3引数は有効期限である。有効期限はUNIXタイムスタンプという1970年からの秒数で指定する。time()で現在のタイムスタンプが取得できるので、それに60秒×60×24×30=30日の間データをキープし続けるよう命令している。

作成したcookieのデータを確認するには以下のcookie_check.phpを作成する。

<?php
 var_dump($_COOKIE);
?>
<html>
 <body>
  <a href='index.php'>戻る</a>
 </body>
</html> 

 この状態でindex.phpにアクセスし、cookie_check.phpへ飛ぶと

array(1) { ["email"]=> string(17) "sample@sample.com" } 

と画面に表示される。クッキーはデータを配列として保存していることが分かる。つまり、echo $_COOKIE['email']というように個別に指定して参照することも可能である。$_COOKIEは$_POSTや$_GETと同じくスーパーグローバル変数なので、どのページからでもアクセスが可能となる。デベロッパーツールを開き、Applicationタグ>Cookieを確認すると、以下のようになっていることが分かる。

f:id:R_de_aru:20180707214327p:plain

ブラウザがクライアントのパソコンの中にクッキーデータを保存しているということがわかる。つまり、他人が同じパソコンを操作すればクッキーデータを盗まれる恐れがあるということである。したがって、クッキーに保存するのは他人に見られても問題ないデータのみということになる。よくある「ログインIDを記憶する」というチェックボックスはログインIDをクッキーに保存するか否かを聞いているものである。また、セキュリティの観点からパスワードをクッキーで管理するのは大変危険である。

クッキーの削除については、以下のcookie_delete.phpのようにすればよい。

<?php
 setcookie('email','',time()-3600);
 var_dump($_COOKIE);
?>
<html>
 <body>
  <p>クッキーが削除されました。</p>
  <a href="index.php">戻る</a>
 </body>
</html> 

クッキーを削除するためのコードは存在しない。そのため、setcookie()を使って第2引数を空文字、第3引数を現在時刻より前に設定して強制的に時間切れの状態を作ることで削除している。

また、クッキーの有効範囲を指定することもできる。デフォルトではドメインは以下全体で有効だが、第4引数に指定することで範囲を狭めることもできる。

 setcookie('email','sample@sample.com',time()+(60*60*24*30),'/practice');

このようにすることで、ドメイン直下のpracticeというディレクトリでのみ有効になる。

 

第12章-02

セッションについて学ぶ。これを学ぶことで、ログイン認証や通販サイトのカートシステムを作れるように!

ブラウザのクッキーにセッションIDを持たせ、アクセス時にWebサーバーにそのIDで問い合わせを行い、セッション情報が見つかれば「こんにちは〇〇さん」と返す動きができるようになる。

セッションでは多次元配列が使えるので、下記コードを試す。

<?php
 session_start();
 $_SESSION['profile']=array('user_name'=>'taro','location'=>'関東');
 $_SESSION['cart']['desk_01']=3;
 $_SESSION['cart']['chair_07']=5;
?>
<html>
 <body>
  <h1>セッションの練習</h1>
  <p><a href='session_check.php'>次のページへ</a></p>
  <p><a href='session_delete.php'>セッションデータ削除</a></p>
 </body>
</html>

セッションを使うときはプログラムの初めにsession_start()と書かなければならず、これによりセッションの仕組みが使用できるようになる。使い方はそのまま配列と同じで、ユーザーデータをまるごと格納することもできる。ログイン時などにデータベースからユーザーデータを引き出して置き、常に「こんにちは、~さん」などと表示することも可能。また、個別に値を代入することもできる。

セッションデータはいつでも引き出して使用できる。session_check.phpを作成して動作を確認する。

<?php
 session_start();
 var_dump($_SESSION); 

 次のページでもsession_start()を書かなければならない。

f:id:R_de_aru:20180707232553p:plain

二次元配列になっていることが分かる。cookieを確認すると…

f:id:R_de_aru:20180707232659p:plain

PHPSESSIDというクッキーを持っていることが分かる。IDのみをクライアントに残し、それを用いてサーバーに保管しているデータへアクセスする仕組み。

実際のサイトのようにセッションデータを取り出してHTML内でデータを出力してみる。session_check.phpを以下のように書き換え。

<?php
 session_start();
 //var_dump($_SESSION);
 $profile=$_SESSION['profile'];
 $cart=$_SESSION['cart'];
?>

<html>
 <body>
  <p>こんにちは、<?php echo $profile['user_name']; ?>さん</p>
  <p>地域:<?php echo $profile['location']; ?></p>
  <h1>カートの中身<h1>
  <hr>
   <table border="1">
    <tr><th>商品ID</th><th>個数</th></tr>
    <?php foreach($cart as $key=>$var): ?>
    <tr align="center"><td><?php echo $key; ?></td><td><?php echo $var; ?></td></tr>
    <?php endforeach; ?>
   </table>
  </hr>
  <a href='index.php'>戻る</a>
 </body>
</html>

f:id:R_de_aru:20180707234402p:plain

このように表示される。

カートの情報を上書きする場合、後から$_SESSION['cart']['desk_01']=12;など、数字を代入するだけで自動的に上書きされる。削除は配列用の組み込み関数であるunset()を使用する。以下のようにコードを書けば指定したものだけの削除が可能。

session_start();

unset($_SESSION['cart']['desk_01']); // 商品データを削除

unset($_SESSION['profile']); // profile全体を削除 

 unset()で指定した階層以下の要素がすべて削除される。cartのdesk_01だけを個別に削除する、profileの配列全体を削除するという風に書いた。

最後に、セッションデータ全体の削除の仕方を確認する。session_delete.phpを以下のように作る。

<?php
 session_start();
 $_SESSION=array(); //配列初期化
 $session_name=session_name(); // セッション名を取得
 if (isset($_COKKIE[$session_name])===true) {
  setcockie($session_name,'',time()-3600); // cookie削除
 }
 session_destroy(); //セッションに関連付けられたデータを削除
 var_dump($_SESSION);
?>

<html>
 <body>
  <a href='index.php'>戻る</a>
 </body>
</html>

まずは$_SESSIONを初期化(全体をunset()するイメージ)。次にsession_name()でデベロッパーツールで確認したクッキー名である「PHPSESSID」を取得し、クッキーに登録されたセッションIDを削除する。最後にsession_destroy()でまだサーバー上に残っているセッションデータを削除する。

 

第12章-02 練習問題

  1. セッションを開始し、キー「age」に36を、キー「email」にsample@sample.comを代入せよ。確認でセッションデータの出力まで行うこと。
  2. 1.で格納したセッションデータののうち、キー「age」の値を40に書き換えよ。また、キー「email」は削除せよ。確認のため、セッションデータの出力まで行うこと。

☆問1のために作成したindex.php

<?php
 session_start();
 $_SESSION['age']='36';
 $_SESSION['email']='sample@sample.com';
 $age=$_SESSION['age'];
 $email=$_SESSION['email'];
?>

<html>
 <body>
  <h1>第12章-02 練習問題</h1>
  <p>年齢:<?php echo $age; ?></p>
  <p>メールアドレス:<?php echo $email; ?></p>
  <a href='session_next.php'>次のページへ</a>
 </body>
</html>

 

f:id:R_de_aru:20180708001919p:plain

☆問2のために作成したsession_next.php

<?php
 session_start();
 $_SESSION['age']='40';
 unset($_SESSION['email']);
 $age=$_SESSION['age'];
 //$email=$_SESSION['email'];
?>

<html>
 <body>
  <h1>第12章-02 練習問題</h1>
  <p>年齢:<?php echo $age; ?></p>
  <!-- <p>メールアドレス:<?php //echo $email; ?></p> -->
  <a href='index.php'>前のページへ</a>
 </body>
</html>

f:id:R_de_aru:20180708002044p:plain

明日はクッキーとセッションの実習としてショッピングカートを作り、余裕があれば総まとめに入る。

PHP実習:ひとこと掲示板を作ろう

昨夜はストレスの赴くまま、久しぶりに引っ張り出した「Horizon Zero Dawn」をずっとやっておりました…。敵を誘い出して罠にはめていくの楽しい…。

 

今日は第11章のまとめとしてひとこと掲示板を作る。その前に…

 

第11章-04

まとめの前準備として、関数のみを分離したファイルを作成しておく。

ファイル名は「functions.php」とし、以下のコードを書いていく。

 <?php
 function html_escape($word){
  //クライアントからフォームへの入力値を出力する場合、必ず行う処理
  return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
 }

function get_post($key){
 //POSTデータ取得用
 if (isset($_POST[$key])) {
  $var=trim($_POST[$key]);
  return $var;
 }
}

function check_words($word,$length){
 //空文字 or 文字数が制限よりも多かった場合はFalseを返す
 if(mb_strlen($word)===0){
  return FALSE;
 } elseif(mb_strlen($word)>$length){
  return FALSE;
 } else{
  return TRUE;
 }
}

function get_db_connect(){
 // MySQL接続用関数
 try{
  $dsn='mysql:dbname=board;host=localhost;charset=utf8'
  $user='root';
  $password='';
  $dbh=new PDO($dsn,$user,$password);
 } catch(PDOExeption $e){
  echo ($e->getMessage());
  die();
 }
 $dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
 return $dbh;
}

 これを作って使うファイルで読み込みを行えば、データベースの接続も「$dbh=get_db_connect();」とだけ書けば済むようになる。

 

第11章ー05

いよいよひとこと掲示板を作る実習に入る。「名前」「ひとこと」「日時」が書き込めるようにし、日時については自動取得する。制作の流れは以下の通り。

  1. ひとことを登録するテーブル(board)を作る
  2. 関数ファイルを読み込み、HTMLファイルも別に分ける
  3. フォームにはバリデーションをつけ、文字数に問題があった場合はエラーを出す

要件定義は以下の通り。

  • 名前欄は50文字まで、コメント欄は200字まで、どちらも入力必須
  • フォームからのデータ挿入時にはbindValue()を使う
  • 入力データを出力するときは、先ほど作ったhtml_escape()を使う

まず、MySQLでテーブルを作成する。

f:id:R_de_aru:20180707181715p:plain

だいぶ手順を忘れているので、今回はデータベースから分けることにした。

MySQL起動

mysql -u root

データベース作成

create database board;

ユーザー設定

grant all on board.* to vagrant@localhost identified by 'abc123';

データベースへ入る

use board;

テーブル作成

create table board(

id int not null auto_increment primary key,

name varchar(50),

comment text,

created datetime

);

データ型でtextを指定した場合、文字数が無制限になる。今回はPHP側でバリデーションをかける。投稿日時を記録するためのフィールドを「created」としたが、これはデータ型をdatetimeと指定している。「0000-00-00 00:00:00」という形で文字列を挿入しないとエラーになるので注意。

次に、先ほど用意したfunctions.phpに新しい関数を追2つ追加する。

function insert_comment($dbh,$name,$comment){
 //Insertクエリ実行
 $date=date('Y-m-d H:i:s');
 $sql="INSERT INTO board (name,comment,created) values (:name,:comment,'{$date}')";
 $stmt=$dbh->prepare($sql);
 $stmt->bindvalue(':name',$name,PDO::PARAM_STR);
 $stmt->bindvalue(':comment',$comment,PDO::PARAM_STR);
 if (!$stmt->execute()) {
  return 'データの書き込みに失敗しました。';
 }

これはコメントを書き込む関数である。データベース接続時に取得した$dbh(切符)を必ず渡すこと。$nameと$commentはPOSTデータを取得後に渡すものである。現在日時取得はデータベースに合わせ、「Y-m-d H:i:s」とすれば「0000-00-00 00:00:00」の形で取得できる。$dateはクライアントからの入力値ではないため、そのまま連結させることが可能。

function select_comments($dbh){
 $data=;
 $sql="SELECT name,comment,created FROM board";
 $stmt=$dbh->prepare($sql);
 $stmt->execute();
 while ($row=$stmt->fetch(PDO::FETCH_ASSOC)) {
  $data
=$row;
 }
 return $data;
}

データを取得する関数である。先ほどと同じく、$dbhを渡す。「select * from ~」と書くことにより、少しではあるがデータベースへの負担を軽くする。$dataは二次元配列、そのままreturnする。

ここから正式なサービスへ近づけていく。まずはindex.phpを作成し、functions.phpを組み込む。

<?php
 // 関数ファイルを読み込む
 repuire_once('./functions.php');

 

 // ここに処理の流れを書く

 

 // HTMLファイル読み込み

 include_once('view.php');

view.phpも空白でいいので作成しておく。view.phpにはHTMLを記述していくが、echoなども使うのでPHPファイルとして作成しておく。

require_once()とinclude_once()はともにファイルを読み込むコードだが、require_once()は読み込んだ途中のコードにエラーがあっても処理を続行し、include_once()はエラーがあれば処理が止まるという違いがある。

ここで処理の流れを確認。

  1. データベースに接続
  2. フォームのデータを取得してバリデーション
  3. データを挿入
  4. データベースから表示用のデータを引き出す
  5. 出力

特に3と4が逆にならないよう注意が必要である。

index.phpにデータベース接続のための処理を書いていく。

repuire_once('./functions.php');
$errs=; //エラー文格納用の配列初期化
$data=
; //データベースから引き出すデータの格納用配列
$dbh=get_db_connect(); //データベース接続 

 前はかなり長かったが、今回からはすっきり。

次に、入力値の検証とデータ挿入の処理を作成する。

$dbh=get_db_connect(); //データベース接続

if ($_SERVER['REQUEST_METHOD']==='POST') {
 $name=get_post('name');
 $comment=get_post('comment');
 if (!check_words($name,50)) {
  $errs='お名前欄を修正してください。';
 }
 if (!check_words($comment,200)) {
  $errs
='コメント欄を修正してください。';
 }
 if (count($errs)===0) {
  $result=insert_comment($dbh,$name,$comment);
 }

作成したget_post()でデータを種痘する。これでtrim()も自動的に行われる。check_words()でfalseが返ってきたらデータ挿入派でいない。$errsにエラーメッセージを格納し、$errsの要素が存在しない場合はinsert_data()を実行する。

次に、入力値の検証とデータ挿入を作成する。

if ($_SERVER['REQUEST_METHOD']==='POST') {
 //中略
}
$data=select_comments($dbh);
include_once('view.php');

データの取得は必ず行うので、if ($_SERVER['REQUEST_METHOD']==='POST')の外に書く。

最後にview.phpのプログラムを作成する。ここまでで表示データの取得は終えているので、出力に関わる部分を作成する。

<html>
 <body>
  <h1>ひとこと掲示板</h1>
  <table border="1">
   <tr style="background-color:orange"><th>名前</th><th>コメント</th><th>時刻</th></tr>
   <?php if (count($data)):
    foreach($data as $row): ?>
   <tr>
    <td><?php echo html_escape($row['name']); ?></td>
    <td><?php echo nl2br(html_escape($row['comment'])); ?></td>
    <td><?php echo $row['created']; ?></td>
   </tr>
  <?php endforeach;
   endif; ?>
  </table>
 <?php if(count($errs)) {
  foreach($errs as $err){
  echo '<p stype="color:red">'.$err.'</p>';
  }
 }?>

 <form action="" method="POST">
  <p>お名前*<input type="text" name="name">(50文字まで)</p>
  <p>ひとこと*<textarea name="comment" rows="4" cols="40"></textarea>(200文字まで)</p>
  <input type="submit" value="書き込む">
 </form>
 </body>
</html>

 $dataの要素が存在する場合はforeach()を使って出力する。include_once()ではboard.phpで種痘した$dataのデータは引き継がれている。フォームからの入力値は出力前に必ず無毒化する必要があるので、html_escape()を使用する。コメントには改行コードが含まれる可能性もあるので、さらにnl2br()も必要となる。エラーがあった場合はどこの入力欄にエラーがあったのかを赤文字で表示するようにする。

コードはこれで完成なので、index.phpにアクセスして動作をテストする。

f:id:R_de_aru:20180707204052p:plain

こんな感じで表示されるようになった。

今回はここまで。

PHP実習:関数の自作とローカル変数、グローバル変数

先ほどのファイル読み書きのところが少ないように感じたのと、今夜はモチベーションが高いのでもう少し続けます。

 

第11章-01

この章ではいよいよ関数を自作する。そもそも関数とは、コードの一連の処理をまとめて定義すること。必要な値を渡したり返したりする仕組みが存在していて、自作した関数を何度も使いまわすことができる。仕事でもお世話になってます。

PHPの関数について、基本的な構文は以下の通りとなる。

function 【関数名】(引数1,引数2,...){

 //一連の処理

 //必要なら値を返す(Return)

関数名はPHPマニュアルの「命名の手引き」に乗っ取って、単語の間にアンダースコアを使用する「スネークケース」という方法を用いることにする。大文字を使わず、単語と単語の間に_を使うので「some_code_name」という形になる。

例として、以前扱ったhtmlspecialchars()を関数に組み込んで使う。

<?php
 function html_escape($word){
  return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
 }

 $word='<h1>こんにちは</h1>';
 echo $word;
 echo html_escape($word);

f:id:R_de_aru:20180705212332p:plain

このように、関数を通すとhtmlspecialcharsの処理を行った文字列を返り値として表示することが分かる。

よく起こる関数の勘違いとして以下のコードが挙げられる。

<?php

$word='<h1>こんにちは</h1>';
echo html_escape($word); // 1.関数の宣言前に呼び出す
function html_escape($str){ // 2.$wordと別の名前の変数にする
 return htmlspecialchars($str,ENT_QUOTES,'UTF-8');
}

 これは正常に動作するコードである。

  1. PHPを実行する際、裏でコンパイルを行って人が理解できる言語から機会が理解できる言語へ変換を行っている。関数の存在はこの時確認するので、実行時にはコンピューター側がhtml_escape()を知っている状態であるためOK。
  2. 関数を呼び出すときは変数をそのまま渡すのではなく、中身のデータだけを渡して新たに$strという変数に詰めなおしている。変数名は揃えなくてもよい。

 

第11章-02

引数は複数設定可能、デフォルト値の設定も可能である。

<?php
function show_member($member1,$member2,$leader='田中'){
 echo '今回のメンバーは'.$member1.'さんと'.$member2.'さんです。<br>';
 echo $leader.'さんが現場を管理します。';
}
show_member('高橋','小林') 

 この関数には3つの引数が設定されているので本来ならばエラーになるが、第3引数にデフォルト値を設定している。そのため、第3引数に何も渡されていないときは「田中」が自動的に入ることになる。

戻り値に複数の値を返すことも可能である。配列を使う。

<?php
function get_sum_and_diff($inti,$int2){
 $sum=$int1+$int2;
 $difference=$int1-$int2;
 return array($sum,$difference);
}
$int1=8;
$int2=3;
list($sum,$difference)=get_sum_and_diff($int1,$int2);
echo $sum,'<br>';
echo $difference; 

 引き算の答えもreturnする。この場合、一度$sumとともに配列に格納する。関数使用時にそのまま配列として取得することも可能だが、今回はlist()を使う。これは添え字配列を一度に複数の変数に代入するコードであり、配列に格納した順番にlist()の方でも変数を指定すれば一度に複数の代入が行える。

 

第11章-03

変数には有効範囲があり、「スコープ」という。以下のコードのうち、いくつかはvar_dump()でundefined(プログラムがその存在を認識していない)というエラーが出る。

<?php
function hello_message($name){
 $now=date('H:i:s');
 echo 'はじめまして'.$name.'さん '.$now;

 var_dump($word); // A
 var_dump($now); // B
 var_dump($name); // C
}

$word='太郎';
hello_message($word);

var_dump($word); // D
var_dump($now); // E
var_dump($name); // F

 関数内で使われる変数をローカル変数といい、関数の中だけで認識される。$wordで作成された変数はグローバル変数といい、関数外で認識される。

Aは関数の中で$wordが宣言されておらず、E、Fについては関数の外で宣言されていないのでundefinedが発生する。

 

明日は関数専用のphpファイル作成と実習を行う。今夜はここまで。