defined()はクラス定数もチェックできる
define()で定義する(グローバルな)定数が、定義されているか否かをチェックするdefined()関数
http://php.net/manual/ja/function.defined.php
ですが、この関数、実はクラス定数に対しても使えます。
(Version 5.4.17にて確認)
<?php class Hoge { const FUGA = 1; } var_dump('Hoge::FUGA'); // 出力bool(true) var_dump('Hoge::BAR'); // 出力bool(false)
定数の値を返す constant()関数
http://www.php.net/manual/ja/function.constant.php
には、
この関数は クラス定数に対しても動作します。
って明記されてるんですけどね。
なぜがdefined()の方には書いてない。
DateTimeのインスタンス達はmax(), min(), sort()できる
PHPerのみなさん、DateTimeクラス使ってますか?
意外とまだPHP4由来の関数(date()、strtotime()など)使っているケースもよくみかけるのですが
DateTimeクラス使えばいろいろスマートにプログラム書けるし、例外処理もできる*1んで
これからはこっちを使うようにしましょうよ、と思うのです*2。
で、これも意外とまだ知らない人多いようなので触れておくと
PHP5.2.2以降、DateTimeクラスのインスタンス同士を、比較演算子で直接比較できるようになりました。
http://www.php.net/manual/ja/datetime.diff.php
の例2のところに記載されています。
<?php $date1 = new DateTime("now"); $date2 = new DateTime("tomorrow"); var_dump($date1 == $date2); //false var_dump($date1 < $date2); //true var_dump($date1 > $date2); //false
日付の比較って、Webアプリケーション書いていると大抵必要になるシチュエーションだと思うので
この書き方ができることを知ってると、読みやすくスマートにコード書けるんじゃないかなと。
で、さらにこの「DateTimeクラスのインスタンス同士は比較演算子で直接比較できる」ことの帰結として
2つのインスタンスを比較できるということは、もっと多数のインスタンスがあれば、それらも比較できる。
つまり、max(), min(), sort() といった関数にDateTimeクラスのインスタンス(の配列)を渡すこともできます。
<?php $date1 = new DateTime; $date2 = new DateTime('Yesterday'); $date3 = new DateTime('Tomorrow'); $dateList = array($date1, $date2, $date3); //max() var_dump(max($date1, $date2, $date3)); var_dump(max($dateList)); //min() var_dump(min($date1, $date2, $date3)); var_dump(min($dateList)); //sort() sort($dateList); var_dump($dateList);
実行結果
object(DateTime)#3 (3) { ["date"]=> string(19) "2013-10-22 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } object(DateTime)#3 (3) { ["date"]=> string(19) "2013-10-22 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } object(DateTime)#2 (3) { ["date"]=> string(19) "2013-10-20 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } object(DateTime)#2 (3) { ["date"]=> string(19) "2013-10-20 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } array(3) { [0]=> object(DateTime)#2 (3) { ["date"]=> string(19) "2013-10-20 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } [1]=> object(DateTime)#1 (3) { ["date"]=> string(19) "2013-10-21 01:01:03" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } [2]=> object(DateTime)#3 (3) { ["date"]=> string(19) "2013-10-22 00:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } }
max()、min()とかは、例えばいくつかの日時候補があって、そのうちもっとも現在日時に近いものを採用したい、というケースで使えます。
ソシャゲでいうと、レイドボスの討伐期限が普通はエンカウントして2時間後なんだけど、イベント終了やメンテナンスがある場合は
その日時と比較して、もっとも現在日時に近いものを討伐期限として採用する、みたいなことができます。
xdebug の各種設定がどのコンテキストで変更できるかを調べる
NetBeans + xdebug + Chromeのxdebug拡張という環境がなかなか良い感じです。
この環境さえ構築できれば、もうvar_dump()要らず\(^o^)/
で、この環境を作る上でひとつハマったことがあったのでメモ。
最初、xdebugでリモートデバッグできるようにini_set()でスクリプトから設定変更*1してみたものの、うんともすんとも言わず。
で、そういえば、xdebugの設定って、それぞれどのコンテキストで指定できるんだっけ?と思い当たり。
公式サイトのドキュメントを当たってみるも、それらしき記述は無し。。。
結構詳しく書いてあるんですけどね、xdebugのドキュメント。英語だけど。
で、結局行き着いたのが、ソースコード。
おおむね、PHP_INI_ALL が多いんですが、いくつかの設定値は異なるものがあります。
PHP_INI_SYSTEM|PHP_INI_PERDIR のもの
- xdebug.trace_enable_trigger
- xdebug.overload_var_dump
- xdebug.profiler_enable
- xdebug.profiler_output_dir
- xdebug.profiler_output_name
- xdebug.profiler_enable_trigger
- xdebug.profiler_append
- xdebug.profiler_aggregate
- xdebug.remote_enable
PHP_INI_SYSTEM のもの
リモートデバッグで僕がはまったのはxdebug.remote_enable でした。
まあ、実現する機能のことを考えたらそりゃそうか、という結論だったわけですが。
*1:正確には、Zend_Applicationを使ってるので、設定自体はZendのiniファイルに書いてますが、実質ini_set()で定義してるのと同じ
ArrayAccessを実装したインスタンスとarray_key_exists()
自分メモ。
ArrayAccessを実装したクラスのインスタンスに
<?php //$objは instanceof ArrayAccess array_key_exists('key', $obj);
は使えない。
<?php isset($obj['key']);
を使うこと。
おそらく、配列のキーと、オブジェクトのプロパティの違いで動かないんだろうね。
ちょっと不便な気もするけど。
まぁarray_key_exists()自体、isset()で代用効くのであまり使わないからいいか。
array_key_exists()だけじゃなく、他の「キーを引数に指定する」ような配列関数もだめかもね。試してないけど。
ちなみになんでこれに気づいたかというと、DoctrineのDoctrine_Recordクラスのインスタンスを扱っていて。
Doctrine_Recordのインスタンスのまま使用するときと、連想配列にhydrateする時を
透過的に扱おうとして、array_key_exists()の結果が違ってびびったのでありました。
SOAP関数を使う時の注意点
ハマったので備忘録。
PHPの標準SOAPライブラリを使う場合、
接続先や利用できるメソッドなどを記述してあるWSDLファイルをキャッシュする機能がある。
http://jp.php.net/manual/ja/soap.configuration.php
この「soap.wsdl_cache_enabled」ディレクティブの値が、デフォルトOnなので、開発時には注意が必要。
WSDLファイルを切り替える時にキャッシュが効いていて、古いWSDLファイルを読み込んだままになっている可能性がある。
ちなみにこのキャッシュファイルの格納先は
「soap.wsdl_cache_dir」(デフォルトは /tmp)
キャッシュ有効期限は「soap.wsdl_cache_ttl」(デフォルトは86400秒=1日)
詳しくは大人の事情で書きませんが、auの携帯公式サイト案件で
「まとめてなんちゃら」のところで
WSDLがキャッシュ読んでるのを気づかずに、動かねぇ、動かねぇ、って頭を悩ませておりました。。。
本番稼働入ったら、キャッシュOnの方がもちろんいいんでしょうけどね。
開発中とか、移行期間中とかはOffの方が変なことに悩まされずに済みますよ、というお話。
SELECT文で rowCount() が使えるか?
PDOではなく、各DB固有の関数だと、「SELECT結果の件数」を返す関数として、
- MySQLなら、mysql_num_rows()
- MySQL拡張(MySQLi)なら、mysqli_stmt::num_rows()メソッドまたはmysqli_stmt_num_rows()関数
- PostgreSQLなら、pg_num_rows()関数
があるんですが、PDOの場合、それっぽいメソッドが rowCount()くらいしかないのですが。PHPマニュアルによると。
PDOStatement::rowCount() は 相当する PDOStatement オブジェクトによって実行された 直近の DELETE, INSERT, UPDATE 文によって作用した行数を返します。 関連する PDOStatement によって実行された直近の SQL ステートメントが SELECT 文の場合、いくつかのデータベースは文によって返された 行数を返すかも知れません。しかしながら、 この振る舞いは全てのデータベースで保証されていません。 さまざまな場所で使用するアプリケーションでは、 これに頼ってはいけません。
つまり、このメソッドは、**_num_rows()のPDO版ではなく、**_affected_rows()のPDO版って位置づけなんですね。。。
で、その代替策として
ほとんどのデータベースでは、PDOStatement::rowCount() は SELECT 文によって作用した行数を返しません。代わりに、 PDO::query() を使って 意図する SELECT 文として同様の述部を持つ SELECT COUNT(*) 文を発行し、PDOStatement::fetchColumn() を使って返される行数を取得することができます。
と記述があるんですが・・・
いやね、それはわかってるの。
でも、今まで1回で問い合わせ済んでたのが、PDOにするから2回クエリ投げろってのはあんまりでないかい?
ってことで、最初の引用にある「SELECT 文の場合、いくつかのデータベースは文によって返された 行数を返すかも知れません。」てことなので、具体的によく使うMySQLやPostgreSQLはどうなのよ?ってとこを調べてみました。
ちなみに実験に使用したコードはこんな感じ。
<?php // DB_CONNECT_STR 定数には、それぞれのDBで必要な接続情報が定義されているとして読んで下さい。 try { $db = new PDO(DB_CONNECT_STR); $stmt = $db->query('SELECT * FROM hoge_table '); var_dump($stmt->rowCount()); } catch (PDOException $e){ die($e->getMessage()); }
hoge_tableには、テストデータを3件ほど突っ込みました。
MySQLの場合:その2
実行結果:
int(3)
あれ?今度は返ってくるぞ?
プログラムは(DB接続文字列以外は)変えてないのに?
使用したバージョンは、PHP5.2.6、MySQL5.0。
さて、この差異はなんなのか?
google先生にいろんな角度(単語)からあ〜でもない、こ〜でもないと聞きまくり、どうやらそれっぽい情報を発見。
PHP5.2.1のChangeLog。
- PDO_MySQL Extension Improvements (Ilia)
- Enabled buffered queries by default.
そういえば、(PDOでない)MySQL関数の mysql_num_rows()関数のヘルプにも
注意: mysql_unbuffered_query() を使用した場合、 結果セットのすべての行を取得するまで mysql_num_rows() は正しい値を返しません。
ってある。
どうやら、「バッファードクエリ」がポイントのようだ。
MySQLの場合:その3
で、よくよく調べてみると、PDOのMySQL用のオプション設定で、このバッファードクエリを使用するか否かを設定できるらしい。
そこで、先の実験プログラムをちょっとだけ修正。
<?php // DB_CONNECT_STR 定数には、それぞれのDBで必要な接続情報が定義されているとして読んで下さい。 try { $db = new PDO(DB_CONNECT_STR); $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); //←この行追加 $stmt = $db->query('SELECT * FROM hoge_table '); var_dump($stmt->rowCount()); } catch (PDOException $e){ die($e->getMessage()); }
追加した行にある、「PDO::MYSQL_ATTR_USE_BUFFERED_QUERY」は詳しくはこちらを:
http://www.php.net/manual/ja/ref.pdo-mysql.php
で、先ほどのPHPバージョン違いの2つの環境(PHP5.1.6/PHP5.2.6)で試したところ・・・
int(3)
よし!両方返ってきた!
結論
2つのDBでしか試してませんが、
- PostgreSQLならrowCount()メソッドでSELECT文の結果件数が取得できる
- MySQLは、PHP5.2.1以降なら*1、rowCount()メソッドでSELECT文の結果件数が取得できる
- それ以前の場合は、PDO::MYSQL_ATTR_USE_BUFFERED_QUERY を true にセットすれば取得可能
ということに。
他のDBは手元に環境がないので、わかりません。
あとは、「DBによっては動かないかもしれない」という、このメソッドを使うかどうかは、そのプロジェクト毎の判断でいいのではないでしょうか。
マニュアルには「どのDBでも動作しなければならない製品の場合は、このメソッドではなく、SELECT count(*)を使え」ってあるんですが、使用するDBが限定できるのであれば、使うという選択もありな気がします。プロジェクト責任者の判断で。
rowCount()使った方が、確実にクエリ投げる回数減りますし、ソースコード的にも読みやすいし。
新たな疑問
PDO::MYSQL_ATTR_USE_BUFFERED_QUERYについて、PHPマニュアルには
PDOStatement でこの属性を TRUE に設定すると、 MySQL ドライバはバッファ版の MySQL API を使用します。 移植性の高いコードを書くには、代わりに PDOStatement::fetchAll() を使用すべきです。
とあるのだが、これって実際のところどうなんだろう?
「移植性の高いコードを書く」ことと「PDOStatement::fetchAll() を使用する」ことが自分の中で繋がらないのだが・・・?
職場の同僚がこの記述を見て、
「PDOはPDOStatement::fetch()は非推奨で、PDOStatement::fetchAll()を使わなければいけない」
と思ってたようなんだけど・・・そうではない気がするんだけど・・・
もし万一そうだったら、PDOStatement::fetch()のページに「この関数は非推奨です」って書いてあるべきだよなぁ。。。
この辺がまだもやもやしています。。。
updated_at「だけ」を更新したい場合
地味だけどハマったのでメモ。
symfonyで、というより正確にはPropelで、
updated_atカラムを含むモデルを扱う場合。
この名称のカラムを作っておけば、他に特になにも設定しなくても、レコードを更新かけた時に、更新日時を自動的に保存してくれるので、便利なのですが。
今回ハマったのが、「更新日時【だけ】を更新したい」というケース。
もうちょっと正確には、別のカラムのデータも更新かけるんだけど、
それがたまたま更新前と同じデータになる時があって、
その時でも、更新日時は最新にしたいと。
具体的には、こんな感じ:
<?php $foo = mt_rand(1,4); $hoge = HogePeer::retrieveByPk(1); $hoge->setFoo($foo); $hoge->save();
1行目で、1〜4の間で乱数を取得。
そうすると、当然以前保存してた値と同じになることがあるんだけど
その場合でも更新日時だけはアップデートしたいんですわ。
ところがこの場合、Propelは、まぁ賢いっちゃあ賢いんだけど
「オブジェクトのプロパティが何も変わってないんで、UPDATE発行必要なし」と判断しちゃうようで。
更新日時が変わらない・・・というかUPDATE文自体発行しない。。。
で、どうしようかなぁ、DELETEしてINSERTしてもいいけど、なんか格好悪いしなぁ・・・と思い、Propelが生成するBaseHogeクラスのソースを眺め。
こうするのがとりあえずよさそうだ。
<?php $foo = mt_rand(1,4); $hoge = HogePeer::retrieveByPk(1); $hoge->setFoo($foo); $hoge->setUpdatedAt('now'); //明示的にupdated_atカラムを更新 $hoge->save();
updated_atカラムを明示的にsetしたらうまくいった。
setUpdatedAt()メソッドの引数は、strtotime()関数が解釈できる文字列ならOK。
ほんとは、プロパティが変わってなくても、強制的にUPDATE文を発行させる方法がほかにあればいいんだけど、
BaseObjectクラスまで遡ってみたけど、使えそうなメソッドが見あたらなかったので、これがいいのかな。。。うん。