[PHP]指定した日時まであと何日(何時間)あるかを調べてカウントダウンする

タイムセールやイベント告知などで指定した日や時刻まで後どのくらい期間があるかを計算したい場合、DateTime クラスの diff() 関数を使うと簡単に得ることができます。
DateTime クラスは日時計算や整形出力に関するクラスです。下記サンプルの実行には PHP 5.3 以上が必要です。
2038年問題にも対応する関数なので今後必須になってくると思います。

<?php
date_default_timezone_set("Asia/Tokyo");

$datetime = new DateTime('2030/01/01 00:00:00');
$current  = new DateTime('now');
$diff     = $current->diff($datetime);

printf('残り %d年%dヶ月%d日 %d時間%d分%d秒(%d日)',
	$diff->y, $diff->m, $diff->d, $diff->h,$diff->i,$diff->s, $diff->days);
実行結果: 残り 15年8ヶ月13日 9時間49分8秒(5736日)

DateTime クラスをインスタンス化する際に、日時を引数として渡します。省略した場合は現在の日時が入ります。(デフォルトは「now」)

diff() はオブジェクトを返し、プロパティとして y や h などの日時を持ちます。

[PHP]PHPを始めた頃に思っていた細かい疑問

「まず何から始めればいいのか」

当然ですが PHP を扱うには PHP を実行できる環境を手に入れなければなりません。
要するにサーバーです。レンタルサーバー上で実験的なプログラムを実行して練習するのは極めて危険な行為なので、基本的には手持ちのパソコンの中にサーバー環境を作ります。
Windows なら XAMPP を、Mac なら MAMP をインストールすれば簡単に構築できます。
どちらも無料です。MAMP の PRO 版は有料ですが無料版でも十分です。


「テキストエディタ?統合開発環境?」

ソースコードを書くにはテキストエディタか統合開発環境(IDE)が必要になります。
どちらを選べばいいかは迷うかもしれませんが、まずはテキストエディタです。
最終的には併用する形になるかもしれませんが最初の頃に IDE が必要になることは有りません。

[参考]
Windows 用テキストエディタの比較
Mac 用テキストエディタの比較


「文字コード、改行コードは何を使う?」

多くのテキストエディタは文字コードを選択できます。
日本語扱う以上 UTF-8、Shift-JIS、EUC-JP、ISO-2022-JP というのが何のことなのか調べておいて下さい。
ソースコードを書く時、特別なケースを除けば「UTF-8」です。「UTF-8N」「UTF-8(BOMなし)」という選択肢があるならそれを選べば問題有りません。「UTF-8(BOM)」を選んではいけません。

改行コードは「LF」に統一した方がいいですが、おそらく FTP ソフトでアップロードするときに自動的に統一されると思うのであまり神経質にならなくても良いと思います。


まずは「Hello, World!」

どんな解説書でもとりあえずはこれです。
下記ソースコードをファイルに保存して実行して下さい。

<?php
echo 'Hello, World!';

実行と言われても・・・という人はブラウザのアドレスバーに「http://localhost/(ファイル名)」を入力してアクセスするだけです。
ファイルをどこに保存するのかは XAMPP なのか MAMP なのかによります。
詳しくは「Document Root」という単語で検索して下さい。


「ソースコードの終わりは『?>』をつけるべき?」

ソース全体が PHP の場合は基本的にはつけません。
HTML 文章の中で部分的に PHP を使う時だけつけます。

<title><?php echo $title;?></title>

「省略してもよい」というよりも「必要でない限り省略すべき」です。
php ファイルを include する際、「?>」以降の無意味な改行コードやスペースが混入するとヘッダーの出力などに思わぬ悪影響を与える可能性があるからです。


何を作ればいいのか?

次の課題を順番にこなすのが良いと思います。

・「Hello, World」
・足し算&掛け算
・数値が 10 以上なら「OK」と表示
・1~10 の数字をループを使って表示
・「あいうえお」を foreach で一文字ずつ表示
・ランダムな挨拶文を表示
・ユーザー定義関数を使った足し算&掛け算
・アクセスカウンター
・一行ずつ追加で書き込める掲示板
・日記
・データベース(MySQL/PostgreSQL)を使った日記

入門者向け解説書で「メールフォームを作ろう」みたいなものが有りますが作ってはいけません。危険です。
「どうして?」と思うかもしれませんが、そう思っている間は手を出すべきでは有りません。


「英語が読めない」

英語だから読めないのでは有りません。専門用語だから読めないのです。
「Special relativity」を「特殊相対性理論」と日本語にしたところで意味までは理解できません。
プログラミングをする際に出てくる英語は簡潔で型通りです。
そのうち慣れます。

【頻出単語】

・invalid (正しくない/ルールに従っていない)
・failed (失敗した)
・required (~が必要)
・variable (変数)
・undefined (未定義の/宣言されていない)
・unexpected (予期せぬ/想定した型でない)
・fatal (致命的な/処理を中断せざるをえないほどの)
・syntax (構文上の/文法的な)
・denied (否定された/拒否された)

変数名や関数名、ファイル名にも英語を使いましょう。
ローマ字を使ってはいけません。


「インデントには何を使う?」

ソースコードを読みやすくするために半角スペースやタブを使って字下げをするのが一般的です。
半角スペースなら4つ入れるのが基本です。
私はタブの方が好きですがプロジェクト内で統一しましょう。
この話題は戦争の火種になりかねないデリケートなものなので他人に押し付けてはいけません。


「オブジェクト指向って何?」

あたかも難しそうな単語を使って初心者を震え上がらせようとするために作られた言葉です。
「クラスや関数が使えるようになるとプログラムが綺麗に書けるよ」という程度の意味だと思っておけば良いです。

律儀に Wikipedia を読んで「オブジェクト指向(オブジェクトしこう)とは、オブジェクト同士の相互作用として、システムの振る舞いをとらえる考え方である。」の一文で挫折しないで下さい。


「将来はマニュアルなしでもスラスラと書けるようになりたい」

駄目です。

プログラミングは入学試験ではありません。オンラインマニュアルが利用できる環境ならどんなプロフェッショナルでも参考にしています。
PHP 自体もバージョンが変われば仕様も変わります。定期的にマニュアルをチェックする癖をつけておかないとかえって危険です。

[PHP]JSON+cURLで時刻同期型ワンタイムパスワードによる認証

外部サーバーと通信する際、許可されたクライアントであるかを確認するためにワンタイムパスワードによる認証を必要とする場合があります。 ワンタイムパスワードにはいくつか種類がありますが、ここでは時刻同期型のワンタイムパスワードを使って認証を行ってみます。

この方式は、現在時刻を共通のパスフレーズで暗号化し、クライアントとサーバーで同じ値になるかを確認し、認証します。

注意:ここで提示しているサンプルは解説のために簡易化したものであり、十分な安全性は考慮されていません。

【クライアント側】
<?php
//cURLによるJSONデータ送信
function post_param($url, $params=array()){
	$conn	 = curl_init($url);
	$content = json_encode($params);
	$header	 = array(
		'Content-type: application/json; charset=UTF-8',
		'Content-Length: ' . strlen($content)
	);

	curl_setopt($conn, CURLOPT_CUSTOMREQUEST, "POST");
	curl_setopt($conn, CURLOPT_POSTFIELDS, $content);
	curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30);
	curl_setopt($conn, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($conn, CURLOPT_HEADER, $header);

	// SSL の証明書を検証しない
	curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($conn, CURLOPT_SSL_VERIFYHOST, false);

	for ( $retry=0; $retry < 3; $retry++ ) {
		$res	 = curl_exec($conn);
		$info	 = curl_getinfo($conn);

		if($res === false || $info === false){
			sleep(3);
		} else {
			break;
		}
	}
	curl_close($conn);
	
	$body = substr ($res, $info["header_size"]);
	return json_decode($body, true);
}

date_default_timezone_set('UTC');

// 暗号化用パスフレーズ(必ず変更すること)
$salt = 'bOZGM6IZ5Od05wRMUA3ASUEZsPQOnslf';

// 通信先URL(例)
$url = 'http://localhost/server.php';

// ユーザーID
$id = 1;

$time = strtotime( date("Y-m-d H:i:00") );

$token = hash('sha256', $salt . $id . $time);

$param = array('id' => $id, 'token' => $token);
$res = post_param($url, $param);

echo $res['status'];
・送信内容(例)
Array
(
    [id] => 1
    [token] => 084750a0b42140de67296799bbfee02f68bebec3a5a5f337e13441367dac8b53
)
【サーバー側】
<?php
// ユーザーによってパスフレーズを切り替える
function get_user_salt($id){

	// 本来はMySQLなどのデータベースから取得するべき
	switch($id){
		case(1): $salt = 'bOZGM6IZ5Od05wRMUA3ASUEZsPQOnslf'; break;
		case(2): $salt = '2WsxL8hMfArsYT5psSFdHhR3Gfr9pQfY'; break;
		case(3): $salt = 'Si68GYAbnppYUH3745PbzEQQDEYUbiTc'; break;
		case(4): $salt = 'tHMKD4F8MHxLEiRbAK8NV3sLbShiJXKs'; break;
		case(5): $salt = '9K6nSS9j3GMYk6KDRB3aiYf4k3f24nkd'; break;
		default: $salt = ""; break;
	}
	
	return $salt;
}

date_default_timezone_set('UTC');
$json	 = file_get_contents('php://input');
$content = json_decode($json, true);

$id = isset($content['id'])	 ? $content['id']	 : null;
$client_token = isset($content['token']) ? $content['token'] : null;

// データベースからユーザーIDに紐付けられたパスフレーズを取得
$salt = get_user_salt($id);

// 前後1分を調べる
$server_token = array();
for($i=-1;$i<=1;$i++){
	$time = mktime( date('H'), date('i') + $i, 0, date('m'), date('d'), date('Y'));
	$server_token[] = hash('sha256', $salt . $id . $time);
}

$result = array();

if(in_array($client_token, $server_token)){
	$result['status'] = 'SUCCEEDED';	// 認証成功
} else {
	$result['status'] = 'FAILED';		// 認証失敗
}

header('Content-type: application/json');
echo json_encode($result);
・結果
SUCCEEDED

クライアントの PHP を実行すると cURL でサーバーに ID と認証用トークンが送信され、正しく認証された場合、配列 status として「SUCCEEDED」が返り、失敗した場合「FAILED」が返ります。
パスフレーズを用いてハッシュを行うので、この $salt は推測されにくいランダムな値に変更して下さい。この値はクライアントとサーバーで同じものを使う必要があります。
今回トークンは「パスフレーズ(salt) + ユーザーID + タイムスタンプ」を SHA-256 によるハッシュで暗号化して作りました。

サンプルなのでユーザー別のパスフレーズを取得する際(get_user_salt)、switch() を使って直書きした値を返していますが、
実際にはソースコードに直接書かずに MySQL などのデータベースに登録した内容を返すようにして下さい。
ID が 1 の時の $salt の内容がクライアント側の $salt と同じ値になっています。

時刻を同期するため、サーバーとクライアントのタイムゾーンは共通のものを使用する必要があります。
上の例では UTC(協定世界時) を使っていますが「Asia/Tokyo」などでも問題有りません。
認証には時刻の分単位までを比較するので秒は省きます。
具体的には秒を常に「0 秒」としてタイムスタンプを生成します。
サーバー側では前後一分のずれを許容するために、現在時刻に「-1 分」「+0 分」「+1 分」した3つのタイムスタンプをもとにしたトークン作っておき、クライアントから送信された認証用トークンがその中に含まれているかを in_array() で調べます。

トークンの内容は 1 分ごとに変化するので、パスフレーズ(salt)の内容を知られない限り推測はかなり困難になります。
とはいえ中間者攻撃にはあまり強くないため、より安全な通信を行うのであれば SSL を利用して通信そのものを暗号化して下さい。

送信内容を JSON にしたのは Javascript との連携が取りやすいことと、文字型・数値型の区別があること、多次元の連想配列が使いやすいことが理由です。
PHP で使うことだけを前提としているのであれば serialize() を使うのも良いと思います。