質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.03%

  • PHP

    17714questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

PHPのセッションの使い方について

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 241

gsuisk

score 59

セッションが機能しなくて困っています。

PHPで商品のデータを保持するデータベースを作成しました。

id、goods、price、makerの4つのカラムを持つテーブルstockです。

fetch.phpというファイルで、このテーブルの全レコードを抽出して出力するというプログラムを作成しました。また、出力した商品名をURLとし、クリックすると詳細ページに飛ぶようにしました。

fetch.phpは以下です。

<?php
session_start();
header('Expires:-1');
header('Cache-Control:');
header('Pragma:');

$user = 'testuser';
$password = 'pw';
$dbName = 'shop';
$host = 'localhost';
$dsn = "mysql:host={$host};dbname={$dbName};charset=utf8";

?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>全商品一覧</title>
</head>
<body>

  <?php

  try {
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $sql = "SELECT * FROM stock";
    $stm = $pdo->prepare($sql);
    $stm->execute();

    $result = $stm->fetchAll(PDO::FETCH_ASSOC);
    if(count($result)>0){

      echo "<table>";

      echo "<tr>";
      echo "<th>", "ID", "</th>";
      echo "<th>", "商品名", "</th>";
      echo "<th>", "価格", "</th>";
      echo "<th>", "メーカー", "</th>";
      echo "</tr>";

      foreach ($result as $row){

        $_SESSION['id']=$row['id'];
        $_SESSION['goods']=$row['goods'];
        $_SESSION['price']=$row['price'];
        $_SESSION['maker']=$row['maker'];

        echo "<tr>";
        echo "<td>", $row['id'], "</td>";
        echo "<td>", '<a href="detail.php">', $row['goods'],"</a>", "</td>";
        echo "<td>" ,$row['price'], "</td>";
        echo "<td>", $row['maker'], "</td>";
        echo "</tr>";
      }
      echo "</table>";
    } else {
      echo "見つかりませんでした。";
    }
  } catch (Exception $e) {
    echo $e->getMessage();
  }

  ?>

</body>
</html>

商品名をクリックすると飛ぶ詳細ページはdetail.phpで以下になります。
詳細ページと言っても、その商品のid、商品名、価格、メーカーを出力するだけです。

<?php

session_start();

if(empty($_SESSION['id'])){
  $id="";
}else {
  $id=$_SESSION['id'];
}

if(empty($_SESSION['goods'])){
  $goods="";
}else {
  $goods=$_SESSION['goods'];
}

if(empty($_SESSION['price'])){
  $price="";
}else {
  $price=$_SESSION['price'];
}

if(empty($_SESSION['maker'])){
  $maker="";
}else {
  $maker=$_SESSION['maker'];
}

 ?>

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>商品詳細</title>
</head>
<body>

<p> <?php echo $id; ?> </p>
<p> <?php echo $goods; ?> </p>
<p> <?php echo $price; ?> </p>
<p> <?php echo $maker; ?> </p>


<br>

<form>
<input type="button" onClick='history.back()' value="戻る">
</form>

</body>
</html>

商品一覧(fetch.php)から詳細ページ(detail.php)にデータを送信するのにセッションを使ったのですが、機能しません。

上のコードだと、どの商品をクリックしても最後の商品の詳細が出力されてしまいます。

foreach内でセッションが設定されているので、最後のループでの値が設定されてしまっているのだと思いますが、どこでセッションを設定すれば全商品のデータをdetail.phpに送る方法が思いつきません。

このような場合はどのように実装すべきでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

foreach内でセッションが設定されているので、最後のループでの値が設定されてしまっているのだと思いますが、

この認識は正しいです。

どこでセッションを設定すれば全商品のデータをdetail.phpに送る方法が思いつきません。

どうも、
セッションがセットされるタイミング(PHPが動作するタイミング)に誤解があるように思います。
PHPは基本的にサーバにアクセスしたタイミングでだけ動作します。

今回の場合だと、fetch.phpにアクセスしたタイミングと、detail.phpにアクセスしたタイミングです。

PHPが「どの商品を選択したか」わかるのは、detail.phpにアクセスした後なので、
detail.phpにアクセスするタイミングで、何らかの方法で「どの商品を選択したか」をdetail.phpに伝える必要があります。

こういったケースだと、GETを利用してリンクに情報を埋め込むケースが多いです。
例えば、

//fetch.php
        echo "<td>", '<a href="detail.php?id=',$row['id'],'">', $row['goods'],"</a>", "</td>";


みたいな感じですね。

次に、GETで埋め込んだ情報を使って、detail.phpで、$_SESSIONから情報を取り出す必要があります。
これには先に、fetch.phpで$_SESSIONに情報を入れるタイミングで取り出しやすい形を意識して連想配列に情報を格納する必要があります。

例えば、こんな感じでIDをキーとした二次元配列に全データを格納しておくと、IDさえわかればdetail.php側でセッションから取得できます。

//fetch.php
$_SESSION[$row['id']] = $row;

という感じで、頑張ってみてください。
*もし、多次元配列についてよくわからないという事であれば、SESSIONを使わずに多次元配列の概念の理解と操作を先に学習されることをお勧めします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

+1

まず、商品一覧の連想配列の構造を考えてみるとわかりやすいかもしれません。
商品は下記のようなデーター構造だと思います。

$goods = [
    '商品ID' => ['id' => '商品ID', 'goods' => '商品名', 'price' => '値段', 'maker' => 'メーカー'],
    ...
];
//または
$goods = [
    ['id' => '商品ID', 'goods' => '商品名', 'price' => '値段', 'maker' => 'メーカー'],
    ...
];

したがって、商品一覧ページでセッションに商品情報を保存しておいて、商品詳細ページで個別の商品を取り出したい場合はfetch.phpforeach()部分を下記のようにします。また、リンクは、どの商品の詳細なのかを明確にするためにdetail.php?goods=商品IDとします。

どうしてもリンクにクエリストリング(?goods=商品ID)を書きたくない場合は、フォームでPOSTするかJavaScriptでPOSTするか、Cookieに保存します。

//fetch.php
foreach ($result as $row) {
    $_SESSION[$row['id']] = [
         'id' => $row['id'], 'goods' => $row['goods'], 'price' => $row['price'], 'maker' => $row['maker',
    ];
    //または
    //$_SESSION[] = [
    //     'id' => $row['id'], 'goods' => $row['goods'], 'price' => $row['price'], 'maker' => $row['maker',
    //];
    echo "<tr>";
    echo "<td>". $row['id'] ."</td>";
    echo "<td>". '<a href="detail.php?goods='. $row['id'] .'">'. $row['goods'] ."</a></td>";
    echo "<td>". $row['price'] ."</td>";
    echo "<td>". $row['maker'] ."</td>";
    echo "</tr>";
}

そして、detail.phpでは下記のようにします。

//detail.php
session_start();

//商品ID$_GET['goods']取得
$goodsId = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_NUMBER_INT);
$id = $goods = $price = $maker = '';

if (! empty($goodsId) && ! empty($_SESSION[$goodsId])) {
    if (! empty($_SESSION[$goodsId]['id'])) {
        $id = $_SESSION[$goodsId]['id'];
    }
    if (! empty($_SESSION[$goodsId]['goods'])) {
        $goods = $_SESSION[$goodsId]['goods'];
    }
    //ゼロ円考慮
    if (isset($_SESSION[$goodsId]['price']) && $_SESSION[$goodsId]['price'] !== '') {
        $price = $_SESSION[$goodsId]['price'];
    }
    if (! empty($_SESSION[$goodsId]['maker'])) {
        $maker = $_SESSION[$goodsId]['maker'];
    }
}

商品数が少ない場合は問題ありませんが、すべての商品情報をセッションに入れているため、商品が多くなるとセッションの連想配列が膨大になりパフォーマンスが悪くなります。

したがって、商品詳細ページでもDBアクセスして商品情報をDBから取得するのがよいです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/14 18:56

    ありがとうございます。

    詳細ページでもDBにアクセスするほうが良いのですね。

    やり方としてはalone.mk2さんが回答されているように、

    <a href="detail.php ?id = ' , $row[' id '] , ' ">

    として、

    詳細ページで再びPDOオブジェクトを生成して

    $id = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_NUMBER_INT);

    として取得するのが良いでしょうか?

    キャンセル

  • 2017/09/14 21:46

    はい、そうですね。
    セキュリティを考慮する場合は、DBのSQLクエリはプレースホルダを使ってバインドしたほうが良いです。下記の「:id」をプレースホルダといいます。

    プリペアドステートメント
    -------------------------
    $st = $conn->prepare('SELECT ... WHERE id = :id');
    $st->bindValue(':id', $id, PDO::PARAM_INT);
    $st->execute();
    $result = $st->fetch(); //1レコードのみなら
    //DB結果ダンプ
    var_dump($result);

    キャンセル

  • 2017/09/14 22:05

    ありがとうございます。

    $id = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_NUMBER_INT); は

    $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); の間違いでしょうか?

    fetch.phpで ?id=',$row['id'],' としているので送信したているのはidですよね?

    キャンセル

  • 2017/09/14 22:08

    また、fetch()としてしまうと

    Array
    (
    [id] => 2
    [0] => 2
    [goods] => バッグ
    [1] => バッグ
    [price] => 3000
    [2] => 3000
    [maker] => AS
    [3] => AS
    )

    $resultにこのような配列が入っていしまいます。

    fetchAll(PDO::FETCH_ASSOC)の方が良いですよね?

    キャンセル

  • 2017/09/14 22:13

    $pdo = new PDO($dsn, $user, $password);

    $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
    $sql = "SELECT * FROM stock WHERE id = :id";
    $stm = $pdo->prepare($sql);
    $stm->bindValue(':id', $id, PDO::PARAM_INT);
    $stm->execute();
    $result = $stm->fetchAll(PDO::FETCH_ASSOC);

    $stm = null;
    $dbName = null;

    このあと$resultをforeachで回せばうまくできました。

    これでよいのですかね..?

    キャンセル

  • 2017/09/14 22:26 編集

    >$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); の間違いでしょうか?
    >fetch.phpで ?id=',$row['id'],' としているので送信したているのはidですよね?

    そうですね。私の回答のコードでは「'...?goods='.$row['id']」としていたので見落としてしまいました。

    >$resultにこのような配列が入っていしまいます。
    >fetchAll(PDO::FETCH_ASSOC)の方が良いですよね?

    fetch()では結果配列をどのように受け取るか、引数を渡して設定することができます。どのように結果を受け取りたいか、好みで決めると良いと思います。

    PDO::FETCH_ASSOC
    ----
    カラム名をキーとした連想配列で結果を受け取ることができます。

    PDO::FETCH_BOTH(初期値)
    ----
    カラム名をキーとした連想配列と、0,1,2... の数字をキーとした配列を受け取ることができます。
    PDO::FETCH_ASSOCと、PDO::FETCH_NUM両方ということです。

    キャンセル

  • 2017/09/14 22:38

    わかりました!丁寧にありがとうございます。

    ちなみになのですが、idではなく

    ?goods=' ,$row['goods'], ' のように

    商品名を送信してGETリクエストすることも可能でしょうか?

    キャンセル

  • 2017/09/14 22:40

    ?goods=',rawurlencode($row['goods']),'

    goodsは日本語なのでエンコードが必要だと思って上のように書いたのですが機能しませんでした(汗)

    キャンセル

  • 2017/09/15 01:41

    商品名を渡した場合は、$_GET['goods']を受け取ったらURLデコードしなければいけません。
    ただし、商品名で検索するより通常プラマリーキーであるレコードID(商品ID)でSELECTしたほうがパフォーマンスが良いです。

    何かしら、DBからSELECTする前や、DBとは関係ない部分で「商品名」を使いたい場合は下記のようにします。
    --
    //AリンクのURL(商品ID、商品名)
    echo '<a href="detail.php?id='. $row['id'] .'&goods='. urlencode($row['goods']) .'">...</a>'
    --
    //商品ID$_GET['id']
    $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);

    //商品名$_GET['goods']
    $goods = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_URL);
    $goods = urldecode($goods);

    キャンセル

  • 2017/09/15 03:11

    idのみでGETリクエストするとURLの末尾の「id=数字」の数字を変えるだけで他のページに移動できるのでよくないのかなと思っていました。

    Tomakさんに上に書いていただいたのはidとgoodsの両方をセットでGETリクエストで送るということですね。

    detail.phpで以下のように書いたのですが、レコードが見つからなかったです...

    $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
    $goods = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_URL);
    $goods = urldecode($goods);
    $sql = "SELECT * FROM stock WHERE id = :id AND goods = :goods";
    $stm = $pdo->prepare($sql);
    $stm->bindValue(':id', $id, PDO::PARAM_INT);
    $stm->bindValue(':goods', $goods);
    $stm->execute();
    $records = $stm->fetchAll(PDO::FETCH_ASSOC);
    $stm = null;
    $dbName = null;

    キャンセル

  • 2017/09/15 03:15

    何度もすみません...

    あと、例えば「idが2でgoodsがバッグ」をクリックするとURLが

    detail.php?id=2&goods=バッグ

    と日本語がエンコードされていませんでした。ページのソースではエンコードされているのですが、、、

    キャンセル

  • 2017/09/15 05:45

    > idのみでGETリクエストするとURLの末尾の「id=数字」の数字を変えるだけで他のページに移動できるのでよくないのかなと思っていました。

    その仕様で特に問題ないと思います。。。
    例えば、本件の質問ページは「https://teratail.com/questions/92366」で、今表示しているページは「質問詳細ページ」ですよね? 質問IDは「92366」です。 POSTしなくても、このURLに移動するときちんとこのページが表示されます。


    >detail.phpで以下のように書いたのですが、レコードが見つからなかったです

    すみません、よくよく考えたら$_GETは urldecode() 済みなので、普通に文字列のサニタイズすればいいだけでした。多分下記なら大丈夫なハズですが、このSQL文の「AND goods = xxx」必要ないです。
    --
    $goods = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $sql = "SELECT * FROM stock WHERE id = :id AND goods = :goods";


    >detail.php?id=2&goods=バッグ
    >と日本語がエンコードされていませんでした。ページのソースではエンコードされているのですが、、、

    最近のブラウザは、自動でURLデコードして表示してくれます。ページのソースではデコードすると実際のコードとは違うことになるので、生のHTMLテキストが表示されます。
    ためしに、ブラウザのURLロケーションバーに下記を入力してエンターしてみてください。「?id=2&goods=バッグ」と表示されるはずです。
    ----
    http://ドメイン/detail.php?id=2&goods=%E3%83%90%E3%83%83%E3%82%B0

    キャンセル

  • 2017/09/15 13:19

    ありがとうございます。できました!

    idだけにしても問題ないのですね。ブラウザのURLロケーションバーに日本語が表示されるのも何か変なのでidだけにしようと思います。

    親切にありがとうございました。

    キャンセル

+1

session_start()を使ってデータを送るよりも、商品詳細ページにおいてもDBからデータを取得するようにした方がやりやすいように感じます。

//fetch.php
echo "<td>", '<a href="detail.php?goods=',$row['id'],'">', $row['goods'],"</a>", "</td>";


このようにして、商品IDを'id'というキーに入れて商品詳細ページに送ります。

//datail.php
$pdo = new PDO($dsn, $user, $password);
//送った'id'を取得する
$id = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_NUMBER_INT);
//WHERE句で詳細を閲覧する商品のデータを取得
$sql = "SELECT * FROM stock WHERE id = $id";
$statement = $pdo->query($sql);
$records = $statement->fetchAll();
$statement = null;
$pdo = null;


商品の一覧ページから取得した'id'から詳細を閲覧する商品のデータを取得しています。
あとは$recordsの内容を表示すれば表示すればオッケーだと思います。

参考になればいいなと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/14 18:51

    ありがとうございます。

    fetch.phpにある$databaseとは$dbNameのことでしょうか?

    またfetch.phpでPDOオブジェクトを作るときにtry{}catch{}は必要ないのでしょうか?

    キャンセル

  • 2017/09/14 18:52

    ちなみに$databaseを$dbNameにしてじっこうしたのですが、

    $statement = $database->query($sql);

    ここの部分でエラーが出てしまいます...

    キャンセル

  • 2017/09/14 19:53

    $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);

    このようにしたらエラーは消えたのですが、$recordsが以下のようになっていました。

    Array
    (
    [0] => Array
    (
    [id] => 2
    [0] => 2
    [goods] => バッグ
    [1] => バッグ
    [price] => 3000
    [2] => 3000
    [maker] => AS
    [3] => AS
    )

    )

    キャンセル

  • 2017/09/14 19:58

    fetchAll(PDO::FETCH_ASSOC)にしたら解決しました!

    キャンセル

  • 2017/09/14 22:11

    コメントが遅れてしまいすみません。
    解決できて、何よりです。


    キャンセル

  • 2017/09/14 22:56

    ちなみにですけど、try{}catch{}は使わなくても大丈夫ですよ

    キャンセル

  • 2017/09/14 23:53

    ありがとうございます。

    ちなみに ?goods=',$row['goods'],' としてGETリクエストすることも可能でしょうか?

    idだとURLの末尾の数字を変えただけで他のページに飛んでしまうので、、

    キャンセル

  • 2017/09/15 00:07

    WHERE句でsqlを取得する際に、goodsに変更さえすれば可能だと思います。
    一度、試してみるといいかもしれませんね!

    キャンセル

  • 2017/09/15 00:38

    ありがとうございます!

    試してみたのですが、goodsは日本語なのでエンコードが必要だと思うので、

    ?goods=',rawurlencode($row['goods']),'

    このようにして、

    detail.phpで

    $goods = filter_input(INPUT_GET, 'goods', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $goods=rawurldecode($goods);
    $sql = "SELECT * FROM stock WHERE goods = $goods";

    このようにしたのですが、うまく動作しません・・・

    どこが原因かわかりますか?

    キャンセル

  • 2017/09/15 01:27

    私もやってみましたが、できませんでした。
    お力になれず、申し訳ないです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 91.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • PHP

    17714questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。