2016年8月27日

再入荷お知らせメールの話2

再入荷お知らせメールの話の続き。

前回は、お客様が手軽にメール送信するだけの機能について、簡単に設置してみました。
メールを受けて→メモって→入荷メールを送信、と大部分を手動で運用する仕組みで、導入も簡単&知識もそれほど不要で、個人的にはおすすめです。

レンタルサーバーにデータベースを持つことで、手動で行っていた部分をPHPのプログラムに代行させることも可能です(前回と比べるとハードルが上がります)。
今回はそのあたりの説明と、コーディングについて書きます。

PHPとデータベース周りについては、この記事では解説していません。
ウェブアプリケーション構築においては、セキュリティ面も気を配る必要があります。
検索して調べるか書籍をあたって、慎重に検討してください。

動作テストは簡単にしか行っていません。
本番環境に実装する場合には、ご自身の責任でしっかりとテストを行ってください(ソフトウェアテストについて理解しておく必要があります)。


0. 仕様と留意点について


データベースにすでに登録されているという前提です(カラムは、id(連番)、pid(商品ID)、mail(お客様メール))。
登録フォームは、ご自分で用意ください。入力時の在庫ゼロチェック、登録のダブりチェック、登録メールアドレスのバリデーションなどをやっておく必要があります。


今回のコーディングは、カラーミーショップの更新をチェックし、データベース(MySQL)からデータを取得し、メール送信する部分です。

カラーミーショップAPIのみで「再入荷した商品」という判断をすることは、現状無理です。ドキュメントによると、都合よさそうなリクエストパラメータがありません。
仕方ないので、カラーミーショップAPIで、今日の更新データを取ってきて、在庫>0の商品IDを抽出しています。

この条件で「再入荷した商品」を抽出したというには、ちょっとまずいです。
たとえば、販売して在庫が5→4に減った場合、商品登録でコメントやその他項目などを更新した場合など、「再入荷したので在庫を増やした」以外の更新をたくさん含んでいます。
ただ、データベース登録時の在庫ゼロチェックがきちんされていれば、データベース検索時に「再入荷した商品」と無関係な商品は(たぶん)除外されます。


じゃぁそれでOKかというと、そうでもなくて。
サーバーに負荷をかけないためにも、明らかに不要な分は先に除外する方向で運用を決めたほうがよいと思います。
また、カラーミーショップAPIはリクエスト一回当たり最大50件なので、それより件数が多いと、複数回に分けてリクエストする必要があります(処理を追加する必要がありますし、リクエストも連投しすぎると制限がかかります)。
この点も考慮する必要が出てくるかもしれません。

お店の運用で再入荷商品であるとわかるような工夫して、条件に追加してやるのがベターだと思います(良い案もないので、ある程度の妥協は必要です)。
商品名につける、空き項目につける、再入荷カテゴリーに移動する、ここは手動にするなど。


今回のコーディングについての留意点。
「データベースに登録が0件だった場合は即終了」を追加すべきです(省略しています)。
エラー処理もろもろ、お客様にメール送信しましたよという報告メール、オプション在庫の対応、ヒットする件数が多い場合の追加処理、送信済みフラグを立てるのか、データベースから削除するタイミング、メール内容をきっちり作成、複数商品入荷した場合はメールをまとめる、メール送信規制の回避、などお店の事情で検討して、機能追加してください。


1. コーディング(人柱版)

<?php
//カラーミー
header("Content-Type:text/html; charset=UTF-8");//文字化け対策
$request_options = array(
    'http' => array(
        'method'  => 'GET',
        'header'=> "Authorization: Bearer xxxxxxxxxxxxxxx\r\n"
    )
);
$context = stream_context_create($request_options);
$url = 'https://api.shop-pro.jp/v1/products.json?limit=50&update_date_min=' . date('Y-m-d');
$response_body = file_get_contents($url, false, $context);
$response_json = json_decode($response_body, true);

//抽出、降順でソートしておくと処理が減らせる
for($i=0; $i<count($response_json['products']); $i++){
    if($response_json['products'][$i]['stocks'] > 0) $data[] = $response_json['products'][$i]['id'];
}

//データベース
$dsn = 'mysql:dbname=mydb;host=hostname';
$user = 'username';
$password = 'password';

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

    for($i=0; $i<count($data); $i++){
        $sql = 'SELECT DISTINCT mail FROM restock WHERE pid=?';
        $stmt = $pdo->prepare($sql);
        $stmt->execute(array($data[$i]));
        foreach ($stmt as $row) {
            //メール送信
            mb_language("Japanese");
            mb_internal_encoding("UTF-8");
            $to = $row['mail'];
            $subject = 'SUBJECT';
            $message = $data[$i];
            $headers = 'From: shop@example.co.jp' . "\r\n". 'Return-Path: shop@example.co.jp';
            $send_mail=mb_send_mail($to, $subject, $message, $headers);

            if ($send_mail) {
                $sql = 'DELETE FROM restock WHERE pid=? AND mail=?';
                $stmt = $pdo->prepare($sql);
                $stmt->execute(array($data[$i], $row['mail']));
            } else {
                //エラー処理
            }
        }
    }
} catch (PDOException $e){
    //print('Error:'.$e->getMessage());
    die();
}


2. おわりに 


このあたりで、作業の半分くらいだと思います。
私は試しに作っただけで、実装する予定はありません。 のこり半分を自分で仕上げられる人用です
バグ報告していただけると、私が喜びます。

ソフトウェアテストも、コーディングと同様に大事な技術になります。
十分なインプットが必要です。

cronで自動実行する予定です(レンタルサーバー次第)。

XAMPPを使ってメール送信のテストをする場合は、事前に設定が必要です。
検索して調べてください。

再入荷お知らせメールの話3」につづきます。

[追記]
PHPやPHPセキュリティの入門書によると、(フォーム入力時に当然バリデーションしてから登録するのですが)データベースの登録内容も無条件に信用せずに、データをあらためてバリデーションするくらいの丁寧な仕事が必要だそうだ(カラーミーショップAPIも含む)。
pid、mail、その他データベースからの項目もチェックする姿勢で臨むほうがよいと考えます。