【PHPでECサイト③】カート機能

## はじめに
今回は、カート機能を実装していきます。
**※当ページは、[【PHPでECサイト】](https://qiita.com/taqmi/items/877292fc8c902f86e20e)で作られたものを前提にしています。**

### バージョン
PHP：7.4.5
phpMyAdmin：5.0.2
MySQL：5.7.30

### 今回作成するファイル
**html**
- index.php
- index_add_cart.php
- cart.php
- cart_delete.php
- finish.php

**model**
- carts.php

**view**
- index_view.php
- cart_view.php
- finish_view.php

## テーブルの作成

```sql:sample_carts
CREATE TABLE `sample_carts` (
  `cart_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  `amount` int(11) NOT NULL,
  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `sample_carts`
  MODIFY `cart_id` int(11) NOT NULL AUTO_INCREMENT,
  ADD PRIMARY KEY (`cart_id`),
  ADD KEY `item_id` (`item_id`),
  ADD KEY `user_id` (`user_id`);
```


## 定義

```php:const.php
define('INDEX_URL', '/index.php');
define('CART_URL', '/cart.php');
define('FINISH_URL', '/finish.php');
```

## Viewの作成

```php:index_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta chartset="UTF-8">
    <title>商品一覧</title>
  </head>

  <body>
  <h1>商品一覧</h1>

  <p>ようこそ、<?php print($user['user_name']); ?>さん。</p>
  <a href="<?php print(ADMIN_URL);?>">商品管理</a>
  <a href="<?php print(CART_URL);?>">カート</a>
  <a href="<?php print(LOGOUT_URL);?>">ログアウト</a>

  <!-- メッセージ・エラーメッセージ -->
  <?php include VIEW_PATH . 'templates/messages.php'; ?>

  <!-- 商品一覧 -->
  <?php foreach($items as $item){ ?>
  <div>
    <?php print($item['name']); ?>
    <?php print($item['price']); ?>
    <!-- 売り切れの場合は、formを置換 -->
    <?php if($item['stock'] > 0){ ?>
      <form method="post" action="index_add_cart.php">
        <input type="submit" value="カートに追加">
        <input type="hidden" name="item_id" value="<?php print($item['item_id']) ?>">
      </form>
    <?php }else{ ?>
     <p>現在、売り切れです。</p>
    <?php } ?>
  </div>
  <?php } ?>

  </body>
</html>
```

```php:cart_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>カート</title>
  </head>

  <body>
  <h1>商品一覧</h1>

  <p>ようこそ、<?php print($user['user_name']); ?>さん。</p>
  <a href="<?php print(ADMIN_URL);?>">商品管理</a>
  <a href="<?php print(INDEX_URL);?>">商品一覧</a>
  <a href="<?php print(LOGOUT_URL);?>">ログアウト</a>

  <!-- メッセージ・エラーメッセージ -->
  <?php include VIEW_PATH . 'templates/messages.php'; ?>

  <!-- カート一覧 -->
  <?php if(count($carts) > 0){ ?>
    <table>
      <thead>
        <tr>
          <th>商品名</th>
          <th>価格</th>
          <th>数量</th>
          <th>小計</th>
          <th>操作</th>
        </tr>
      </thead>  
      <tbody>
        <?php foreach($carts as $cart){ ?>
        <tr>
          <td><?php print($cart['name']); ?></td>
          <td><?php print($cart['price']); ?></td>
          <td><?php print($cart['amount']); ?></td>
          <td><?php print($cart['price'] * $cart['amount']); ?></td>
          <td>
            <form method="post" action="cart_delete.php">
              <input type="submit" value="削除">
              <input type="hidden" name="cart_id" value="<?php print($cart['cart_id']); ?>">
            </form>
          </td>
        </tr>
        <?php } ?>
      </tbody>
    </table>

    <p>合計金額：<?php print($total_price); ?></p>
    <form method="post" action="finish.php">
      <input type="submit" value="購入する">
    </form>
  <?php }else{ ?>
    <p>カートに商品はありません。</p>
  <?php } ?>
  </body>

</html>
```

```php:finish_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>ご購入ありがとうございました！</title>
  </head>
  
  <body>
  <h1>ご購入ありがとうございました！</h1>

  <!-- メッセージ・エラーメッセージ -->
  <?php include VIEW_PATH . 'templates/messages.php'; ?>

  <!-- 購入した商品 -->
  <?php if(count($carts) > 0){ ?>
    <table>
      <thead>
        <tr>
          <th>商品名</th>
          <th>価格</th>
          <th>購入数</th>
          <th>小計</th>
        </tr>
      </thead>  
      <tbody>
        <?php foreach($carts as $cart){ ?>
        <tr>
          <td><?php print($cart['name']); ?></td>
          <td><?php print($cart['price']); ?></td>
          <td><?php print($cart['amount']); ?></td>
          <td><?php print($cart['price'] * $cart['amount']); ?></td>
        </tr>
        <?php } ?>
      </tbody>
    </table>

    <p>合計金額：<?php print($total_price); ?></p>
  <?php }else{ ?>
    <p>カートに商品はありません。</p>
  <?php } ?>

  </body>

</html>
```

## カートに追加
### Model

```php:carts.php
<?php 
require_once MODEL_PATH . 'functions.php';
require_once MODEL_PATH . 'db.php';

// カートに追加するために必要なデータ
function get_user_cart($db, $user_id, $item_id){
  $sql = "
    SELECT
      sample_items.item_id,
      sample_items.name,
      sample_items.price,
      sample_items.stock,
      sample_carts.cart_id,
      sample_carts.user_id,
      sample_carts.amount
    FROM
      sample_carts
    JOIN
      sample_items
    ON
      sample_carts.item_id = sample_items.item_id
    WHERE
      sample_carts.user_id = ?
    AND
      sample_items.item_id = ?
  ";
  return fetch_query($db, $sql, array($user_id, $item_id));
}

// カートに追加(既に同じ商品があれば、個数のみUpdate)
function add_cart($db, $user_id, $item_id ) {
  $cart = get_user_cart($db, $user_id, $item_id);
  if($cart === false){
    return insert_cart($db, $user_id, $item_id);
  }
  return update_cart_amount($db, $cart['cart_id'], $cart['amount'] + 1);
}

function insert_cart($db, $user_id, $item_id, $amount = 1){
  $sql = "
    INSERT INTO
      sample_carts(
        item_id,
        user_id,
        amount
      )
    VALUES(?,?,?)
  ";
  return execute_query($db, $sql, array($item_id, $user_id, $amount));
}

function update_cart_amount($db, $cart_id, $amount){
  $sql = "
    UPDATE
      sample_carts
    SET
      amount = ?
    WHERE
      cart_id = ?
    LIMIT 1
  ";
  return execute_query($db, $sql, array($amount, $cart_id));
}
```

### Controller

```php:index.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';
require_once MODEL_PATH. 'items.php';

session_start();

if(is_logined() === false){
    redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);
$items = get_items($db);

include_once VIEW_PATH. 'index_view.php';
```

```php:index_add_cart.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';
require_once MODEL_PATH. 'items.php';
require_once MODEL_PATH. 'cart.php';

session_start();

if(is_logined() === false){
    redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);

$item_id = get_post('item_id');

// カートに追加
if(add_cart($db, $user['user_id'], $item_id)){
    set_message('カートに商品を追加しました。');
} else{
    set_error('カートの更新に失敗しました。');
}

redirect_to(INDEX_URL);
```

## カートの商品を購入

### Model

```php:functions.php
function has_error(){
  return isset($_SESSION['__errors']) && count($_SESSION['__errors']) !== 0;
}
```

```php:carts.php
// カートの商品データ
function get_user_carts($db, $user_id){
  $sql = "
    SELECT
      sample_items.item_id,
      sample_items.name,
      sample_items.price,
      sample_items.stock,
      sample_carts.cart_id,
      sample_carts.user_id,
      sample_carts.amount
    FROM
      sample_carts
    JOIN
      sample_items
    ON
      sample_carts.item_id = sample_items.item_id
    WHERE
      sample_carts.user_id = ?
  ";
  return fetch_all_query($db, $sql, array($user_id));
}

// カートの商品の合計額
function sum_carts($carts){
    $total_price = 0;
    foreach($carts as $cart){
        $total_price += $cart['price'] * $cart['amount'];
    }
    return $total_price;
}

// 購入処理
function purchase_carts($db, $carts){
  if(validate_cart_purchase($carts) === false){
    return false;
  }
  // 購入後、カートの中身削除&在庫変動
  $db->beginTransaction();
  try {
    foreach($carts as $cart){    
      if(update_stock($db, $cart['item_id'], $cart['stock'] - $cart['amount']) === false){
          set_error($cart['name'] . 'の購入に失敗しました。');
        }
      }
      delete_user_carts($db, $carts[0]['user_id']);
      $db->commit();
  }catch(PDOException $e){
    $db->rollback();
    throw $e;
  }
}

function delete_user_carts($db, $user_id){
  $sql = "
    DELETE FROM
      sample_carts
    WHERE
      user_id = ?
  ";
  execute_query($db, $sql, array($user_id));
}

// バリデーション
function validate_cart_purchase($carts){
  if(count($carts) === 0){
    set_error('カートに商品が入っていません。');
    return false;
  }
  foreach($carts as $cart){
    if($cart['stock'] - $cart['amount'] < 0){
      set_error($cart['name'] . 'は在庫が足りません。購入可能数:' . $cart['stock']);
    }
  }
  if(has_error() === true){
    return false;
  }
  return true;
}
```

### Controller

```php:cart.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH . 'functions.php';
require_once MODEL_PATH . 'users.php';
require_once MODEL_PATH . 'items.php';
require_once MODEL_PATH . 'carts.php';

session_start();

if(is_logined() === false){
    redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);
$carts = get_user_carts($db, $user['user_id']);

$total_price = sum_carts($carts);

require_once VIEW_PATH. 'cart_view.php';
```

```php:finish.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH . 'functions.php';
require_once MODEL_PATH . 'users.php';
require_once MODEL_PATH . 'items.php';
require_once MODEL_PATH . 'carts.php';

session_start();

if(is_logined() === false){
  redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);
$carts = get_user_carts($db, $user['user_id']);

$total_price = sum_carts($carts);

// 購入処理
if(purchase_carts($db, $carts) === false){
    set_error('商品が購入できませんでした。');
    redirect_to(CART_URL);
} 

include_once VIEW_PATH. 'finish_view.php';
```

## カートの商品を削除

### Model

```php:carts.php
// カートの商品の削除
function delete_cart($db, $cart_id){
  $sql = "
    DELETE FROM
      sample_carts
    WHERE
      cart_id = ?
    LIMIT 1
  ";
  return execute_query($db, $sql, array($cart_id));
}
```

### Controller

```php:cart_delete.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH . 'functions.php';
require_once MODEL_PATH . 'users.php';
require_once MODEL_PATH . 'items.php';
require_once MODEL_PATH . 'carts.php';

session_start();

if(is_logined() === false){
  redirect_to(LOGIN_URL);
}

$db = get_db_connect();
$user = get_login_user($db);

$cart_id = get_post('cart_id');

if(delete_cart($db, $cart_id)){
    set_message('カートの商品を削除しました。');
  } else {
    set_error('カートの商品の削除に失敗しました。');
  }
  
  redirect_to(CART_URL);
```