にゃあ

コマンドラインでPHPを使うときの11のTips

1. ファイル単位の構文チェックはphpコマンドで行おう

phpコマンドにlオプションを指定することで、特定のファイルの構文チェックができる。エラーの詳細は、php.iniで指定したエラーログに出力されるので、そちらを確認する。

php -l /path/to/file.php

2. 成功・失敗はexit()の引数で切り分けよう

exit()の引数に整数を指定すると、端末側に処理が成功したか失敗したかを伝えることが可能。成功で終わったことを伝える場合は、exit(0);というふうにし、失敗で終わったという場合は、exit(1);か1以上の整数を与える。

<?php
echo "成功しました".PHP_EOL;
exit(0);

<?php
echo "失敗しました".PHP_EOL;
exit(1);

(ちなみに、私のプロンプトは前回のコマンドが失敗したときは、顔文字が赤くなるように細工してあるので、exit()の引数が働いているのがよくわかります。)

3. メッセージ出力用の関数をつくろう

メッセージの出力は、単純にecho 'message';とやってもいいのだが、これだと改行されないので、見づらくなってしまう。改行を有効ににするには、echo "message\n";のように最後に改行コードを付ける。ただ、毎回改行コードを文字列に埋め込むのは非効率的なので、下のようなメッセージ出力用の関数を作って、その関数で運用したほうが100倍楽だ。

function print($message)
{
	echo $message.PHP_EOL;
}

4. エラーメッセージの出力用の関数をつくろう

「メッセージ出力用の関数」と同様にエラーメッセージの出力も関数にしておくのがおすすめ。「え?エラーメッセージって普通にechoするだけでいいんじゃないの?」と思われそうだが、echoは標準出力になるので、実行端末側が普通のメッセージなのかエラーメッセージなのか区別できなくなる。実行端末と上手く連携するためには、エラーメッセージは「標準エラー出力」に吐き出すことが得策だ。標準エラー出力先はphp://stderrになる。エラーメッセージの出力用の関数の最も単純な実装が下の例だ。

function error_message($message)
{
	file_put_contents('php://stderr', $message.PHP_EOL);
}

エラーメッセージを標準エラー出力に吐き出すようにしておくと、例えばcronでエラーの時だけメールがほしいといった場合に便利。crontabを次のように、設定しておくだけでエラーに気づけるようになる。

0 4 * * * php /path/to/scheduled_job.php > /dev/null
# 標準出力は捨てて、標準エラー出力はメールに送られる。

5. exec()関数はラップして使おう

外部コマンドを実行するexec()関数ですが、ラップした関数を用意しておこう。自分がよく使うラッパーは次のような実装だ。

function execute($command, $captureStderr = false)
{
	$output = array();
	$return = 0;

	if ( $captureStderr === true )
	{
		$command .= ' 2>&1';
	}

	exec($command, $output, $return);

	$output = implode("\n", $output);

	return array('output' => $output, 'return' => $return);
}

exec()関数の第二引数はデフォルトではエラー出力を受け取らないので、エラー出力をキャプチャする必要がある場合には、コマンドに 2>&1 を埋め込む必要がある。上のラッパーでは、第二引数でエラー出力を受け取るか指定できるようにしている。

6. プロセスは小分けにしよう

Webインターフェイスだと、プロセスの寿命は長くてもせいぜい3秒程度だが、バッチ処理などを実装すると、どうしてもプロセスの寿命が長くなってしまう。プロセスの実行時間が長くなれば、メモリの使用量も大きくなりがりなので、メモリオーバに陥る危険性も必然的に高くなる。メモリ管理に自身のあるプログラマならメモリの残りを見ながらプロセスをコントローラすべきだ。が、もしメモリ管理に不安を覚えるようであれば、プロセスの寿命を短くすることに注力しよう。プロセス実行時間が短くなれば自ずとメモリオーバの危険性も低くなる。

実行時間を短くするTipsとして、最も単純なのがプロセスを小分けにする方法だ。exec()関数で、サブプロセスのPHPを実行するだけである。

exec("php subprocess.php", $output, $return);

7. 変数のスコープは狭くしよう

メモリオーバの対策として、できるだけグローバル変数を使わないという方法もある。グローバル変数はプロセスが終了しないかぎり、メモリから解放されないが、関数内の変数はスコープが切れると直ちにメモリから解放される。なので、できるだけメソッドや関数に小分けにして実装するのがおすすめだ。

<?php

function hoge()
{
	$bar = range(0, 1000);
}

$foo = range(0, 1000);
hoge();
// $barはここで開放される

// 長い処理…

// $foo は最後の最後まで開放されない

8. 実行権限に注意しよう

特にLinux系のサーバで実行するときは、実行権限に注意する必要がある。root権限で実行されるのか、apache権限で実行されるのか、それとも別のユーザ権限で実行されるのか。ファイル操作や、コマンドの実行で、権限によってエラーになったり実行できなかったりする。今、このPHPが誰によって実行されているか確認するには、passthru('whoami');を実行するといいだろう。

9. apache権限でsudoするときはコマンドごとにNOPASSWDを設定しよう

どうしてもapcahe権限でsudoする必要があるときがある。そんなときは、visudoでapacheをsudorに追加しておこう。その際、apacheに無条件でsudoできるようにするのは危険なので、コマンドを絞ろう。そして、そのコマンドに関してはパスワードなしで実行できるようにしよう。パスワードなしで実行できるようにするのは、PHPでインタラクティブにパスワードを入力するのは実装が面倒だからだ。

apache  localhost=(ALL) NOPASSWD: /path/to/command.sh, /path/to/command2.sh, /path/to/command3.sh

/path/to/command.shの中身は例えば、次のような例だ。たとえ、lsやmkdirのような単純なコマンドでも引数も含めてshファイルに固定しておき、lsやmkdirの実行権限自体は与えないようにする。そうすることで目的のコマンドを安全に実行できるようになる。

#!/bin/sh
ls /root
mkdir /root/newdir

PHP側の実装は、exec()関数を次のように叩くだけだ。

exec('sudo /path/to/command.sh');

余談だが、sudoするときの別の裏技として、apacheにパスワードを設定しておき、sudo -Sを利用する方法がある。ex.「echo "apache passowrd here" | sudo -S ls /root'」

10. プロセスはロックが必要か検討しよう

cronなどで処理の実行をスケジュール化しているとき、スケジュールの間隔が数分単位など短くなっていると、前のプロセスが完了する前に次のプロセスが走ってしまう場合がある。

process 1 -------------------------------------------------------->
process 2                    ------------------------------------------------------>
                             [この部分で二重に処理される危険性がある]

このような場合、ステータスファイルを検討してみるといいだろう。process1が走り始めた段階で、「現在プロセスが走ってます」という状態を表すファイルをどこかに作っておき、そのファイルがある限り、process2が走らないようにしておくといったものだ。下のような実装にすれば、同じプロセスが二重に走る心配がなくなる。

<?php

if ( file_exists('/path/to/process.lock') )
{
	echo "このプロセスはロックされています。".PHP_EOL;
	die(1);
}

echo "プロセスを開始します".PHP_EOL;

touch('/path/to/process.lock');

// 時間のかかる処理

unlink('/path/to/process.lock');

echo "プロセスを終了します".PHP_EOL;
die(0);

11. 今PHPのプロセスが走っているかは ps ax | grep php で調べよう

開発中はデバッグ中に、現在PHPのバッチ処理が動いているか知りたい時がある。Linuxであれば、そうしたときは次のコマンドを実行して調べてみよう。コマンドにphpが含まれているプロセスが表示され、一目瞭然だ。

ps ax | grep php

この応用として、私がよく使う確認コマンドは次のようなものだ。どういうものかというと、1秒ごとに上のコマンドを実行して、実行状況の進捗を確認するものだ。

while true; do ps ax | grep php; sleep 1; done;

まとめ

以上が、コマンドラインPHPを利用する上でのTipsでした。もっとこうしたほうがいいとか、こんな便利なTipsもあるよ、という意見・ご感想などもお待ちしてます^-^ 本気で、バッチ処理などを書くときはPHPを使わないなんて意見もありそうですが、それは自粛し...(ry




クラウド時代のウェブサービスで考えるべき3つのタイムゾーン

メモ的エントリー。日本で日本向けのサービスを展開していると、あまり意識することがないが、概して、ひとつのサイトには3つのタイムゾーンが共存している。 時差やサマータイムを計算する処理を実装する際には注意が必要である。

3つのタイムゾーン

1. サーバータイムゾーン

  • OS自体のタイムゾーン
  • ファイルシステムの更新時刻などが関係
  • 一度タイムゾーンが決まればまず変わらない

2. サイトタイムゾーン

  • サイト(サイト管理者)のタイムゾーン
  • データベースの時刻を記録するフィールドなどが関係
  • 一度タイムゾーンが決まればまず変わらない

3. クライアントタイムゾーン

  • サイトを利用するユーザのタイムゾーン
  • ユーザによってタイムゾーンは異なる
  • 同国内でも居住地が異なるとタイムゾーンが異なる場合がある

パターン

1. すべて同じ

例えば、サーバが日本にあり、日本に住んでいる人が管理者で、日本のユーザが利用する場合。

2. サーバとサイトのみ同じ

例えば、サーバとサイト運営会社はカリフォルニア時間で、ユーザはニューヨーク時間の場合。

3. サイトとクライアントが同じ

例えば、サーバはカリフォルニアの共有サーバで、サイト運営の母体は日本、ユーザは日本人のような場合。

4. すべて違う

例えば、サーバはカリフォルニアの共有サーバで、サイト運営はインドにオフショア、ユーザはニューヨークの時間のような場合。




svn commitする前にvar_dump, console.logが含まれてないかチェックするスクリプトをPHPで書いてみた

開発中にデバッグするために変数を出力する関数をプログラムに直接書きこむことがある。PHPであればvar_dump()、JavaScriptであれば、console.log()などがその関数になる。これらのプログラムに記述したデバッグ用のコードは、デバッグコードと呼ばれる。

プロジェクトを複数人で共有している場合、デバッグコードを書いたとしても、メンバーと共有する場合は消しておくことが必要だ。仕事でのプロジェクトであれば、デバッグコードによる余計な出力やプロセスの終了は不具合として扱われることになる。

自分も仕事でデバッグコードを書いて、それをそのままSubversionのリポジトリに上げてしまっていたので、svn commitする手前でvar_dump, console.logが含まれてないかチェックするスクリプトを作ってみた。

できたもの: svn-commit

ダウンロード
  • 開発環境がMacOSXなので、Macで動く。
  • ターミナルから使うので、コマンドラインインターフェイス。
  • どうせなのでPHPで書いてみた。
  • svn commitコマンドの感覚で使える。
  • ついでに、コミットコメントもいれやすい感じにしてみる。svn ci -m ""とかエディタの設定とかめんどくさいって人なので。

写真はsciにsvn-commitのエイリアスを張っている

使い方

  • svn-commitをダウンロード
  • PATHの通っているところにsvn-commitをおく。
  • svn-commitに実行権限を与えておく。
  • あとは、svn ciをするかわりにsvn-commitを実行するだけ。



cronでPHPを動かす2つの方法

1. コマンドラインインターフェイスで動かす

PHPをコマンドラインインターフェイスで動かす方法です。

# 4時半に実行
30 4 * * * php /private/path/to/batch.php

メリット

  • サーバ外部から実行されないので、セキュリティ的に安心。
  • タイムアウトの心配がない。
  • phpコマンドだけあればいい。
  • 余計なトラフィックがない。

デメリット

  • $_SERVER['SERVER_NAME']などのApache由来の環境変数が取れない。
  • ウェブインターフェース用に作ったプログラムがそのまま再利用できない場合がある。
  • 処理をトリガするサーバと、処理を行うサーバが同じである必要がある。(* SSHなどでリモートコントロールする場合をのぞく)

2. ウェブ経由で動かす

PHPをウェブ経由で動かす方法です。

# 4時半に実行
30 4 * * * wget --spider http://localhost/batch.php

メリット

  • $_SERVER['SERVER_NAME']などのApache由来の環境変数が取れる。
  • ウェブインターフェース用に作ったプログラムがそのまま再利用できる。
  • 処理をトリガするサーバと、処理を行うサーバが別でもいい。

デメリット

  • サーバ外部から実行されうるので、セキュリティ的に心配。あるいは認証処理を実装する必要がある。
  • タイムアウトの心配がある。
  • wgetコマンドが必要。(別にwgetじゃなくてもいいんだけど。)
  • 余計なトラフィックがある。

ケースによって柔軟に使い分けよう

長い処理、セキュリティが担保される必要のある処理は、コマンドラインで。

誰が実行してもよくて、ウェブインターフェース用のプログラムを再利用したい場合は、ウェブ経由で。

まあ、でも、全部、CLIでやってもいいんだけどね。




もっとより良いPHPerになるためのTips

PHPに限らず、プログラムには書き手やプロジェクトによって流儀は様々なのは最初に述べておくとして、より良いPHPerになるための20Tips(英語原典20 Tips you need to learn to become a better PHP Programmer | webgeekly.com)に自分なりの補足をしたいと思う。

5. 変数名に意味を持たせよ

変数に意味を持たせることは、いつもあなたのためになる。
データの型まで分かると、更に良い。
例えば、integer型の変数には、変数名のプレフィックスとしてiをつける。
変数に型宣言しないPHPにおいて、これは特に重要だ。

変数名の頭文字を大文字にするかどうかは、あなた次第だ。
しかし、読み易さから言っても、単語の先頭は大文字にすることをお勧めする。

こんな感じだ。

 
<?php
$iNumber = 10; // For Integers
 
$sName = "Marc"; // For Strings
 
$fPi = 3.14159265; // For Floats
 
$aNames = array(); // For Arrays
 
$oUser = new User(); // For Objects
 
$bIsMember = false; // For Booleans

これは自分は真反対。というより、変数名に意味を持たせることは非常に同意するけど、ハンガリアン記法について反対ということ。「PHPは型がゆるい言語だからハンガリアン記法で型を宣言しよう」っていう主張は前からあるけど、型がゆるい言語だからこそハンガリアン記法は避けるべきだと思う。これが1つ目の理由です。

$sTags = 'php,perl,ruby,python';
$sTags = explode(',', $sTags);

例えば、カンマで区切られた文字列をexplodeで配列に分解したら、あっというまに型が変わってしまう。ここでもうハンガリアン記法は意味のないものになってしまう。それなら、配列用に別の変数名をつければいいじゃないかという反論があるかもしれない。しかし、それは一方で無駄な一時変数を増やすことでもある。

もうひとつの理由は、ハンガリアン記法は必ずしもプログラマ同士で共通化されていないという点。ハンガリアン記法自体、いくつかの流儀があって流儀が異なる者同士が同じコードを触った際に混乱につながる。混乱させるような記法なら最初から避けるべきだと思う。

私が思うに、「変数名に意味を持たせよ」で重要なことは、つきつめれば次の1点だと思う。

  • 変数名を略しすぎず、できるだけ発音できるものにする。

具体的に言えば、$categoryを$ctgと略したりしないということです。

10. 三項演算子を使え

もし、シンプルなif文を書きたいなら、三項演算子を使って、ワンラインで通常のif文を書くのと同じことが書ける。

 
<?php
if ($a == 1) $b = 2;
else $b = 3;

上記のコードは、以下の三項演算子を使ったのと同じことだ。

 
<?php
$b = ($a == 1) ? 2 : 3;

「三項演算子を使え」は言い過ぎだと思う。せいぜい、「読みやすいコードになる場合に限ってのみ三項演算子を使ったほうがいい」だと考える。現に三項演算子は重ねて書かれると読みにくいし、ブレースがないぶん制御構造としての認識が低くなる。さらに言えば、単体テストのときにテストケースへの意識も低くなりかねないという危険性がある。上の例のようなコードでは、三項演算子が歓迎されると思う。しかし、三項演算子の乱用は控えるべきだとも思う。

12. ++と–の演算子を使え

整数を減少あるいは増加させる場合、インクリメントあるいはデクリメントの演算子が使える。
もし、あなたがこんなコードを書いているなら、

 
<?php
$iNumber = $iNumber  + 1;
$iNumber2 = $iNumber2 - 1;

こう変換すべきだ。

 
<?php
$iNumber++;
$iNumber2--;

もっとより良いPHPerならこうすると思う。++より、+= 1を使う。

$iNumber  += 1;
$iNumber2 -= 1;

なぜなら、万が一 $iNumber に 'a' が代入されていたら、どうなるだろうか?その際の動作は、期待したものだろうか?

$iNumber = 'a';
$iNumber ++;
var_dump($iNumber); // string(1) "b"

もちろん$iNumberは、整数型にキャストする、ないしはバリデーションしておくべきだ。それでも、ちょっと工夫するだけで防衛プログラミングができるならそれに越したことはないと思う。





Author

Submenu

Recent Entries

XOOPS Cube Dev Ring

氷川 XOOPS Module 開発室

Recent Comments

Recent Trackbacks

facebookいいね