[PHP7.4.11]セッション管理によるショッピングカート作成

ベタのphpでショッピングカートを作成して、仕組みを理解する事が目的

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

### 機能要件
– カートに追加した商品は「追加ボタン」の代わりに「追加済み」とする
– カートには「変更」と「削除」の2種類のボタンを用意
– カートで表示する商品はIDではなく、商品名

### ページ一覧
– 商品一覧(list.php)
– 商品追加(create.php)
– カート(cart.php) ※カートの中身一覧を表示
– カートの中身全削除(delete.php)
※商品画像は img/*.png

list.php (商品一覧)

<?php 
	session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<h1>商品一覧</h1>
	<a href="cart.php">カートを見る</a>
	<table style="text-align=center">
		<tr>
			<th>画像</th><th>商品</th><th>数量</th><th>ボタン</th>
		</tr>
		<form action="create.php" method="POST">
		<tr>
			<td><img src="img/rolex.png" width="50px" height="50px"></td>
			<td>ロレックス ROLEX 腕時計 サブマリーナデイト 16610T </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="rolex_16610T">
				<?php if(isset($cart&#91;'rolex_16610T'&#93;)===true):?>
					<p>追加済み</p>
				<?php else: ?>
					<input type="submit" value="カートに入れる">
				<?php endif; ?>
			</td>
		</tr>
		</form>
		<form action="create.php" method="POST">
		<tr>
			<td><img src="img/omega.png" width="50px" height="50px"></td>
			<td>OMEGA SPEEDMASTER 腕時計 アナログ </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="OMEGA_323-21-40-44-01-001">
				<?php if(isset($cart&#91;'OMEGA_323-21-40-44-01-001'&#93;)===true):?>
					<p>追加済み</p>
				<?php else: ?>
					<input type="submit" value="カートに入れる">
				<?php endif; ?>
			</td>
		</tr>
		</form>
	</table>
</body>
</html>


うん、何となくイメージ通り

create.php(商品追加)

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


OK ちゃんと渡されています。
※amazonだと、カートに入れると、「追加されました」ってページに遷移
※rakutenの場合はポップアップで「商品かごに追加しました」と表示して商品ページに戻る
※yahoo shoppingの場合は、「ショッピングカート一覧」に遷移

cart.php(カートの中身一覧)

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

	if($_SERVER&#91;'REQUEST_METHOD'&#93;==='POST'){
		$product=$_POST&#91;'product'&#93;;
		$kind=$_POST&#91;'kind'&#93;;
		if($kind==='change'){
			$num=$_POST&#91;'num'&#93;;
			$_SESSION&#91;'cart'&#93;&#91;$product&#93;=$num;
		} elseif($kind==='delete'){
			unset($_SESSION&#91;'cart'&#93;&#91;$product&#93;);
		}
	}
	if(isset($_SESSION&#91;'cart'&#93;)){
		$cart=$_SESSION&#91;'cart'&#93;;
	}
	var_dump($_SESSION);
?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Cart</title>
</head>
<body>
	<h1>ショッピングカート</h1>
	<p><a href="list.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>
			<th>削除ボタン</th>
		</tr>
		<?php foreach($cart as $key=>$var):?>
		<tr>
			<td>
				<?php
					switch($key){
						case 'rolex_16610T':
							echo '<img src="img/rolex.png" width="50px" height="50px">';
							break;
						case 'OMEGA_323-21-40-44-01-001';
							echo '<img src="img/omega.png" width="50px" height="50px">'
							break;
					}
					?>
			</td>
			<td>
				<?php
					switch($key){
						case 'rolex_16610T':
							echo 'ロレックス ROLEX 腕時計 サブマリーナデイト 16610T ';
							break;
						case 'OMEGA_323-21-40-44-01-001';
							echo 'OMEGA SPEEDMASTER 腕時計 アナログ '
							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で行う

delete.php(中身全部削除)

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

OK, sessionで商品IDと商品数を管理していることを理解した。つまり、DB側で商品名、商品画像、料金などを持っておけば良さそう。
一応、amazon, rakuten, yahooがセッションをどう管理しているか見ておこうと思う。

[amazon linux2] PHP7.4でQRコードを生成する

$ require endroid/qr-code
Problem 1
– endroid/qr-code[3.9.0, …, 3.9.3] require ext-gd * -> it is missing from your system. Install or enable PHP’s gd extension.

$ yum list | grep gd
php-gd.x86_64 7.4.11-1.amzn2 amzn2extra-php7.4

$ sudo yum install php-gd.x86_64
$ composer require endroid/qr-code

app.php

<?php
require_once(__DIR__.'/vendor/autoload.php');

use Endroid\QrCode\QrCode;

$qrCode = new QrCode('hello qr code');

// header('Content-Type:'.$qrCode->getContentType());
// echo $qrCode->writeString();
$qrCode->writeFile(__DIR__.'/qrcode.png');

echoでブラウザで表示し、writeFileで画像を保存

OK, Let’s Go

DBに現在時刻を5分区切りの単位で入れたい時

### フォーム入力
1. フォームから時間を入力する場合はselect文で5分単位で選べるように、5分単位のテーブルを作る
insert into minutes (name) values (’00’);
insert into minutes (name) values (’05’);
insert into minutes (name) values (’10’);
insert into minutes (name) values (’15’);
insert into minutes (name) values (’20’);
insert into minutes (name) values (’25’);
insert into minutes (name) values (’30’);
insert into minutes (name) values (’35’);
insert into minutes (name) values (’40’);
insert into minutes (name) values (’45’);
insert into minutes (name) values (’50’);
insert into minutes (name) values (’55’);

2. select文では、minutesテーブルから値を呼び込む

{!! Form::select('start', $minutes, 1, ['class' => 'form-control']) !!}

### 現在時刻を5分単位でDBに入力

$minute = date('i');

        $minute_margin = 5;
        if($minute % $minute_margin){
            $minute += $minute_margin - ($minute % $minute_margin);
        }

例: 43分の時:43 + (5-3) = 45

上記は端数があった場合繰り上がりですが、$minute -= $minute % $minute_margin とすれば、繰り下がりになる筈です。

あ。。。55〜00分の処理忘れてた。。
### 追加

$hour = date('H');
        $minute = date('i');


        $minute_margin = 5;
        if($minute % $minute_margin){
            if($minute > 55){
                $hour = date('H',strtotime("+1 hour"));
                $minute = 00;
            } else {
                $minute += $minute_margin - ($minute % $minute_margin);
            }          

        }

mysqlからカラム名も取得してfputcsvによる帳票出力

### データ作成
create database universe;
use universe;
create table planets(
id int primary key auto_increment,
name varchar(255),
diameter float,
mass float
);
describe planets;
insert into planets(name, diameter, mass) values (‘Sun’,’110′,’330000′);
insert into planets(name, diameter, mass) values (‘Mercury’,’0.38′,’0.06′);
insert into planets(name, diameter, mass) values (‘Venus’,’0.95′,’0.82′);
insert into planets(name, diameter, mass) values (‘Earth’,’1′,’1′);
insert into planets(name, diameter, mass) values (‘Mars’,’0.53′,’0.11′);
insert into planets(name, diameter, mass) values (‘Jupiter’,’11’,’320′);
insert into planets(name, diameter, mass) values (‘Saturn’,’9.5′,’95’);
insert into planets(name, diameter, mass) values (‘Uranus’,’4′,’15’);
insert into planets(name, diameter, mass) values (‘Neptune’,’3.9′,’17’);
select * from planets;

### データ出力
var_dumpでテストしてから、出力します。

try {
$dbh = new PDO('mysql:host=localhost;dbname=universe;charset=utf8','root','hogehoge', array(PDO::ATTR_EMULATE_PREPARES => false));
} catch(PDOException $e){
    exit('データベース接続失敗。'.$e->getMessage());
}

$export_sql = "select * from planets";

// $stmt = $dbh->query($export_sql);
// $row = $stmt->fetchAll(PDO::FETCH_ASSOC);
// var_dump($row);

$file_path = "csv/planets.csv";
$export_csv_title = ["id","name","diameter","mass"];

foreach($export_csv_title as $key => $val){
	$export_header[] = mb_convert_encoding($val, 'SJIS-win', 'UTF-8');
	}

	if(touch($file_path)){
		$file = new SplFileObject($file_path, "w");

		$file->fputcsv($export_header);
		$stmt = $dbh->query($export_sql);

		while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
			$file->fputcsv($row);
		}
		$dbh = null;

	}

echo "done";

### カラム名も取得したい場合
show columnsとして配列を取得して、その中からFieldを取り出す

$export_column = "show columns from planets";
$stmt = $dbh->query($export_column);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
	$field[] = $row["Field"];
}

つなげると

try {
$dbh = new PDO('mysql:host=localhost;dbname=universe;charset=utf8','root','hogehoge', array(PDO::ATTR_EMULATE_PREPARES => false));
} catch(PDOException $e){
    exit('データベース接続失敗。'.$e->getMessage());
}

$export_column = "show columns from planets";
$stmt = $dbh->query($export_column);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
	$field[] = $row["Field"];
}

$export_sql = "select * from planets";
$file_path = "csv/planets.csv";
$export_csv_title = $field;

foreach($export_csv_title as $key => $val){
	$export_header[] = mb_convert_encoding($val, 'SJIS-win', 'UTF-8');
	}

	if(touch($file_path)){
		$file = new SplFileObject($file_path, "w");

		$file->fputcsv($export_header);
		$stmt = $dbh->query($export_sql);

		while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
			$file->fputcsv($row);
		}
		$dbh = null;

	}

echo "done";

カラム名だけ取得のニーズはあるので、その命令文を作っても良いように思ったが、、、
MySQL8.0のGithubレポを見ると軽々しく言えんな。。
https://github.com/mysql/mysql-server/blob/8.0/sql/sql_select.cc

slackに脆弱性情報をpost

function send_to_slack($message){
	$webhook_url = 'https://hooks.slack.com/services/hogehoge';
	$options = array(
		'http' => array(
		'method' => 'POST',
		'header' => 'Content-Type: application/json',
		'content' => json_encode($message),
	  )
	);
	$response = file_get_contents($webhook_url, false, stream_context_create($options));
	return $response === 'ok';
}

$message = array(
	'channel' => '#general',
	'username' => '脆弱性情報',
	'icon_emoji' => ':warning:',
	'text' => $string,
);

send_to_slack($message);

おおおおおおお、できたー

とりあえず、リファクタリングしよう

ipa RSSを取得

<?php 
$target_day = date('Y/m/d', strtotime('-1 day'));
$xml = "https://jvndb.jvn.jp/ja/rss/jvndb_new.rdf";

$xmlData = simplexml_load_file($xml);
foreach ($xmlData->item as $entry){
  $dc = $entry->children('http://purl.org/dc/elements/1.1/');
  $day = date('Y/m/d', strtotime($dc->date));
  if($day == $target_day){
      $string.= date('Y/m/d h:i', strtotime($dc->date))."<br>";
      $string.= $entry->title."<br>";
      $string.= $entry->link."<br>";
  }
}
?>
<html>
<body>
	<?php echo $string; ?>
</body>
</html>

OK、次はslack webhook

php.ini session.save_handler

/etc/php.ini

[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
session.save_handler = files

session.save_hanlder defines the hanlder to use when saving and retrieving data related to the session. The default is files. Npte that each extension can use its own save_handler.

先ほどElastiCacheで作成したmemcacheのエンドポイントをphp.iniに設定する

;session.save_handler = files
;session.save_path = "/tmp"
session.save_handler = "memcached"
session.save_path = "php-session.xxxxx.cfg.apne1.cache.amazonaws.com:11211""

続いて動作確認

session_start();
echo "This is Web Server 1<br>";

if (isset($_SESSION["username"])){
	echo $_SESSION["username"];
} else {
	$_SESSION["username"] = "hoge";
}

なるほど、sessionを共有する両方のphp.iniファイルで、エンドポイントを指定して共有しているのね。

$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set('foo', 'var',60);
var_dump($m->get('foo'));
$m->add('hoge', 'fuga', 60);
$m->add('hoge', 'piyo', 60);
var_dump($m->get('hoge'));
$m->flush();
var_dump($m->get('foo'));

Mcrypt

This function is an extensive block algorithm such as CBC, OFB, CFB, ECB cipher modes DES, TripleDES, Blowfish (default), 3-WAY, SAFER-SK64, SAFER-SK128, TWOFISH, TEA, RC2 and GOST. Interface to the mcrypt library to support. In addition, it supports RC6 and IDEA, which are described as “not free”.

php zip

インストール済みを確認します。
[vagrant@localhost ~]$ yum list installed | grep zip
bzip2.x86_64 1.0.5-7.el6_0 @anaconda-CentOS-201605220104.x86_64/6.8
bzip2-devel.x86_64 1.0.5-7.el6_0 @base
bzip2-libs.x86_64 1.0.5-7.el6_0 @anaconda-CentOS-201605220104.x86_64/6.8
gzip.x86_64 1.3.12-24.el6 @base
libzip5.x86_64 1.5.2-1.el6.remi @remi-safe
php-pecl-zip.x86_64 1.15.3-1.el6.remi.7.1 @remi-php71
unzip.x86_64 6.0-5.el6 @base

ん?
php-pecl-zipか??

[vagrant@localhost ~]$ php –ri zip

zip

Zip => enabled
Zip version => 1.15.3
Libzip headers version => 1.5.1
Libzip library version => 1.5.2

はいってるっぽいですね。
これのこと?

php70-zipとphp-pecl-zipの違いが分からんぞ。