トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

PHPプログラミング

はまりどころ?
特に断りがなければPHP5での記述ということで。

emptyとissetとis_null

if($var)if(empty($var))if(isset($var))if(is_null($var))
$var=1TRUEFALSETRUEFALSE
$var="";FALSETRUETRUEFALSE
$var="0";FALSETRUETRUEFALSE
$var=0;FALSETRUETRUEFALSE
$var=NULL;FALSETRUEFALSETRUE
$varFALSETRUEFALSETRUE
$var=array()FALSETRUETRUEFALSE
$var=array(1)TRUEFALSETRUEFALSE

値(中身)があるかないかでif($var)とif(empty($var))は正反対、
変数(入れ物)があるかないかでif(isset($var))とif(is_null($var))は正反対になる。

PHP Manual:PHP 型の比較表
http://www.php.net/manual/ja/types.comparisons.php
ゆどうふの湯豆腐ろぐ:$_POST、$_GETのよくある間違い
http://d.hatena.ne.jp/yudoufu1/20060628

ini_setとdisplay_errors

マスターのphp.iniでdisplay_errors = Offのとき、エラーが発生してもブラウザのほうにはエラー表示されない。
開発中(テスト中)はエラーを表示させるために、コード内で

ini_set('display_errors', '1');

などとすることがあるが、これは致命的なエラーの場合はエラー表示しないようになっている。
まぁ、場合によってはそのコードを書いたphpファイル自体がfatal errorやsyntax errorなんてこともあるわけだから、ini_setするより先に処理が停止していることもあるわけで。

ということで、変にはまりたくなければphp.iniや.htaccessで設定したほうが無難そう。

PHP Manual 実行時設定
http://www.php.net/manual/ja/errorfunc.configuration.php#ini.display-errors

error_reporting

PHP 5では、E_ALLにE_STRICTは含まれないというところ。

error_reporting = -1

と指定した場合はE_STRICTも含まれるみたい。

PHP Manual:error_reporting
http://www.php.net/manual/ja/function.error-reporting.php

PHPタグと短縮形

htmlにphpコードを埋め込む際に

<?php echo 'Hello World!!' ?>

としたりするが、php.iniのshort_open_tagがonならば

<? echo 'Hello World!!' ?>

とすることができ、さらに<? echoのショートカットである

<?= 'Hello World!!' ?>

が利用できる。

php.ini ディレクティブに関する説明
http://www.php.net/manual/ja/ini.core.php#ini.short-open-tag

include_pathとinclude

include 'foo/bar/hoge.php';

とやると、include_pathから検索される。

include './foo/bar/hoge.php';

の場合はリクエストで呼ばれたファイルから相対パスでの読み込みとなり注意が必要だが、include_pathは使われないため高速になる。

include '/home/user/foo/bar/hoge.php';

のような絶対パスならばはまることもないため、予めプログラムの基点となるディレクトリをdirname(FILE)で

$library_path = dirname(__FILE__);
include $library_path . '/foo/bar/hoge.php';

のようにして使いまわすといいかも。

include_pathとfile_exists

file_existsはinclude_pathを使わずファイルの有無を返す関数なので、下記のようにするとfile_existsはtrueなのにincludeできないという状況がありえる。

if(file_exists('foo/bar/hoge.php')){ //基点となったファイルから相対パスでhoge.phpを探す
    include 'foo/bar/hoge.php';      //include_pathから相対パスでhoge.phpを探す
}

これを成功させるにはinclude_pathに .(カレントディレクトリ)が含まれていなければならず、普通はphp.iniの初期設定でそのようになっているが、.htaccess等を使ってinclude_pathを再設定(上書き)する際は注意が必要。

大文字小文字

変数名の大文字小文字は区別される。

$hoge = 10;
$Hoge = 20;
echo $hoge; // 10
edho $Hoge; // 20

配列の添え字も同様。

$foo = array();
$foo['bar'] = 10;
$foo['Bar'] = 20;
echo $foo['bar']; // 10
echo $foo['Bar']; // 20

関数名の大文字小文字は区別されない。

function hoge() {
    return 10;
}

echo hoge(); // 10
echo Hoge(); // 10

クラスのときも関数と同様。

class foo {
    static function bar() {
        return 10;
    }
}

echo foo::bar(); // 10
echo Foo::bar(); // 10
echo Foo::Bar(); // 10
$obj = new fOO();
echo $obj->bAR(); // 10

値渡しと参照渡し

 newについて

PHP4のとき

$Foo = &new Foo(); // 参照渡し
$Foo = new Foo();  // 値渡し
$Bar= $Foo;        // 値渡し

PHP5のとき

$Foo = new Foo();  // 参照渡し
$Bar = $Foo;       // 参照渡し
$Bar = clone $Foo; // 値渡し

PHP5ではnewのデフォルトが参照渡しになったため、逆に値渡し(コピー)したい場合はcloneを使う。

 引数の参照渡し

普通は受け取る側で&で受け取ればよい。

<?php
class Hoge {
    function changeValue(&$data) {
        $data = 'Hoge';
    }
}

$data = 'Huga';
$Hoge = new Hoge();
$Hoge->changeValue($data);
echo $data;  // Hogeが出力される

user_call_func_array()を使った場合は、送る側と受け取る側のどちらも&になっていなければ参照渡しとならない。

<?php
class Hoge {
    function changeValue(&$data) {
        $data = 'Hoge';
    }
}

$data = 'Huga';
call_user_func_array(array('Hoge', 'changeValue'), array(&$data));
echo $data; // Hogeが出力される

 戻り値の参照渡し

<?php
class Hoge {
    public $data = 'Huga';
    function &getValue() {  // A
        return $this->data;
    }
}

$Hoge = new Hoge();
$data = &$Hoge->getValue(); // B
$data = 'Hoge';
echo $Hoge->getValue(); // Hogeが出力される

AとBに対し、&が付いたときのみ参照渡しとなる。
いずれが欠けても値渡しとなるので注意。

session.use_trans_sid

cookieが使える環境では

session.use_cookies = 1

としていればcookieが利用できるが(ただしブラウザ側でもcookieに対応のこと)、携帯などcookieに対応していない場合に

session.use_trans_sid = 1

とする方法がある。

これを設定するとHTMLのAタグ等のリンク

<a href="/foo.php">foo</a>

に自動的に

<a href="/foo.php?PHPSESSID=XXXXX">foo</a>

session idが付加される。
ただしhttpから記述したリンクは外部パスとみなされ、session idが付加されないので注意。

<a href="http://foo.bar.com/foo.php">foo</a>

また、当然ながらsession start(auto start)しないと機能しない。

PHP Manual:セッションIDの受渡し
http://jp2.php.net/manual/ja/session.idpassing.php

mod_phpとCGIのリダイレクト

sample.phpからのリダイレクトで

header('Location: ' . $url);
exit;

とかした場合、$urlが

  1. http://foo.bar.com/hoge.html
  2. /hoge.html
  3. hoge.html

2のときの挙動がmod_phpとCGIのphpとで違ってくる。

mod_phpのときは1も2もURL http://foo.bar.com/hoge.htmlにリダイレクトされるが、CGIのときは2のときにURLはhttp://foo.bar.com/sample.phpのまま、hoge.htmlの内容が表示される。

これは内部リダイレクトと呼ばれているみたいで、Locationヘッダ自体が出力されないらしい。

OKwave:相対URLによるリダイレクト
http://okwave.jp/qa/q2428571.html

一部のブラウザやdocoomでは相対URLがNGみたいだが、絶対URLならすべての環境でOKというわけでもないらしい。

@IT:モバイルWeb開発に失敗しない鉄則
http://www.atmarkit.co.jp/fdotnet/aspnetmobile/aspnetmobile05/aspnetmobile05_02.html

CGIの挙動の件は下記が詳しい。

ぐらめぬ・ぜぷつぇんのはてダ:cgi.force_redirect って何?
http://d.hatena.ne.jp/msakamoto-sf/20080802/1217662203

split

split()は『大文字小文字を区別しない正規表現により文字列を分割し、配列に格納する』なのだが、正規表現にはまった。

$strs = split("\s", $str);

これで$strに格納された文字列中の空白文字により分割されると思ったが、実際には機能しない。
POSIXで空白文字は

$strs = split("[ \t\n\r\f\v]", $str);

というようになるようだ。

空白文字が連続していても1つの区切りとしたい場合は

$strs = split("[ \t\n\r\f\v]+", $str);

とすればいい。

正規表現を必要としない、例えば改行で分割されればよい、というのであればexplodeを使ったほうが高速。

$strs = explode("\n", $str);
正規表現 - Wikipedia
http://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE

pregとereg

共に正規表現を扱う関数群。
pregのほうが速く動作するようなので、基本はpregで。

  • eregはPOSIX拡張
  • pregはPerl互換
POSIX 正規表現関数
http://jp2.php.net/manual/ja/ref.regex.php
PCRE 関数
http://jp2.php.net/manual/ja/ref.pcre.php

動作確認に便利なサイト発見。

PHPの種:PREG ONLINE
http://www.php-seed.net/preg/

mbstring

日本語を扱う場合に設定するものだが、いまいち各設定の関係がよくわからなかったので試してみた。

mbstring.language               neutral
mbstring.internal_encoding      UTF-8

まず.languageがneutralだと.internal_encodingのUTF-8が効かない。

つづいて

mbstring.language               neutral
mbstring.internal_encoding      UTF-8
mbstring.encoding_translation	 On

だと、HTTP入力に対して自動的に.internal_encoding(内部エンコーディング)の設定に変換しようとする。

たとえば.encoding_translationが正常に機能する場合、EUC-JPのHTMLフォームからポストされてきたデータは

$_POST['hoge'];

に格納されている状態で既にEUC-JPから.internal_encoding(内部エンコーディング)の
UTF-8に変換済みとなる。

が、やはり.languageがneutralだと.internal_encodingが効かないため、結果的に.encoding_translationも機能しないことになる。

同様にプログラム内部でmb_convert_encoding()を使用し、autoを使っている場合、

$str = mb_convert_encoding($str, 'UTF-8', 'auto');

このautoも.internal_encoding(内部エンコーディング)が効かない状態だとうまく機能しない模様。
autoではなく直接、'EUC-JP, UTF-8'のように指定する分にはOKらしい。

とりあえず

 mbstring.language               Japanese

じゃないといろいろはまる。

プログラムでセットする場合は

mb_language('Japanese');

配列に対する文字エンコーディングの一括変換

string mb_convert_encoding(str, to_encofing, from_encoding)

となっており、strにarrayを渡してしまうと空っぽのarrayが返ってくる。

$str = array('古池や','蛙飛び込む','水の音');
$str = mb_convert_encoding($str, 'UTF-8', 'auto');
print_r($str); // Array()

上記のmbstring設定との絡みを考えてサンプルを作ってみる。

// mbstring.encoding_translation = Off を想定
mb_language('Japanese');
ini_set('mbstring.detect_order', 'auto');
ini_set('mbstring.http_input'  , 'pass');
ini_set('mbstring.http_output' , 'pass');
ini_set('mbstring.internal_encoding', 'UTF-8');
ini_set('mbstring.script_encoding'  , 'UTF-8');
ini_set('mbstring.substitute_character', 'none');

// ↓euc-jpのformから空白文字区切りの文字列がpostされてくるのを想定
$request = split("[ \t\n\r\f\v]+", $_POST['comment']);
$request = mb_convert_variables('UTF-8', 'EUC-JP', $request)

プロキシ経由かチェック

レンタルサーバで共有SSLを利用可能とするコードを書く場合、クライアント〜共有SSL用プロキシはHTTPSでも、共有SSL用プロキシ〜Webサイトのあるサーバは内部でHTTPとなっている場合がほとんど。

クライアント − (https) − 共有SSL用プロキシ − (http) − Webサーバー

そのためWebサーバー側で処理される$_SERVER['HTTPS']でHTTPSかどうか判定できない。

xrea、coreserverではsquidが使われていて

if (isset($_SERVER['HTTP_VIA']) && isset($_SERVER['HTTP_X_FORWARDED_FOR']))

というような感じ。

sakuraではプロキシが何か調べてないが

if (!isset($_SERVER['HTTP_VIA']) && isset($_SERVER['HTTP_X_FORWARDED_FOR']))

となる模様。
実際には変数にセットされている値もチェックしたほうがいいのかな。

eForm で画像認証を使いたい
http://modxcms.com/forums/index.php?topic=32384.75;wap2

MySQLとclientと文字化け

文字化けの仕組みは割愛して(汗)。
以前から気になっていたのはmy.cnfにある[client]の設定。

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
default-character-set=utf8

となっている場合、[client]ってなんなのだろうか?ということ。

例えばMySQLがlatin1で、上記のようにmy.cnfの設定によりutf8になっているとする。
このときPHPからMySQLに接続をかけると、[client]の設定に従いutf8で接続することになるのでは...と思ったが、事実は違ってlatin1での接続となる。
この[client]はlibmysqlclientを使ってmysqldサーバに接続をかけるときの話らしい。

で、libmysqlclientを使って接続をかけることができるのはPHPではmysqli_real_connect()で、mysqliはPHP5から利用できるMySQL 改良版拡張モジュールだ。
要するにPHP4の場合は使えない。

MySQL 改良版拡張モジュール
http://jp.php.net/manual/ja/book.mysqli.php

で、PHP5だったとして、PEAR::DBでmysqliを利用できるが、コードを読むと普段はmysql_connect()で接続し、SSLで接続する際はmysqli_real_connect()を使うようになっていた。
「mysqliを指定しているのにmysql_connect()を使ってる状態」になるので注意が必要。
多分、PEAR::MDB2ならOKだと思われる。

まとめると

PHP5 → mysqli → mysqli_real_connect() → libmysqlclient(my.cnf) → mysqld

という接続のときmy.cnfの[client]の設定が効いてくることになる。

MySQL初心者日記:libmysqlclientについて
http://nysql.g.hatena.ne.jp/py4s-tnk/20090329/1238341015

パフォーマンス改善

え〜 そうだったの? みたいなのもあって、かなり参考になった。

Selfkleptomaniac:PHPのパフォーマンス改善(1)
http://selfkleptomaniac.org/archives/43
Selfkleptomaniac:PHPのパフォーマンス改善(2)
http://selfkleptomaniac.org/archives/44
Selfkleptomaniac:PHPのパフォーマンス改善(3)
http://selfkleptomaniac.org/archives/45

高速化のための書き方

なるほど〜

Otaxa:PHP、チョイ高速化テクニック
http://otaxa.com/blog/others/php-faster-technics/?utm_source=twitterfeed&utm_medium=twitter

マジックメソッド

クラスの中で使用する、アンダースコア2つではじまる名前の特殊関数。定められたタイミングで起動する。

マジックメソッド説明
__construct()新たにオブジェクトが 生成される度に起動
__destruct()特定のオブジェクトへの全てのリファレンスが 削除された直後やオブジェクトが明示的に破棄された直後、あるいはスクリプトの終了時に起動
__call()アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動
__callStatic()アクセス不能メソッドを静的コンテキストで実行したときに起動
__get()アクセス不能プロパティからデータを読み込む際に起動
__set()アクセス不能プロパティへデータを書き込む際に起動
__isset()isset() あるいは empty() をアクセス不能プロパティに対して実行したときに起動
__unset()unset() をアクセス不能プロパティに対して実行したときに起動
__sleep()serialize()によるシリアル化の前に起動
__wakeup()unserialize()によるアンシリアル化の前に起動
__toString()クラスが文字列に変換される際に起動
__invoke()スクリプトがオブジェクトを関数としてコールしようとした際に起動
__set_state()PHP5.1.0以降でvar_export()によってエクスポートされたクラスのためにコール
__clone()オブジェクトがcloneキーワードでコピーされる際に起動
PHP Manual:マジックメソッド
http://jp2.php.net/manual/ja/language.oop5.magic.php

flush()とob_flush()とob_start()のネスト

phpの出力バッファを任意のタイミングで吐き出すのがob_flush()で、webサーバーの出力バッファはflush()らしい。

よって順序としては

<?php
for ($i=0; $i < 1000; $i++) {
    echo $i . "<br>\n";
    ob_flush();
    flush();
}

で、逐次出力されていく。

ob_start()してる間のechoとかの出力がバッファに溜まって、途中でob_flush()、flush()しなければプログラムが終了したときに出力されるが、既にob_start()しているときに更にob_start()みたいなネストすることもできる。

この場合ネストは先入れ後出しとなり、ob_end_flush()でネストの深いところから総出力するには

while(ob_get_level()) ob_end_flush();
flush();

となる。