TRY AND ERROR

気になったこと、勉強したこと、その他雑記など色々メモしていきます。。Sometimes these posts will be written in English.,

【PHP】Windowsのbatファイルを実行する

なかなか使うことは少ないと思いますが、PHPからbatファイルを実行する
方法をメモっときます。

PHPの組み込みメソッドで「exec」という外部プログラムを実行するものがあります。
例えば、以下のように記載するとphpファイルと同階層にあるtest.batを実行できます。

exec("cmd.exe /c test.bat");

また、batにパラメータを渡すこともできます。
bat側では「%+数字」で左から順番にパラメータを取得できます。

※「%0」にはbatの名前が入るので、パラメータ自体は「%1」から始まります。

ーーPHP側ーー
exec("cmd.exe /c test.bat param1 param2");

ーーbat側ーー
echo %1 //"param1"を出力
echo %2 //"param2"を出力


クライアントからのリクエストパラメータを渡してbatを実行するといったこともできそうですね。。

exec("cmd.exe /c test.bat" ." " .$_POST["id"] ・・・);

【PHP】HTML文字列のエスケープ

クライアント側に文字列を返すサーバ側の処理を考えた際に、
そのまま返すと文字列にscriptが含まれる場合にscriptが動作してしまうことも考えられます。

そこで、サーバから文字列を返す際には、HTMLを形成する特殊文字をエスケープする必要があります。
PHPにはhtmlspecialcharsというHTMLのエスケープメソッドが用意されています。

htmlspecialchars("文字列", "エスケープの種類", "文字コード");

エスケープの種類は以下の通りで、定数を指定します。

エスケープ種類(定数) 意味
ENT_COMPAT ダブルクォートは置き換えるが、シングルクォートは置き換えない
ENT_QUOTES シングルクォートとダブルクォートの両方を置き換える
ENT_NOQUOTES シングルクォートとダブルクォートの両方を置き換えない

入力フォームの値をサーバで取得し別ページに出力するようなプログラムがあったとして、
次のようなスクリプトが入力された場合、htmlspecialcharsを使っている場合とそうでない場合の出力結果は以下の通りです。

・htmlspecialcharsを使った場合
<script type="text\/javascript">alert(document.cookie)<\/script>

・htmlspecialcharsを使っていない場合
<script type=\"text\/javascript\">alert(document.cookie)<\/script>


htmlspecialcharsを使っていない場合、クライアント側で出力した段階でscriptが実行されてしまいます。悪意のあるスクリプトが入力されることを想定すると、出力時にHTMLとして認識させないことが重要です。

WEBサイト中で動的にHTMLやJavascriptを生成している部分に、悪意のあるコードを埋め込む攻撃のことをXSS(クロスサイトスクリプティング)という。

【PHP】sessionとcookieについて

今回はsessionとcookieについて少しおさらい。
あとPHPで具体的にどう使うか、についても触れておきます。

sessionとcookieって?

大まかに言うと、両者ともある一定期間データを保存しておく仕組みです。
ただ、データをサーバ側に保存しておくのか、クライアント(ブラウザ)側に
保存しておくのかという点が異なり、サーバ側に保存するのがsession、クライアント側に保存するのがcookieです。。

こんなところで使われている

ネットショップなどで買い物する際に、一度ログインすると一定期間ログイン状態が保持されることがあると思います。この仕組みにsessionとcookieが使われています。
(ざっくりと言うと)sessionにユーザー情報を保存しておき、cookieにsessionを特定するためのID(仮にsessionIDとしておきます)を保存しておきます。サイトにアクセスされた際に、cookieに保存されたsessionIDをキーにsession情報を呼び出し、ログイン状態を再現するというわけです。
なので、sessionが使われる場合はcookieも使われることになります。

PHPでsessionとcookieを使ってみる

実際にPHPでは以下のように記載します。

cookie

setcookie('クッキー名','値','有効期限','クッキーが有効なパス')でcookieを作成することができます。

setcookie('testCookie', 'クッキー', time()+3600, '/');

実際にcookie情報を見てみると、上記で作成したものが表示されています。
f:id:tamago-engineer:20150602233604p:plain

firefoxの場合、cookie情報はfirebugというプラグインを入れるとみることができます。



session

session_start()でセッションを開始し、$_SESSION変数に値をセットしていきます。
以下の例では'test'という名前のsession変数に'テスト'という値をセットしています。

session_start();
$_SESSION['test'] = 'テスト';


該当ページでsessionが使われると、クライアント側(ブラウザ)にsessionを特定するためのIDを格納したcookieが作成されます。
ブラウザのcookie情報を見てみると、クライアント側には'PHPSESSID'という名前のcookieにsessionIDが保存されていました。
f:id:tamago-engineer:20150602225412p:plain



サーバ側でsession_id()を実行するとsessionIDを取得することができます。
試しに出力してみると、cookieに保存されていたsessionIDと同じものが確認できました。

echo session_id();

ーーーーー結果ーーーーー
9mcbahq5vof3ccht496h3r16h7


sessionIDが保存されるcookieに有効期限を指定することもできます。
以下の例では、有効期限を60秒間としています。

session_set_cookie_params(60);
session_start();
・・・

cookie情報からも有効期限が設定されていることがわかります。
f:id:tamago-engineer:20150602231236p:plain


cookieとして保存されたsesssionIDは改竄される可能性もあります。
この対処法としてsession_regenerate_id()というメソッドを使うと、
sesssionIDを新しいものにしてくれます。
session_regenerate_id(true)とすると、古いsession情報を削除することができるので、無駄なリソースを削減するために行っておいたほうが良いと思います。

session_regenerate_id(true)を連発するとsessionが切れるバグがあります。例えば、連続でリロードされた場合やsubmitボタンを連打された場合などに切れてしまうことがあるとか。。。新しいIDを生成してそこにセッション情報をコピーするといったような処理を行っているため、短時間に連続リクエストされると不都合が出てしまうのか・・・?
なんにせよ、不用意にsession_regenerate_id(true)を連発するのは危険なので、
jsで連続リクエストを防止するとか、PHPで一定確率でsession_regenerate_id(true)を行うようにするとか、対策が必要です。


セッション情報をすべて削除したい場合、session_destroy()というメソッドを使います。ただ、session_destroy()はcookieの削除までは行ってくれないため、
自前で削除する必要があります。

setcookie('PHPSESSID', '', time() - 1800, '/');
session_destroy();

setcookieはcookieをセットするメソッドですが、第3引数の有効期限を過去にすることでcookieを削除することができます。

ちなみに、特定のsession変数のみ削除したい場合はunset($_SESSION['XXX'])を使います。

【PHP】PDOでSQLを発行する2つの方法

PDOでクエリを実行する方法として以下の方法があります。

⑴ queryを使う
⑵ prepare後にexecuteする


具体的な使用例は以下の通り

query

$sql = 'select * from userTable';
$stmt = $dbh->query($sql); 

prepare

$sql = 'select * from userTable where id = ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array('A0001'));


prepareではプレースホルダーを利用することができ、最初にプレースホルダー付きのSQL文の解析を行います。その後、SQL文の解析をせずにパラメータだけ変えて何度も実行することができるようです。
複雑なSQL文は解析にコストがかかるため、複数回発行されるようなSQLではprepareを使うと効果的ということです。
また、prepareを使うとパラメータのエスケープを行ってくれます。

一方で、queryは一般的にパラメータを必要としないような単純なSQLを発行する際に利用するようですが、これはprepareを使ってもできるのでいまいちメリットがわかりません。。。

【PHP】PDOでselect件数を取得する

PDOを使ってDBから検索結果の件数を取得したい場合、
PHPの公式マニュアルでは、rowCount()というそれらしきメソッドがあります。

ただ、公式マニュアルによると「rowCount()はすべてのデータベースでうまく作用するとは限らない」とのこと。。。

そもそも、rowCount()はinsert、delete、updateの実行件数を取得することが目的で、select件数を取得するのは推奨されないっぽい。。

PDOStatement::rowCount() は 相当する PDOStatement オブジェクトによって実行された 直近の DELETE, INSERT, UPDATE 文によって作用した行数を返します。


じゃあどうするのかというと、countを取得するSQL文を書けば良いとのことでした。

$sql = 'select count(*) from userTable';
$stmt = $dbh->query($sql);
echo $stmt->fetchColumn();

※PDOStatement::fetchColumn()は引数にindexを指定して単一カラムを取得することができますが、上記のように省略すると0番目のカラムを取得します。