※ パクレゼルヴではWeb開発エンジニアを大募集中!詳細はこちら

Archive

Archive for the ‘PHP’ Category

PHPのガーベジコレクタについて  

2010/6/30 水曜日 16:48:12

PHP 5.2までのガーベジコレクタは参照カウント方式です。
循環参照が発生すると、その参照に含まれるオブジェクトが回収できませんので、デーモンなど長時間実行するようなスクリプトを作る場合は循環参照を起こさないように注意する必要があります。

尚、PHP 5.3からは循環参照コレクタが使え、デフォルトで有効になっています。
実際にPHP 5.1.6と5.3.0を比較し、実際にどのような動きになるか以下のコードで試してみました。

< ?php

ini_set('memory_limit', -1);
// gc_disable();

class A
{
}

$loop_count = 1000000;
for($i = 1; $i <= $loop_count; $i++)
{
    $a = new A;
    $a->self = $a; // 循環参照
    if (($i % round($loop_count / 10)) ==  0)
    {
        printf("%10d: %10d KB\n", $i, memory_get_usage() / 1024);
    }
    // unset($a->self);
}

if ( function_exists('memory_get_peak_usage') )
{
    // (PHP 5 >= 5.2.0)
    printf("max %10d KB\n", memory_get_peak_usage() / 1024);
}

PHP 5.1.6の場合

PHP 5.1.6だと、以下のような結果になります。
循環参照されている状態だと、どんどん増え続けます。

$ php mem_test.php
    100000:      31947 KB
    200000:      63872 KB
    300000:     101942 KB
    400000:     127723 KB
    500000:     153504 KB
    600000:     203861 KB
    700000:     229643 KB
    800000:     255424 KB
    900000:     281205 KB
   1000000:     306986 KB

unsetの所のコメントアウトを外して開放するようにすると以下のような結果になります。
循環参照されていないので、メモリの開放は正しく行われます。

    100000:        622 KB
    200000:        622 KB
    300000:        622 KB
    400000:        622 KB
    500000:        622 KB
    600000:        622 KB
    700000:        622 KB
    800000:        622 KB
    900000:        622 KB
   1000000:        622 KB

PHP 5.3.0の場合

PHP 5.3.0の結果。一定以上から変化しません。
循環参照コレクタがうまく働いているようです。

$ php mem_test.php
    100000:       1582 KB
    200000:       1582 KB
    300000:       1582 KB
    400000:       1582 KB
    500000:       1582 KB
    600000:       1582 KB
    700000:       1582 KB
    800000:       1582 KB
    900000:       1582 KB
   1000000:       1582 KB
max       5139 KB

循環参照コレクタの有効/無効の変更はgc_enable/gc_disable関数で行います。
gc_disableの所のコメントアウトを外すと、PHP 5.3.0でもメモリが増え続けます。

$ php mem_test.php
    100000:      43125 KB
    200000:      85692 KB
    300000:     136451 KB
    400000:     170827 KB
    500000:     205202 KB
    600000:     272345 KB
    700000:     306720 KB
    800000:     341096 KB
    900000:     375471 KB
   1000000:     409846 KB
max     409863 KB

尚、gc_collect_cycles関数で強制的にガベージを収集することができますが、gc_disableで循環参照コレクタを無効にしている時に確保したメモリは収集されないようですので、わざわざ無効にする必要はないと思います。
また、gc_collect_cycles関数を使うと、わずかながらメモリが増えていく現象が見られましたので、ガベージの収集も基本的にはPHPに任せるようにした方が良いようです。

当然正しく開放されるようにプログラムを組むべきですが、デーモンのように長時間実行されるようなスクリプトを作る場合、石橋を叩いて渡るなら5.3以降を使うようにした方が良さそうです。

個人的な意見としては、開発時はgc_disableで開発し、テスト・運用時にgc_enableで動かすのが良いのではないかと思います。

マカー PHP

久々にベタ書きで入力フォームを書いてみる  

2010/6/30 水曜日 15:47:47

モリモリモリモリです。

Webアプリは実際の業務ではCakePHP、symfony、Zend Framework等のPHPフレームワーク、
自社フレームワーク、またテンプレートエンジンとしてsmarty等を使うかと思います。

WEBプロミングを始めたばかりの人は何を参考にすればいいか割と苦労するかなと。
私自身がそうでした。

ベタ書きで簡単な入力フォームを書いたらどーなるかなと思って久々に書いてみることにしました。

以下は、セッションを利用したPC向けの書き方です。
手抜きな部分も有りますので参考程度にお願いします。

< ?php
session_start();

$param = array('name','tel');
foreach($param as $val){
	// SESSION値よりもPOST値を優先
	if(isset($_POST[$val])){
		${$val} = trim($_POST[$val]);
	}elseif(isset($_SESSION[$val])){
		${$val} = trim($_SESSION[$val]);
	}else{
		${$val} = '';
	}
}

// 確認ボタンが押された際の処理
if(isset($_POST['confirm_btn'])){
	// バリデーション
	$err = array();
	foreach($param as $val){
		switch($val){
			case 'name':
				if($name == ''){
					$err['name'] = '※名前は必須です。<br />';
				}
				break;
			case 'tel':
				if($tel == ''){
					$err['tel'] = '※電話番号は必須です。<br />';
				}elseif(!preg_match('/^\d+$/',$tel)){
					$err['tel'] = '※電話番号は数字で入力して下さい。<br />';
				}
				break;
			default:
		}
	}
	// エラーが無ければ
	if(count($err) == 0){
		// 値をセッションに格納
		foreach($param as $val){
			$_SESSION[$val] = ${$val};
		}
		// 確認画面へロケート
		header('Location:http://example.com/confirm.php');
		exit;
	}
}
?>
<html>
<body>
<form method="post" action="">
	< ?php
	if(isset($err['name'])){
		echo $err['name'];
	}
	?>
	名前: <input type="text" name="name" value="<?php echo htmlspecialchars($name); ?/>" /><br />
	< ?php
	if(isset($err['tel'])){
		echo $err['tel'];
	}
	?>
	電話番号: <input type="text" name="tel" value="<?php echo htmlspecialchars($tel); ?/>" /><br />
	<input type="submit" name="confirm_btn" value="確認" />
</form>
</body>
</html>

投稿の際に勝手に置換されたりするので、上記のコードをまんまコピペしてもうまく動きません。汗
適宜修正お願いします。
すいません。

5行目あたりからの処理の

${$val}

ですが、こんな感じで可変変数を使うことにより、$name、$telに入力値が入ってくるようになります。

これについては弊社の下記記事も参考にどうぞ。
http://labs.pakureserve.jp/archives/124

バリデーション処理もベタで書いてますがご了承下さい。

処理としては、エラーが無ければセッションに格納して、確認画面へロケーション。
確認画面ではセッションの値を表示
(表示の際は項目に応じてhtmlspecialchars、nl2br関数等を使って表示させましょう)し、
登録完了画面で登録が完了したら、

$_SESSION = array();

等でセッションを空にしてやる感じで。

あと、最近たまたま気付いたことですが、もしこのフォームのテキストフォームが一つだった場合、
IEでテキストを入力してENTERキーを押すと、多分動きません。笑

これはIEのバグ?

IEでテキストフォームが一つの場合、ENTERキーでSubmitした場合に、
なぜか type=”submit” のパラメータと値がpostされないのです。
つまり、この場合だと、$_POST['confirm_btn']のパラメータと値が送信されません。

これを回避するためには以下のようなテキストフォームをダミーで入れといてやれば大丈夫です。

<input type="text" style="display:none;" />

…美しくない!

MoriMoriMoriMori HTMLとか, PHP

コーディングルール  

2010/6/8 火曜日 17:35:14

皆さん、コードを書くときコーディングルールを気にしますか?
自分は気にしてませんダメな人ですね。

ということで、好きなフレームワークCodeIgniterの例にならって、まとめ的に書きとめようと思う。

◆クラス名・メソッド名
クラス名は常に最初の文字を大文字にしなければいけません。コンストラクタメソッドはクラス名と同じにします。
メソッド名は、すべて小文字にして動詞など含めてわかり易い名前にするべきです。

間違い:
class superclass
class SuperClass

正しい:
class Super_class

間違ったメソッド名と正しいメソッド名

間違い:
function fileproperties()		// 表現がわかりにくく、アンダースコアが抜けている
function fileProperties()		// 表現がわかりにくく、キャメルケースが使われている
function getfileproperties()		// ベター! しかしながら可読性に欠ける
function getFileProperties()		// キャメルケースが使われている
function get_the_file_properties_from_the_file()	// 長過ぎる

正しい:
function get_file_properties()	// 説明的、アンダースコア、全て小文字

◆変数名
変数名はクラス名とほぼ同じ。「$p」とか単語になっていないものはループ変数のみにつかいます。

◆定数
定数は変数とほぼ同じ。だけど、例外として大文字のみを使用します。

◆TRUE、FALSE、NULL
常に大文字を使用します。

◆論理演算子
「||」は解像度の低いデバイスでは良く見えなく「11」に見えてしまうことがあることから「OR」を使用する。

間違い:
if ($foo || $bar)
if ($foo AND $bar)  // 問題無いがアプリのシンタックスハイライト機能にはお勧めできない
if (!$foo)
if (! is_array($foo))

正しい:
if ($foo OR $bar)
if ($foo && $bar) // 推奨
if ( ! $foo)
if ( ! is_array($foo))

◆インデント
Allman スタイルのインデントを利用します。クラス宣言を除いて、括弧は常に1行に単独で配置し、その制御ステートメントと同じ深さにインデントします。

間違い:
function foo($bar) {
	// ...
}

foreach ($arr as $key => $val) {
	// ...
}

正しい:
function foo($bar)
{
	// ...
}

foreach ($arr as $key => $val)
{
	// ...
}

◆括弧の前後のスペース
一般的に、括弧の前後にはスペースを追加すべきではありません。
例外として、括弧内に引数を記述する PHP 制御構造(if,switch,for,while等)では、関数と区別しやすくしたり可読性を高めるために、常にスペースを配置すべきです。

間違い:
$arr[ $foo ] = 'foo';

正しい:
$arr[$foo] = 'foo'; // 配列のキーの前後には空白を入れません

間違い:
function foo ( $bar )
{

}

正しい:
function foo($bar) // 関数定義の括弧の前後には空白を入れません
{

}

間違い:
foreach( $query->result() as $row )

正しい:
foreach ($query->result() as $row) // PHPの制御構造の後ろには空白を1つ入れますが、中の括弧には入れません

◆PHPエラー
コードはエラーを起こさず、潜在的なWARNINGやNOTICEもなく動作するようにしましょう。
例えば、最初にisset()で確かめずに($_POST配列のキーのような)未定義の変数を使わないようにしてください。

あくまでチラシの裏ですので、人それぞれのコーディングルールなので、特に決まりごとが無ければやりやすい方法でやるべきかとー。

下音タヌキ PHP

簡単な入力フォームこそ気を付けよう  

2010/5/31 月曜日 22:57:17

phpで作る入力フォームでやってしまいがちな事を紹介します。

入力した情報を確認画面で表示させる、というよくあるパターン。
(ベタ書きで失礼します。)

▼form.php(入力画面)

<form method="post" action="./confirm.php">
	ニックネーム: <input type="text" name="title" value="" /><br />
	<input type="submit" value="送信" />
</form>

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title']){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

これ、一見問題無さそうですが、ある文字を入力すると未入力と表示されてしまいます。

そうです。

0(ゼロ)です。

TRUEかFALSEかで判断しているため、post送信された0はFALSEとして見なされます。

なのでこうやりましょう。

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title'] != ''){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

どうしても「0」という名前で登録したいんだ!というお客様がいないとも限りませんからね。。

 

さて、もう一つ紹介します。

クロスサイトスクリプティング(XSS)のお話です。

今度は同じphpファイル自身にpost送信するパターン。
▼http://example.com/test.php

< ?php
// なんらかの処理
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
…
</form>

action先に何の迷いも無く

$_SERVER['PHP_SELF']

と書くと、実はこれが結構危ないのです。

他のhtmlファイルで下記のようなソースを書き、上記のスクリプトへのリンクを貼りましょう。
そして、クリックしてみましょう。(攻撃!)

<a href="http://example.com/test.php/%22%3E%3Cscript%3Ealert('xss');%3C/script%3E/">攻撃</a>

JavaScriptが実行され、アラートが表示されるかと思います。

URLの後半部分はURLエンコードされたものです。

$_SERVER['PHP_SELF']は、渡されたURIのホスト部分の後から、
GETクエリ部分の前まで(?の前まで)をURLデコードした形で格納します。

つまり、$_SERVER ['PHP_SELF']は、

/test.php/"><script>alert('xss');</script>/

となるので、

<form method="post" action="/test.php/"><script>alert('xss');</script>/">

となり、JavaScriptが実行されてしまうわけです。

 
では結論。
このようにしましょう。

</form><form method="post" action="">
…
</form>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>">
…
</form>
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
…
</form>

空にしてあげるのが一番ラクかと思います。

MoriMoriMoriMori HTMLとか, PHP

fgetcsvがphp5で文字化けする  

2010/5/31 月曜日 20:45:08

仕事でCSVを使うことがあったんですけど、その時に悩んだことを書こうと思います。

fgetcsv()という便利な関数があるんですが、php5で使うと文字化けするんですよ。
なぜ文字化けが起きるのかと言うと、
「fgetcsvはマルチバイト文字の2バイト目に’\'があると、直後のダブルクォーテーションをエスケープするという、少なくともエクセルの仕様にはない独自解釈をする。」
らしい。
訳わかんない事言ってるなーと思いますが、要はShift-JIS形式のCSVをfgetcsv()関数でと文字化けするみたいです。

いろいろと調べた結果、
「PHP 5.0 の fgetcsv() はロケールの設定に依存します。」
とのことなので、

setlocale('LC_ALL', ja_JP.EUC_JP);

でロケールを設定してあげれば直ら・・・・ない。
ぱっと見た感じ直ってるように見えるんですけど、機種依存文字が化けてしまう。

それじゃあCSVをまずUTF-8とかに変換しちゃえば解決するんですけど、実はfgetcsvぽく動いて、しかもバグがない
関数があったので、それを使うことに。

function fgetcsv_reg (&$file, $length = null, $d = ',', $e = '"') {
	$d = preg_quote($d);
	$e = preg_quote($e);
	$_line = "";
	$eof = FALSE;
	while (!$eof && !feof($file)) {
		$_line .= (empty($length) ? fgets($file) : fgets($file, $length));
		$itemcnt = preg_match_all('/'.$e.'/', $_line, $dummy);
		if ($itemcnt % 2 == 0) $eof = true;
	}
	$_csv_line = preg_replace('/(?:\r\n|[\r\n])?$/', $d, trim($_line));
	$_csv_pattern = '/('.$e.'[^'.$e.']*(?:'.$e.$e.'[^'.$e.']*)*'.$e.'|[^'.$d.']*)'.$d.'/';
	preg_match_all($_csv_pattern, $_csv_line, $_csv_matches);
	$_csv_data = $_csv_matches[1];
	for($_csv_i=0;$_csv_i<count ($_csv_data);$_csv_i++){
		$_csv_data[$_csv_i]=preg_replace('/^'.$e.'(.*)'.$e.'$/s','$1',$_csv_data[$_csv_i]);
		$_csv_data[$_csv_i]=str_replace("'","\\'",$_csv_data[$_csv_i]);
		$_csv_data[$_csv_i]=str_replace($e.$e, $e, $_csv_data[$_csv_i]);
	}
	return empty($_line) ? false : $_csv_data;
}

この関数で、CSVを正規表現で切ってます。
使うときはこういう感じで

$new_data = array();
$header = array('name','zip','addr','tel','fax','manage','security','url');

$filename = 'CSVファイルパス';
$file = fopen($filename, "r");

while (($data = fgetcsv_reg($file)) !== false) {
	$_enc_to=mb_internal_encoding();
	$_enc_from=mb_detect_order();
	mb_convert_variables($_enc_to,$_enc_from,$data);
	$num = count($data);
	for ($c=0; $c < $num; $c++) {
		$new_data[$header[$c]] = $data[$c];
	}
	foreach($new_data as $key => $val){
		echo $key." : ".$val."<br />";
	}
}

こういう風に使えば、fgetcsv()関数とほぼ同じ動作をし、さらに文字化けもおきません。

こういうオリジナルの関数を自分でも作れるようになりたいですね。。

つっしー PHP, 未分類