カテゴリー別アーカイブ: PHP

複数人開発の時にデータベースの同期をうまく取る方法

最近「意外に若かったんだね」と言われるようになりました、その言葉の指すところについては考えないようにしています岡田洋一ですコンニチハ。

さて、僕はしばらくチームで開発を行っています。大人数というわけでも無く、2〜3人での小規模プロジェクトチームです。少人数制のチームではありがちで、僕以外のメンバーもプログラム〜インフラ周りも修正することが多く、データベースのテーブル構造を修正することが各自にありえます。

さてそんなときにデータベースの同期を取る方法について考えてみました。

現状はテーブルのCREATE分を出力させ、プロジェクトのwikiに書き込んでいます。

wikiは変更情報を保存しますので、ある程度のバージョン管理ができました。が、いかんせん「出力、編集、保存、確認、伝達、適用」と、修正内容をチーム内で共有するためのステップが多いのです。少人数チームでステップが多いのは良くないですね。素早さこそがウリですから。

次に「テーブルの差分になるALTERのみを書く」ということについて勘案しました。が、こちらも開発を兼ねているため、テーブルの変更となるALTER分はそこそこの数が出てしまい、すべてのALTERを記録して適用することは面倒となりました。素早さがなくなってしまいます。

 

というわけで、現在のテーブル情報を書き出しつつバージョン管理システムで共有、という手段にしました。

現在プロジェクトはSVNで管理されていたので、テーブル情報のみをファイルに書き出してコミット、それを手元へ適用という手段をとりました。ちなみにデータベースはMySQLです。

テーブル情報のみを書き出す為に必要なコードは


shell> mysqldump -h mysqlhost -u user -ppassword --no-data target_schema | sed 's/ AUTO_INCREMENT=[0-9]*//' > tables.sql

shell> svn commit tables.sql -m 'update table sql.'

となりました。mysqlのホスト名、ユーザ名、パスワード、対象スキーマは読み替えて下さい。パスワードはナシにするとプロンプトで聞いてきます。

パイプでsedに渡してAUTO_INCREMENTを消しています。

最後にこの出力をファイルに書き出して、コミットして共有します。

 

これだと差分をうまく見ることができるので便利ですね!

WordPressのテーマ/テンプレート開発の時に役に立つコード

ダイエットが終わらない岡田洋一です、コンニチハ。

さて、最近ひょんなことからWordPressのテンプレートを修正する機会がありました。WordPressはで見た目をいじるときにはテーマ(テンプレート)ファイルを編集する必要があります。

WordPressでのページ表示には色々な仕組みがあるのですが、今回僕が編集することになったのは「カスタム投稿タイプのカスタムタクソノミーのアーカイブ」だったんです。この辺りについてはテンプレートヒエラルキーなんていう聞き慣れない仕組みが関わってきています。

さて、このテンプレートヒエラルキーというのは、簡単に言ってしまえば「テンプレートを適用する順序」を決めるモノなのです。「カスタムタクソノミーのアーカイブ」であれば「taxonomy-archive.php」とかになるのですが、これがなかなかうまく見つけられなかったりします。

さてこんな時、開発を行うにあたっては、現在どのテンプレートファイルを表示しているか、というのが分かると非常に助かります。

今回はこの「どのテンプレートファイルで表示しているか」を調べるためのコードを紹介します。

下記のコードを header.php の好きな位置に埋めて下さい。

<?php if(is_user_logged_in()) : ?></span>

<!-- <?php global $template; print_r($template); ?> -->
 <?php endif; ?>

Screen Shot 2013-10-01 at 4.48.45 PM

こうすることでHTMLのヘッダにコメントとして、「表示に使用しているテンプレート」が書き込まれます。

ちなみに if文で「ログインしている状態にのみ」に表示されるようになっていますので、知らない人には表示されません!ちょっと安心ですね。

Screen Shot 2013-10-01 at 4.55.28 PM

これでどのテンプレートを使っているか迷わずに開発ができますね!

リバースプロキシを使っているとWordPressで記事の参照がループしてしまう問題について

0. WordPressで記事の参照がループしてしまう問題について

WordPressで記事ページを開いたときに無限ループしてしまう。記事ページはいわゆるsingle.php シングルというページ。ブラウザの表示は

“このウェブページにはリダイレクト ループが含まれています” (chrome)
“ページの自動転送設定が正しくありません” (FireFox)

といった感じでループがおかしいという事を告げられる。
開発ツールで転送設定を見てみると、確かに同じアドレス宛に301の “Moved Permanently” が連続して返却されている。
このままでは利用者が入って来れないので調査をしてみた。

1

1. WordPressでのリダイレクト

WordPressで転送はwp-includes/canonical.phpというファイルが担当する。
このファイルはリクエストURLを見てカスタムパーマリンク設定などに照らし合わせて最適なURLへ誘導してくれる機能を持っている。

例えばパーマリンクを投稿名にしているとき、 http://www.example.com/?p=123 というURLへアクセスがあった場合、
http://www.example.com/test-post/ というパーマリンクへ301(こっちのURLが正しいですよ、というブラウザへの命令)などのコードで転送してやるのだ。

他にも http://www.example.com/test-pos/ なんていうのを http://www.example.com/test-post/ へ転送することも行っている。(推測です)

こういったことを担当しているcanonical.phpなんだけれど、その中身はちょっと複雑。主に301系の転送を担当してはいるものの、いろいろな処理が入り乱れている。

2. 問題点

そこでこの処理を超ナナメ読みして今回の問題点を洗い出すとこうだ。
以下に二つのURLがある。
パーマリンクを元に作成された、記事自体のパーマリンク。リダイレクトしたいURLを redirect_url と呼ぶ。
リダイレクトされる前の、実際に今アクセスされているURLを requested_url とする。
この二つが一致しないので延々とredurect_urlにリダイレクトさせようとしてしまっている。

canonical.phpにあるredirect_canonicalという関数。すでにかなーり縦長で読みにくい上、一度だけ自身を呼び出すプチ再帰になってるのでこれまたわかりにくい。中にデバッグ用のコードがコメントアウトされて配布されてるところをみても苦労が窺える。
canonical.phpにあるredirect_canonicalという関数。すでにかなーり縦長で読みにくい上、一度だけ自身を呼び出すプチ再帰になってるのでこれまたわかりにくい。
中にデバッグ用のコードがコメントアウトされて配布されてるところをみても苦労が窺える。

普通の情況であれば一度リダイレクトされればredirect_urlはrequested_urlとなるんだけれど、今回の問題は違った。
じつはこのrequested_urlは$_SERVERを元に作成される。サーバ環境変数。つまり見せかけ上のURL(ドメイン名)と実際にPHPが動いているドメイン名が違っていると無限ループする、というわけだ。
この見せかけ上のドメインとPHPが動いているサーバのドメイン名が違う、というのはリバースプロキシを使っていると起こってしまう。
リバースプロキシは大規模なウェブサイトになると負荷分散の為に用いられる手法だ。

今回このサイトではapacheのmod_proxyを利用した簡単なリバースプロキシを噛ましていた。
手前で動いていてWordPressに登録されているドメイン名はwww.example.comなのに、背後のウェブサーバではwww1.example.comという具合にドメイン名が違う。
なのでredirect_urlとrequested_urlが延々と違う、ということになってしまったのだ。

3. 解決方法

この問題を解決するにはいくつかの方法がある。
一つはcanonical.phpを書き換えてしまう方法。
requested_urlが$_SERVERから取得しているので該当部分を書き換えてしまえば良い。
ただしWordPressのコアファイルを書き換える、というのは禁断の果実を食べるに等しいのであまりオススメしない。

次にフィルタを書いてやる、という方法。redirect_url側をrequested_url側のドメイン名に揃えてやる、というやり方。これならば問題は起きにくいだろう。

// Note that you can use the "redirect_canonical" filter to cancel a canonical redirect for whatever reason by returning false
    $redirect_url = apply_filters('redirect_canonical', $redirect_url, $requested_url);

というわけでridirect_urlを書き換えるフィルタを書いてみました。これをfunctions.phpに加えておけばURLのドメイン部分がリバースプロキシの奥側のドメインに一致します。

add_action('redirect_canonical', 'change_requested_url');
function change_requested_url($redirect_url, $requested_url) {
 $redirect_url = is_ssl() ? 'https://' : 'http://';
 $redirect_url .= $_SERVER['HTTP_HOST'];
 $redirect_url .= $_SERVER['REQUEST_URI'];
 return $redirect_url;
}

今回のサイトでは複雑なクエリの組み立てなども行っていないのでこれで問題なさそうです。元のコードは先ほどのcanonical.phpから拝借してきました。

今回は見事、上記のフィルタ動作にてうまく動きました。めでたしめでたし。

ブログの記事が見つからなかったときトップページに飛ばす方法

WordPressの記事が見つからなかったときにトップページに飛ばす方法について。404 on chrome

WordPressで記事を書いたり作ったりしていると記事が無いことがある。原因は主に二通りで、一つはリンク先のアドレスを間違えてしまったリンクミス、もう一つは過去には存在したけれど削除されてしまったケース。

これらのケースにおいてWordPressではどんな対応を取るべきなだろうか。

WordPressではデフォルトで404.phpというテーマファイルがくっついてくる。
これはHTTP Status Codeが404の場合、つまり記事が見つからなかった場合に表示されるページのテーマだ。
“おっと、失礼しました。”と表示されるのはこの404.phpが表示されるケースだ。

ブログの運営方針にもよるが、今回は記事が見つからなかった場合にトップページに飛ばすことにした。
ここで注意しなければいけないことが二点。
一つはトップページに“飛ばす”ということ。実はテンプレートヒエラルキーなんていうムズカシイ言葉があって、404.phpを削除するだけで”記事が見つからなかった場合にトップページを表示“させることができる。このやり方だと見た目はトップページなんだけれどURLが無くなった記事そのままになってしまう。これはダサい。
注意点の二つ目はHTTP Status Code。HTTPステータスコードというのはブラウザに送る機械的な意味だ。良く目にする404というのは”記事が見つかりませんでした”という意味を確実に的確に機械的にブラウザに伝えるための番号なのだ。
さて実はこのHTTPステータスコードなんだけれど、その確実性ゆえ、検索エンジンにも番号は理解される。つまり記事ID123番のページに検索エンジンのボットが来た、でも記事がないよーというとき、404を返しておけば「このページは削除されたんだね」と解釈してくれる。(厳密にはそうで無い場合もあるのだけれど)
なので極力このステータスコードを返してあげた方が良い。

というわけでfunctinons.phpあたりにこのへんをまとめて書いてみた。


add_action('wp_head', 'redirectToIndex');
function redirectToIndex() {
if (is_404()) {
header('HTTP/1.0 404 Not Found');
header('Location: '.get_bloginfo('url').'/');
}
}

これで404を送りつつLocationを指定してページをジャンプさせることができる。Locationの行を変えることでジャンプ先を任意のページにすることもできる。

 

おわりに
HTTPステータスコードはなんだかなじみが無くてなかなか利用価値がわからなかったりするんだけれど、例えばサーバの負荷を減らすためのキャッシュの制御だったりだとか、一時的なメンテナンスを通知したりだとか、そういった用途に使える。
とくにメンテナンス通知に使われる503は日時指定もできて何時何分ごろには復旧しますよー、なんてこともできたりする。
検索エンジンにメンテナンス画面がキャッシュされてしまうことも防げたり、これは防げないこともあったり。でも気を遣うに超したことはないよね。

 

というわけで404をリダイレクトでジャンプさせる、でした。

CakePHPでセションメッセージ、フラッシュメッセージを二度以上使いたかった

CakePHPでセションメッセージを二度以上使いたかった。

CakePHPでセションを使ってメッセージを表示することができる。通常はエラーメッセージを表示させたりするためにeditやaddなどでエラーメッセージを収めておき、リダイレクト先のviewでflush()を呼んで表示している。

便利な機能なのだけれど、setFlashという名前の通り、このメッセージは一度限り設定できる。コントローラが大きくなってくるとセションメッセージの設定位置が散らばったりするため、意図せずしてセションメッセージを後から上書きしてしまうことになってしまう。今回はこの問題を調べてなんとかしてみた。

1. やりたいこと
下のようなコードがあったとする。

Class HogeController extends AppController {
public function action() {
$this->Session->setFlash('hoge');
$this->Session->setFlash('fuga');
}
}

このとき、actionのviewでflush()でセションのメッセージを呼び出したとき、hogeは無くなってしまってfugaが表示される。
今回はここで何とかして hogefuga という風に連結したメッセージを表示させてみる。

$this->Session->setFlash('hoge');
$flashMessage = $this->Session->read('Message.flash');
$this->Session->setFlash($flashMessage['message'] .= 'fuga');

これでセションのフラッシュメッセージを追加できる。この場合だと hogefuga が表示される。

こうすると動かせるようになるのは以下の理由。
SessionComponent.phpにあるsetFlashのメソッドの定義を見てみる。

public function setFlash($message, $element = 'default', $params = array(), $key = 'flash') {
CakeSession::write('Message.' . $key, compact('message', 'element', 'params'));
}

setFlashというのはCakeSessionのwriteメソッドを呼んでいる。このメソッドの定義はCakeSession.phpにある。このクラスにはwriteの他にreadもある。このreadを使って上記のように取り出して追記している。

public static function read($name = null) {
if (!self::started() && !self::start()) {
return false;
}
if (is_null($name)) {
return self::_returnSessionVars();
}
if (empty($name)) {
return false;
}
$result = Hash::get($_SESSION, $name);

if (isset($result)) {
return $result;
}
self::_setError(2, "$name doesn't exist");
return null;
}
public static function write($name, $value = null) {
if (!self::started() && !self::start()) {
return false;
}
if (empty($name)) {
return false;
}
$write = $name;
if (!is_array($name)) {
$write = array($name => $value);
}
foreach ($write as $key => $val) {
self::_overwrite($_SESSION, Hash::insert($_SESSION, $key, $val));
if (Hash::get($_SESSION, $key) !== $val) {
return false;
}
}
return true;
}

readとwriteするときにはキーによって制御している。このキーはsetFlashを使ったときには ‘Message.flash’ が用いられている。なのでこれを手動でreadして改めて連結してsetFlashしている。

これでセションのメッセージを追記することができる。
なにか別名を与えてsetFlashの代わりにこれを全部使っても良いかなと思うのだけど、しばらく様子をみてからにしよう。

emacsのphp-modeでarrayのインデントを改善した

1. はじめに

emacsでphp-modeを利用している。とても便利なelispなんだけどデフォルトだといくつか不便な点がある。

基本的にはPEARに準拠したコーディング規約にしたかったので下記のブログ記事を参考にしてinit.elの設定を行った。

(setq php-mode-force-pear t)

PHP開発で追加しておきたいEmacs Lisp 8選 : アシアルブログ

だけれどもこれだけだとarrayのインデントがおかしい。

2. おかしいインデント


<?php
function hoge(){
    $hoge = array(
                 'hoge' => 1,
    );
}

このように連想配列の添え字(キー名)のインデント位置が前述の括弧の位置まで下がってしまうのだ。 コレは見づらい。できれば $hoge の開始位置から規定のタブ幅(4とか)で字下げして欲しい。

2. 解決方法は見かけるのだけれど

この問題でググってみるといくつか解決方法が出てきた。だけれどもその辺のelispを適用してみてもうまくいかない。バージョンがダメなのか他のelispと競合しているのか、とにかく動かなかった。というかelispに対する理解が少ないというのが一番の根本的な問題かな。

で、しばらくこの問題は放置していたのだけれど、あまりにも気になると言うことで気合いを入れて調べてみた。すると意外にも(?)公式的なみんな大好き emacswiki が出てきた。

3. 解決

Indentation of arrays
The php-mode indentation for array is not good, It will indent like this:

$post=Post::model()->find(array(
                              'select'=>'title',
                              'condition'=>'postID=:postID',
                              'params'=>array(':postID'=>10),
                              ));

The better indentation is like:

$post=Post::model()->find(array(
    'select'=>'title',
    'condition'=>'postID=:postID',
    'params'=>array(':postID'=>10),
));

Add this to php-mode-hook:

(add-hook 'php-mode-hook (lambda ()
    (defun ywb-php-lineup-arglist-intro (langelem)
      (save-excursion
        (goto-char (cdr langelem))
        (vector (+ (current-column) c-basic-offset))))
    (defun ywb-php-lineup-arglist-close (langelem)
      (save-excursion
        (goto-char (cdr langelem))
        (vector (current-column))))
    (c-set-offset 'arglist-intro 'ywb-php-lineup-arglist-intro)
    (c-set-offset 'arglist-close 'ywb-php-lineup-arglist-close)))

EmacsWiki: Php Mode Indentation of arrays

さて早速コレを適用。init.elを開いて、php-completionですでにphp-mode-hookがあったのでそこに追記して再起動。

うまくいきました。

<?php
function hoge(){
    $hoge = array(
        'hoge' => 1,
    );
}

めでたしめでたし。

Smartyを2系列から3系列に移行するにあたって困ったこと

1. 始めに

自分が運用しているいろいろなwebサイトでテンプレートエンジンであるSmartyを利用している。エスケープとかを全部オマカセにできてMVCが分離できるっていうのでなかなかの優れものだ。

で、最近、ひょんなことからwebサーバを構築し直すことになった。従来のサーバではSmartyのバージョンが2系列を利用したのだけれど、どうやら3系列が最新版らしかったのでこっちを入れてみた。

動作が高速になるだとかいろいろと恩恵があるそうで、それならば、ということで。何より最新版があるならばそっちを利用しよう、というのがモットーなので今回も多分に漏れず移行してしまったみました。

Smarty2.0からSmarty3.0への変更点 | suinasia

2. 問題その1

さて、インストールなどは簡単に終わり早速移行したのだけれどいきなり問題が発生した。

smartyでappendしている変数がどうにも表示されない。{debug}を埋め込んでみても変数が渡されている形跡が無い。不審に思ってappend(assign)している箇所にて渡される変数を見てみるとこちらはちゃんと内容が入っている。うーん、これは困ったなぁ、と思いながら眺めているとすべての変数が渡されていないわけじゃないことに気がついた。一部変数はちゃんと渡されているのだ。ということはappend(assign)が絶対的に悪いわけじゃ無いらしい。

で、どんな値なら渡されてどんな値は渡されていないのか、そこが気になった。で、一目瞭然、日本語データが渡されていないのだ。

mb_convert_stringなどでテキトウに変換して渡してやると変な状態になった。出力がUTF-8の時のみ、データが表示されたのだ。

今回対象となっているwebサイトは作られた時期が古く、SJISでやりくりしていた。smartyの出力もSJISだったんだ。というわけでどうやらそこが原因らしい。

で、smarty3とutf-8あたりでググるとさっそく情報が出てきた。というか前述のページにもしっかり書かれてました。

SMARTY_RESOURCE_CHAR_SETという変数がUTF-8で宣言されるのですが、コレをsmartyより先にSJISとかでdefineしないとダメぽです。

UTF-8 以外でテンプレートを作成する – smarty3

define('SMARTY_RESOURCE_CHAR_SET', 'SJIS');
require_once('Smarty.class.php');

cakephp命名規則のメモ

種別 規則 例1 例2
テーブル名 複数形, アンダースコアケース users user_settings
カラム名 単数形,アンダースコアケース age modified
カラム名(キー) 主キーはid,外部キーは単数形のテーブル名_id user_id category_id
カラム名(予約語) name,title,created,modified,updated
モデル名 単数形キャメルケース User UserSetting
コントローラ名 複数形キャメルケース UsersController UserSettingsController
コントローラ名(URL) 複数形アンダースコアケース users user_settings
アクション名 アンダースコアケース index save
アクション名(URL) アンダースコアケース index save
ビューテンプレート名 View/コントローラ名/アクション名.ctp View/Users/index.ctp View/UserSettings/save.ctp

PHPで平仮名からローマ字書きに変換したかった

PHPで平仮名からローマ字書きに変換したかった。と、諸事情があってひとまずリストを配列で書き出した。

またいつか何かの機会に使うかもしれないので貼っておく。でももうこういうのやりたくない。


$hoge = array
('あ' => array('a'), 'い' => array('i'), 'う'=>array('u'),
'え'=>array('e'), 'お'=>array('o'),
'か'=>array('ka'), 'き'=>array('ki'), 'く'=>array('ku'),
'け'=>array('ke'), 'こ'=>array('ko'),
'さ'=>array('sa'), 'し'=>array('si', 'shi'), 'す'=>array('su'),
'せ'=>array('se'), 'そ'=>array('so'),
'た'=>array('ta'), 'ち'=>array('ti', 'chi'), 'つ'=>array('tu', 'tsu'),
'て'=>array('te'), 'と'=>array('to'),
'な'=>array('na'), 'に'=>array('ni'), 'ぬ'=>array('nu'),
'ね'=>array('ne'), 'の'=>array('no'),
'は'=>array('ha'), 'ひ'=>array('hi'), 'ふ'=>array('hu', 'fu'),
'へ'=>array('he'), 'ほ'=>array('ho'),
'ま'=>array('ma'), 'み'=>array('mi'), 'む'=>array('mu'),
'め'=>array('me'), 'も'=>array('mo'),
'や'=>array('ya'), 'ゆ'=>array('yu'), 'よ'=>array('yo'),
'ら'=>array('ra'), 'り'=>array('ri'), 'る'=>array('ru'),
'れ'=>array('re'), 'ろ'=>array('ro'),
'わ'=>array('wa'), 'を'=>array('wo'), 'ん'=>array('n', 'nn'),
'が'=>array('ga'), 'ぎ'=>array('gi'), 'ぐ'=>array('gu'),
'げ'=>array('ge'), 'ご'=>array('go'),
'ざ'=>array('za'), 'じ'=>array('ji'), 'ず'=>array('zu'),
'ぜ'=>array('ze'), 'ぞ'=>array('zo'),
'だ'=>array('da'), 'ぢ'=>array('di', 'ji'), 'づ'=>array('du', 'zu'),
'で'=>array('de'), 'ど'=>array('do'),
'ば'=>array('ba'), 'び'=>array('bi'), 'ぶ'=>array('bu'),
'べ'=>array('be'), 'ぼ'=>array('bo'),
'ぱ'=>array('pa'), 'ぴ'=>array('pi'), 'ぷ'=>array('pu'),
'ぺ'=>array('pe'), 'ぽ'=>array('po'),
'きゃ'=>array('kya'), 'きゅ'=>array('kyu'), 'きょ'=>array('kyo'),
'しゃ'=>array('sya', 'sha'), 'しゅ'=>array('syu', 'shu'),
'しょ'=>array('syo', 'sho'),
'ちゃ'=>array('tya', 'cha'), 'ちゅ'=>array('tyu', 'chu'),
'ちょ'=>array('tyo', 'cho'),
'にゃ'=>array('nya'), 'にゅ'=>array('nyu'), 'にょ'=>array('nyo'),
'ひゃ'=>array('hya'), 'ひゅ'=>array('hyu'), 'ひょ'=>array('hyo'),
'みゃ'=>array('mya'), 'みゅ'=>array('myu'), 'みょ'=>array('myo'),
'りゃ'=>array('rya'), 'りゅ'=>array('ryu'), 'りょ'=>array('ryo'),
'ぎゃ'=>array('gya'), 'ぎゅ'=>array('gya'), 'ぎょ'=>array('gyo'),
'じゃ'=>array('zya', 'ja', 'jya'), 'じゅ'=>array('zyu', 'jyu', 'ju'),
'じょ'=>array('zyo', 'jyo', 'jo'),
'びゃ'=>array('bya'), 'びゅ'=>array('byu'), 'びょ'=>array('byo'),
'ぴゃ'=>array('pya'), 'ぴゅ'=>array('pyu'), 'ぴょ'=>array('pyo'),
);

PHPでマルチバイト(日本語)に対して正規表現を使って後方参照したかった

PHPでマルチバイト(日本語)に対して正規表現を使って後方参照したかった。ちょっと正規表現でごにょごにょしてるとつっかえたことがあったのでメモ。

まず、次のような試験がうまく通らなかった。



<?php

$str[] = '1あああ2'; // 1) hit => OK
$str[] = '1ああ)あ2'; // 2) not hit => OK
$str[] = '1ららら2'; // 3) not hit => NG
$str[] = '1らら)ら2'; // 4) not hit => NG
$ptrn = '/1([^\)]*)2/';

foreach($str as $val){
if(preg_match($ptrn, $val, $matches)){
var_dump($matches);
}
}


この例の場合、”あ”が検査対象となっている場合には思った通りの動作をしてくれる。でも”ら”が入った文字列には思い通りの動作にならない。

いろいろ悩んだところ、これはマルチバイトの問題だろう,というところに行き着いた。

まず最初に、preg_matchのマルチバイト版がないか探した。mb_preg_match的な関数で探した。でも無い。残念。

どうやらmb_ereg系はあるらしい。でも最近のPHP (5.3以降)ではeregじゃなくてpreg_matchが推奨されている。

>PHP: PHP 5.3.x で推奨されない機能 – Manual

そしてmb_eregを使うと後方参照(あとで括弧を使って正規表現で合致した文字列の一部を利用できるようになる機能)が使えない。なのでmb_eregは今回の解決方法対象外。

で、ぐぐってると正解にたどり着けた。

どうやら検査パターンの最後のオプションにuを付けるとイイらしい。おそらくutf-8で解釈しなさいよ、ってことかな。

preg系でオプションを付けるには終了デリミタ(って言えば良いのかな)の後ろに文字を列挙して指定する。今回の場合にはこんな感じ。


<?php

$str[] = '1あああ2'; // hit => OK
$str[] = '1ああ)あ2'; // not hit => OK
$str[] = '1ららら2'; // not hit => NG
$str[] = '1らら)ら2'; // not hit => NG
$ptrn = '/1([^\)]*)2/u';

foreach($str as $val){
if(preg_match($ptrn, $val, $matches)){
var_dump($matches);
}
}

?>

原因についても考えてみた。今回は文字(列)”ら”と文字(列)”)”が問題を起こしていた。双方のUTFコードは以下の通りである。

ら E38289

)EFBC89

パっと見た感じ、下二バイトが合致してるとダメみたい。試しに同様に下2バイトが合致する”ド”で試したら案の定”)”と区別できなくなってた。ほむー。

JavaScript Unicode Charts