page_adsence

2011年9月28日水曜日

MySQLって正規表現使えたんだ・・・。

今日初めて知った。
MySQLって正規表現使えるんだ…。
でもマルチバイトはうまくいかない場合があるみたい。
mregexpっていうユーザー定義関数があるらしく、それをインストールするとどの文字コードでもうまくいくって話。

でも、この記事とかこの記事とか見るとバイナリで比較するような感じで検索すればいけんじゃねって感じです。

まぁ時間がある時に試してみようと思います。

DoctrineのSELECT文に関して

DoctrineでSELECT文を上書きする際にハマったのでメモ。

対象テーブルは2テーブル
・account(アカウント一覧)
・friends(友達一覧)
・check(チェックしたユーザー一覧)

以下のようなSQL文をDoctrineから生成しようとしたがうまくいかなかった。
SELECT *, account_id IN (1,2,3,4) AS is_friend FROM friends ORDER BY is_friend DESC;

やりたいことは以下の通り。
checkテーブルのデータを各種条件を指定して取得し、
そのデータを友達かどうかをキーにしてソートする。

最初に書いていたのはこんな感じ。

$query = Doctrine_Core::getTable('Check')
           ->select('*, account_id IN (1,2,3,4) as is_friend')
           ->where('delete_flg = ?', 0)
           ->limit(10)
           ->offset(0)
           ->orderBy('is_friend DESC');

SELECT内に書かれている「account_id IN (1,2,3,4)」の部分がうまく解釈されずに、
本来であればいる「1」かいないか「0」の2種類の結果がis_friendに格納されて返ってくるはずなのだが、
is_friendにはcheckテーブルに含まれるaccount_idが入って返ってきてしまっていた。

原因はどうやらparseSelectの中でやっている処理っぽい。
さすがにこの部分を書き換えるわけにはいかないので、
どうにかできないかと色々と試してはみたのだが、結局わからず終い。

どうしたものかと思っていたのだが、
だったら別のSQLの書き方で同じ結果を取得できるようにすればいいんじゃないかと思った。

JOINとかしてやる方法はパフォーマンス的に悪すぎるので却下。
なんとかSQL一発で取れる方法を探していたらありましたよ。

「CASE」

これ使えばいけるかもと思って、早速MySQLでコマンド叩いてみた。
SELECT *, CASE WHEN account_id IN (1,2,3,4) THEN 1 END AS is_friend FROM friends ORDER BY is_friend DESC;

いけた!
全く同じ結果ではないですが、is_friendに1かNULLが入る感じの結果が返ってきました。
CASEもう一個書けばNULLのとこを0返すようにできますが、必要ないと思ったのでやっていません。

で、今度はこれをちゃんとDoctrineが作ってくれるのかってとこです。

$query = Doctrine_Core::getTable('Check')
           ->select('*, CASE WHEN account_id IN (1,2,3,4) THEN 1 END AS is_friend')
           ->where('delete_flg = ?', 0)
           ->limit(10)
           ->offset(0)
           ->orderBy('is_friend DESC');

これを実行してみると、SQLのSyntaxエラーに…。
ググってみたら、意外とさっくり出てきた。
どうやらCASE~ENDの前後に括弧をつけるといけるらしい。
早速試してみた。

$query = Doctrine_Core::getTable('Check')
           ->select('*, (CASE WHEN account_id IN (1,2,3,4) THEN 1 END) AS is_friend')
           ->where('delete_flg = ?', 0)
           ->limit(10)
           ->offset(0)
           ->orderBy('is_friend DESC');

出来た!!!

エラーもなく、希望通りのレスポンスが返ってきました。
やはり複雑なSQLになるとDoctrineを使うのも一苦労ですね…。
Doctrineのキャッシュを使いたいがためにDoctrineで頑張ってやってましたけど、
それに見合うかどうかも判断する必要がありますね。
毎回こんな感じでハマってると時間がいくらあっても足りない…。

フレームワークでデバッグする際に使える関数

PHPでデバッグする際によく使う関数にvar_dumpやprint_r等がありますが、
symfony等のフレームワークを使用している場合、こういったダンプ関数を使ってブラウザ上に情報を出力しようとすると、出力する情報量が多すぎてブラウザがクラッシュしてしまうことが多々あります。
こういった時に使える便利な関数がいくつかあったので、メモしておくことにします。
※但しバックトレースに関してはsymfonyで試しにやってみましたが、駄目でした・・・。


debug_backtrace
バックトレースを生成する
debug_print_backtrace
バックトレースを生成する
get_included_files
include または require で読み込まれたファイルの名前を配列として返す
get_defined_constants
すべての定数の名前とその値を連想配列として返す
get_class
オブジェクトのクラス名を返す
get_class_method
クラスメソッドの名前を取得する
get_class_vars
クラスのデフォルトプロパティを取得する
get_object_vars
指定したオブジェクトのプロパティを取得する
get_parent_class
オブジェクトの親クラスの名前を取得する

2011年9月21日水曜日

symfony2のインストール方法

今頃ですが、symfony2のインストール方法です。

今回は以下の環境にインストールをしていきます。
CentOS 5.5
Apache 2.2
PHP 5.3.6
MySQL 5.5.12

Symfonyの設置先ディレクトリは以下の通りです。
/path/to/dir/

まず設置先ディレクトリへ移動します。
$ cd /path/to/dir/

で、ここからソースをダウンロードしてきます。
「Symfony Standard」か「Symfony Standard without vendors」のいずれかを選択してダウンロードします。
通常は「Symfony Standard」をダウンロードしてくることになるかと思いますが、
gitをインストールしている環境では「Symfony Standard without vendors」を選択しても良いみたいです。
まぁ結局ダウンロードすることには変わりないので、お好きな方を選んで下さい。
「Symfony Standard without vendors」を選んだ場合には、ダウンロード後にgit経由でvendor系のファイルをインストールすることになります。

ダウンロードが完了後、落としてきたファイルを解凍します。
$ tar -xzpf Symfony_Standard_Vendors_2.0.1.tgz

解凍が完了すると、Symfonyディレクトリが作成され、その中にいくつかファイルやディレクトリがあります。

で、その解凍して出てきたSymfonyディレクトリへ移動。
$ cd Symfony

Symfonyがきちんと使用できる環境なのかチェックしてくれるファイルがありますので、
それを実行します。
$ php app/check.php
********************************
*                              *
*  Symfony requirements check  *
*                              *
********************************

php.ini used by PHP: /etc/php.ini

** WARNING **
*  The PHP CLI can use a different php.ini file
*  than the one used with your web server.
*  If this is the case, please ALSO launch this
*  utility from your web server.
** WARNING **

** Mandatory requirements **

  OK        Checking that PHP version is at least 5.3.2 (5.3.6 installed)
  OK        Checking that the "date.timezone" setting is set
  OK        Checking that app/cache/ directory is writable
  OK        Checking that the app/logs/ directory is writable
  OK        Checking that the json_encode() is available
  OK        Checking that the SQLite3 or PDO_SQLite extension is available
  OK        Checking that the session_start() is available
  OK        Checking that the ctype_alpha() is available
  OK        Checking that the APC version is at least 3.0.17

** Optional checks **

  OK        Checking that the PHP-XML module is installed
  OK        Checking that the libxml version is at least 2.6.21
  OK        Checking that the token_get_all() function is available
  OK        Checking that the mb_strlen() function is available
  OK        Checking that the iconv() function is available
  OK        Checking that the utf8_decode() is available


[[WARNING]] Checking that the posix_isatty() is available: FAILED
            *** Install and enable the php_posix extension (used to colorized the CLI output) ***


[[WARNING]] Checking that the intl extension is available: FAILED
            *** Install and enable the intl extension (used for validators) ***
  OK        Checking that a PHP accelerator is installed
  OK        Checking that php.ini has short_open_tag set to off
  OK        Checking that php.ini has magic_quotes_gpc set to off
  OK        Checking that php.ini has register_globals set to off
  OK        Checking that php.ini has session.auto_start set to off

** Optional checks (Doctrine) **

  OK        Checking that PDO is installed
  OK        Checking that PDO has some drivers installed: mysql, sqlite

今回の僕の環境では特になにもエラーが出なかったので何もしていませんが、
エラーが出た場合は不足しているものをインストールしてあげましょう。

で、この後にwithout vendorsを選んだ方はvendorライブラリをインストールします。
$ php bin/vendors install

以上で設置作業は完了です。

ここからはバーチャルホストの設定ですが割愛します。
ドキュメントルートは以下のディレクトリに設定します。
/path/to/dir/Symfony/web

webディレクトリ直下にconfig.phpというファイルがありますので、
ブラウザからここにアクセスしてみます。
但し、localhostからしかアクセスできるようになっていないので、適宜修正して下さい。
僕の場合は自分のIPを追加しておきました。

設定が成功していると「Symfony Configuration」というタイトルのページが表示されます。
以上でSymfonyの設置は完了です。

次回はSymfony2の初期設定の手順を書いていきたいと思います。

2011年9月13日火曜日

memcacheのインストール

memcacheが標準っぽくなってきたので、自分のVMWare上にも入れてみた。
まずyumからインストールする
必要であればリポジトリを指定してやる。
yum install memcached

以下のファイルを作成し、中身を書く。
# vi /etc/php.d/memcached.ini

extension=memcache.so

で、apache再起動。

きちんと読み込まれているか確認
php -i | grep memcache

/etc/php.d/memcached.ini,
memcache
memcache support => enabled
memcache.allow_failover => 1 => 1
memcache.chunk_size => 8192 => 8192
memcache.default_port => 11211 => 11211
memcache.default_timeout_ms => 1000 => 1000
memcache.hash_function => crc32 => crc32
memcache.hash_strategy => standard => standard
memcache.max_failover_attempts => 20 => 20
Registered save handlers => files user memcache

で、起動させる。
memcached -d -m 128 -l 127.0.0.1 -p 11211

ちなみにオプションはこんな感じ
オプション 説明 デフォルト値
-l <ip_addr> memcachedがListenするIPアドレス。 セキュリティを考慮するときちんと指定したほうがよい。 INDRR_ANY
-d デーモンとして起動
-s <file> Unixソケットへのパス
-P <filename> PIDファイルの指定。デーモンとして動作した場合のみ有効。
-u <username> memcachedを起動するユーザーを指定。root権限で実行した場合のみ有効。
-p <num> ListenするTCPポート番号。 11211
-U <num> ListenするUDPポート番号。 0, Off
-m <num> キャッシュとして利用するメモリの最大容量。単位はMB。 64
-c <num> 最大同時接続数。 1024
-M メモリを使い果たしたとき勝手にキャッシュを削除しないでエラーを返す
-n <bytes> キャッシュ1件(key+value+flags)に割り当てる最小サイズ 単位はバイト。 48
-v errorとwarningを出力
-vv -vに加えてクライアントコマンドとレスポンスを出力
-i ライセンス表示
-h バージョンとヘルプの表示
-r コアファイルのサイズ制限を最大化する。
-k 巨大なキャッシュを扱うときにキケンなオプション。
-f <factor> チャンクサイズの増加係数 1.25
-b 管理されたインスタンスの起動

起動できているか確認してみる。
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
set foo 0 0 3     (保存コマンド)
bar               (データ)
STORED            (結果)
get foo           (取得コマンド)
VALUE foo 0 3     (データ)
bar               (データ)

とりあえずこんな感じです。

2011年9月12日月曜日

myPageStateとmemcacheの相性の悪さに関して

前の会社の人が作ったライブラリでmyPageState.class.phpを今回の案件で使用してみたのですが、
memcacheとの相性が悪いのか、symfony1.4との相性が悪いのかよくわかりませんが、セッションから値が取れないという不具合が発生しました。

前の会社でもsymfony(ver.1.2ですが)を使ってフォームを作る時には大体使っていたのですが、
こういったことは起こったことがありませんでした。

なので消去法でいくと一番怪しいのはmemcacheかなと。

最初はmemcacheにオブジェクトを突っ込んでた(memcacheが勝手にserializeする)んですが、
その場合には3割位の確率でmyPageState経由で保存したセッション情報の取得に失敗してしまうという現象が起きました。
仕方ないのでオブジェクトを配列にして保存するように修正してみた。
一応発生しにくくはなったものの、根本的な原因がわかってないので、
発生しないという状態までは持っていけませんでした。
しかし一体何が原因なんだろう…。
不思議です。

2011年9月7日水曜日

symfonyのfilterに関して

symfonyを久々に使って、filterを使うことになった。
symfonyの中核を担っているらしいフィルタの機能ですが、今回初めて使ってみた。
フィルタは必ず通るので、全部に対して通したい場合は比較的楽なんですが、
ユーザーのステータスを見て、どこかにリダイレクトとかさせたりする場合は、
下手をすると無限ループに陥るので注意が必要。
一度無限ループに陥ると、どういう状態になっているのか分からなくなってしまうので、
きちんと状態をログファイルに出力するようにしたほうがよい。

filterの設置場所は以下の通り。
project_root/app/アプリ名/lib/filter/~Filter.class.php

基本的な書き方としては以下の通り。
<?php
class myTestFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // 入力時に実行されるfilter
    if ($this->isFirstCall())
    {
      // contextとかはこうやって取れる
      $context    = $this->getContext();
      $user       = $context->getUser();
      $controller = $context->getController();
      $request    = $context->getRequest();

    }

    $filterChain->execute();

    // 出力時に実行されるfilter
  }
}

ちなみにログの出力方法はこんな感じ。
$logger = sfContext::getInstance()->getLogger();
$logger->info('ログメッセージ');

Fillder2を使ってみた

前々から使ってみようと思って、英語版しかないためずっと日本語化パッチがでるのを待ってたんですが、いよいよ待ち切れずに使ってみた。

マジで便利。

最高です。

いままではProxomitronとSwitchProxyを使ってローカルリダイレクトさせてたんですけど、
Fiddlerだとこれ1つでいける。
やばいです。

まだ全然使いこんでないので、ただリクエストがあったファイルをローカルのファイルと差し替えるみたいな感じしかやっていませんが、色々と便利機能が他にもありそうです。

とりあえず使い方に関しては以下の通り。
http://d.hatena.ne.jp/seiunsky/20100516/1274022823

画面遷移なしで画像のアップロードをするときの罠

Ajaxを使って画面遷移なしで画像をアップロードするってことになったらしく、
調べてみたら別にできないことではないし、やってる人はいっぱいいるみたいでした。
で、早速やってみたら以外にサクッといけた。

Javascript単体でファイルのアップロードはできないので、サーバ側にデータをPOSTする必要がある。
だけど普通にPOSTすると画面遷移が発生してしまう。
それを回避するためにiframeを使用する。
普通にformでpostする際には(1)のように記述するが、
画面遷移なしでやる場合には(2)のように記述する必要がある。

(1)通常
<form action="test" method="post">
form elements…
</form>

(2)
<iframe name="iframeName"></iframe>
<form action="tset" method="post" target="iframeName">
form elements…
</form>

このようにすると、POSTされたデータ自体はiframeに飛んでいき、
iframe内で画面遷移が発生するが、現在ページを表示している方には影響がない。
また、iframe自体を画面外に飛ばしておくことで、ユーザーには遷移が行われていることを見られずに
画像をアップロードすることが可能になる。

但し何点が注意点がある。
1つはformのPOST先であるiframeをdisplay:noneにするような形で対応すると、
Safariでtargetに指定したiframeではなく、新しいウィンドウに対してPOSTされてしまうので注意が必要である。

2つ目はサーバ側からクライアントに対してデータを返す際のContent-Typeだ。
通常サーバ側でjsonを返す場合のContent-Typeは「application/json」だが、
「application/json」で返すと謎のnoscriptタグにメッセージが付加されて返ってきてしまい、
jsonオブジェクトのパースに失敗するが、「text/javascript」にしてみたらいけた。
しかし、jquery.uploadのライブラリを使ってアップしようとした場合には「text/html」で返すように指定してねって記述があるので、実は「text/html」が一番いいのかも知れない。
今回は色々と切羽詰まっていて、複数ブラウザで確認していないのでなんとも言えませんが、
ライブラリが使っているContent-Typeが安全そう。

まぁ自分で実装する機会があればぜひやってみよう。

2011年9月2日金曜日

MySQLで1時間毎にまとめた情報を取得したい場合

SUBSTRINGで時間までをとってやり、GROUP BYをしてやればOK

以外にさっくりいった。

2011年9月1日木曜日

eclipse

ファイル→新規→PHPプロジェクト

ワークスペース内に新規プロジェクトを作成にチェックする。
そうすると入力したプロジェクト名がワークスペース以下に作成される。

次へを押して、ソースのフォルダを追加を押すと、ビルドしたいディレクトリを指定できる。
ここでcacheやlogを抜いておくとよい。
ビルドすると、ソースの問題点やエラーがでる。

symfonyのprod環境のログ出力に関して

symfonyのprod環境に関してはプロジェクトルート以下に作成されているlogディレクトリ以下に、
ログファイルが作成されないようになっている。
これを出力するように変更するには、各アプリケーションディレクトリの下にあるコンフィグファイル内にあるfactories.ymlを修正する必要がある。

prod:
#  logger:
#    class:   sfNoLogger
#    param:
#      level:   err
#      loggers: ~


この部分を上記のようにコメントアウトすることで、ログファイルが生成されるようになる。

ちなみに、 自分でアプリケーションログに吐きだしたい場合は以下のように書くことで出力が可能になる。

$logger = sfContext::getInstance()->getLogger();
$logger->info('出力したいメッセージ');