Hatena::ブログ(Diary)

Lism.in * blog - nekoya (id:studio-m) RSSフィード

2012-10-30

2012-02-21

DBIx::Skinny::Schema::Loader 0.24 released

久々のリリースです。例によって自分では全くコードを書かずに、頂いたPull Requestを取り込むだけの簡単なお仕事です。nyo-kichiさんありがとうございました。

今回のアップデートでは後方互換性が無くなった部分があるので、ご報告を兼ねてエントリ。

内容的には、insert後の挙動のためにPKの持ち方が変わったのと、その周辺で少し動きがありました。詳細は以下。


DBIx::Skinny::Schema::LoaderではテーブルにPKが定義されていない時に、なんやかんやと適当なルールでPKでっち上げていました。これには、DBIx::Skinny::Schema::Loader 0.15 released でも書いたように初期のSkinnyではPKが無いテーブルは扱えなかったので、何らかのカラムをPKとして挙げる必要がありました。

今のSkinnyではPKは必ずしも必須ではないし、複合PKの持ち方やらも含めて当時とはかなり変わっているので、こういう歴史的な遺物は無理に残す必要もないだろうということで削除の運びとなりました。


ちなみに、自分はSkinnyを使う時に、insertの戻り値は使わないというポリシーを持っています。これは初期にinsertの戻り値をどうするか、という仕様が錯綜していたことに起因しています。最初の頃はlast_insert_idを返していたんじゃないかと思います(ちょっとあやふや)。

そんなわけでinsertの戻り値に影響する部分なんかは全然フォローが行き届いていなかったのですが、こうして使ってくださる方がいて、改善されるのは嬉しいものですね。

2012-01-11

フリーランスという働き方

TLで「フリーランスの仕事とは」みたいな話題が目に付いたので。

答えのないものだし、それぞれの人が自分なりのやり方を見つけていくしかないのだけど、サンプルはあればあっただけ有効だろうということで、自分のことも少し書いてみようと思います。


■これまでの歩み

Web関係のお仕事で気がつけば12年ほどやってきてます。そんなに長いつもりはなかったけど、改めて数えてみたらびびった。

社員としてフルタイム勤務した期間と、フリーランスとして動いていた期間はだいたい半々ぐらい、といったところでしょうか。今は会社と雇用契約を結んで、フルタイムで勤務しています。

転職などはわりと回数を重ねていますが、自分ではあまり「転々としている」感はありません。会社や契約形態が変わったり一時的に離れたりはしても、結果的には同じところの仕事をしていることが多かったからかも知れません。長いお客さんとは途中離れる時期がありつつも、足かけ9年に渡って仕事をさせて頂きました。

会社に所属する場合は、基本的には小さな会社で少人数で回すことを好んでいますが、5〜10人程度、20〜30人程度、50人規模、ごくわずかな期間だけど数千人規模の会社とそれなりに見てきたつもりです。

振り返ってみれば、これまでお付き合いのあった各社には非情に柔軟な対応をして頂いたものだと思います。改めて感謝。


フリーランスという在り方

自分にとってフリーランスという形態はこれといって特殊なものではなく、単に仕事を進める上での契約形態がそのようになっているというだけのものです。

世間で言うところの「会社員」というステータスは、会社との関係性が雇用契約に基づいているというだけで、本質的にフリーランスとの違いは無いと考えています。

その時々で仕事を円滑に進めるための契約形態を取ればいいと考えているので、必要だと判断した場合は雇用契約を結びますし、発注に基づく契約形態をとる場合もあります。

なので、会社員からフリーランスに変わることも、フリーランスから会社員になることも自然な流れととらえています。どちらがいいとか、そういう話じゃない。あくまで目の前の仕事を遂行するための手段です。

自分の中ではフリーランスになることと転職することは同列の扱いで、所属がどこかの会社から自分自身になるだけだと考えています。


■多分、少数派な考え方

この手の話題が出ると、まず間違いなく「会社に雇われて」といったテキストに出くわします。僕はこの表現が嫌いです。

会社というのは同じ課題に向かい合って共に歩んでいくパートナーだと考えているので、そこで雇う・雇われるというスタンスを前面に出すことは好みません。

エンジニアと企業の関係は、プロ野球選手が球団と契約を結ぶ感覚が近いのではないかと思っています。エンジニアには会社を選ぶ権利があるし、一方で会社にもエンジニア戦力外通告をする権利があるというのが基本的な考えです。

実際には雇用関係にある場合は労働者側の権利が保護されていたりするわけですが、会社から必要とされていないのにその場にいるというのはしんどいし、お互い幸せになれないので違う道を探した方がいいのではないかと思います。

こういうバックグラウンドの影響でしょうか、よくある「会社員 vs フリーランス」みたいな図式は単純なモデルに落とし込みすぎているように思われます。

世の中には0と1だけじゃなくて、0.5も0.7もあるので一概にどっちがどうとくくってしまうのはちょっと乱暴すぎですね。


■ぶっちゃけフリーランスってどうよ?

ここまでを前置として、そんな自分がフリーランスとして過ごした実感をつらつらと。

●時間的な制約

拘束時間の長さについてはケースバイケースなので何とも言えません。

フリーランスだと際限なく仕事ができてしまう」という話もありますが、別に会社勤めしててもいくらでも出来ます。別に社外から会社のリソースへのアクセスが禁止されていても、やることはいくらでもあります。ライブラリのコードを読んだり、本を読んだり、実験的なコードを書いたり。

平日の自由度はフリーランスの方が圧倒的に高いです。そりゃまぁ当然っちゃ当然です。これがどうしても欲しい場合はフリーランスという形態で活動するのはありでしょう。

生活のリズムについては、フリーランスでも独身か所帯持ちかで話が大きく変わってきます。後者の場合はフリーランスでも規則正しい生活を送らざるを得ない場合もありますねw

●仕事の裁量

フリーランスにそんなに大事なところは任せないよ」という指摘はある意味正しいが、必ずしもそうではない場合もあったりするわけで。これもケースバイケースとしか言えないのですが、ある程度の規模のチーム体制で臨むような仕事の場合は、社員として参加しないと難しいケースがやはり多いでしょう。

他には「運用や継続的な開発といった部分は自社のリソースでまかないたい」というケースも多いのではないでしょうか。

ある程度の規模のものに継続的に関わっていったり、体制を作るところなどに深くコミットしていこうと思ったら入社してしまうのが手っ取り早いとは思います。

フリーランス的に動いていた人が、DeNAGREEなどに入社される場合はこういった部分がわりと大きいのではないかなーと想像したりします。

●情報管理統制

このへんはデリケートな問題なので何とも言い難いのですが、日々電車に乗ってるとノートPC広げて会社メール見たり、機密書類っぽいものを印刷して読んでる人とかいくらでもいますよね。

会社としては正社員にしてしまえば安心感はあるのでしょうけど、会社員は辞めることも出来るので実際のところそれで内部統制が担保できるわけではないと思います。

●仕事場的な制約

自宅にこもるパターンも、相手先に席を用意してもらってそこで仕事するパターンも経験してきましたが、どちらも一長一短です。

外に出た方が気持ちの切り替えがしやすいのは確かだし、自宅だったらヘッドホンじゃなくてスピーカーから音が出せて気持ちいいのも確か。

複数人で仕事を進めていく場合は、同じ場所にいた方がコミュニケーションコストが圧倒的に下がるので、そこは仕事の内容次第で決めるのがいいでしょう。

ただし、居心地の悪いオフィスはストレスが溜まるので、そこだけは自分の中で基準を持っていた方がいいかも知れません。

自分の基準はこんな感じでしょうか。

  • ヘッドホンしても怒られない
  • 極端にうるさくない
  • コーヒー豆が挽ける

あと、自宅にこもっていると太る。通勤で往復2時間半とか無駄だと思っていたけど、通勤再開したら1年で10kg以上痩せました。

●孤独との戦い

みたいなものはなかった。

少なくとも東京では常日頃からあちこちで勉強会が開かれているし、三鷹だとプログラマーズカフェとかに出入りしていれば、一人だからどうこうみたいなことはあまり感じないです。

●お金の話

いろいろと恵まれていたこともあってか、正直、どっちでもたいして変わらなかったのが実際のところ。

金額的な話はいろいろな場面で出てくるけど、あんまり気にしなくてもいいんじゃないかなぁというのが正直な感想です。あえて言うなら、

  • 初期投資をしないこと
  • 支出を抑えるような生活にすること

ぐらいでしょうか。変に気合い入れて設備投資とかしてもあんまり意味ないし、コワーキングスペースとかノマドとかに変なあこがれを抱いて必要以上にお金を垂れ流したりいなければ、そのうち自分なりの基準が見付かるはずです。もちろん必要なら積極的に使い倒せばいいのだけど、ご利用は計画的に

収入の安定性に不安がある人は、固定でお金がもらえる案件の割合を多めにしていけばいいのではないでしょうか。

逆に、この部分でプランを描いて食い扶持を確保していけない人はフリーランスでやっていくのは難しいかも知れません。そういう意味では、若いうちに経験しておくのはありかなーとも思いますね。

確定申告は面倒だけど、いざとなれば白でも出せるし、にっちもさっちもいかなくなることはないです。申告シーズンの税務署には、PC使えない自営業のおっちゃんおばちゃんやら、日本語のおぼつかない留学生の人たちもたくさんいます。出来る出来る絶対出来る。一度見学しておくと雰囲気がつかめてよいかも。

●向き不向きの話

「迷うぐらいだったらやめとけ」でFA

「行きがかり上そうなるのが自然だった」とか「なんか気がついたらそういうことになってた」とか、それぐらいがちょうどいいような気がします。変な力みがあると、しんどいかも。

あとは、フリーランスから会社員になることに抵抗を感じる必要はないです。状況なんてその時々で変わるんだから、それに合わせて柔軟に対応すればいいだけのこと。

フリーランス」という枠に縛られるのは、全然フリーじゃないですよ。

2011-12-14

cobblerでRHEL6系のリポジトリをミラー出来ない時の対処法

そろそろCentOS5系からの意向を図ろうかということで、ScientificLinux6.1をcobblerに取り込もうとしたらreposyncがこんな感じのエラーを吐いて死ぬ症状に遭遇。

Wed Dec 14 12:10:09 2011 - INFO | Exception occured: cobbler.cexceptions.CX
Wed Dec 14 12:10:09 2011 - INFO | Exception value: 'reposync failed, retry limit reached, aborting'
Wed Dec 14 12:10:09 2011 - INFO | Exception Info:
  File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 95, in run
    rc = self._run(self)
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 242, in runner
    name=None, nofail=False, logger=self.logger)
   File "/usr/lib/python2.4/site-packages/cobbler/api.py", line 636, in reposync
    return reposync.run(name)
   File "/usr/lib/python2.4/site-packages/cobbler/action_reposync.py", line 128, in run
    utils.die(self.logger,"reposync failed, retry limit reached, aborting")
   File "/usr/lib/python2.4/site-packages/cobbler/utils.py", line 131, in die
    raise CX(msg)

Wed Dec 14 12:10:09 2011 - ERROR | ### TASK FAILED ###

既存のリポジトリの更新は出来るので、ネットワークの問題でもなさそう。ミラー変えてもダメ。EPEL6もミラー出来ない。

ググッてみたら、cobblerMLで既出でした。

I am running my cobbler server on Scientific Linux 5 and had the same

problem. After installing the package

python-hashlib-20081119-4.el5.x86_64

cobbler reposync runs fine, syncing epel6.

ということで、

$ sudo yum install python-hashlib

したらリポジトリがミラー出来るようになりました。

2011-08-23

PHPのnullはハマりポイントになりかねない

$ php -r '$data = array(); echo $data["key"];'
PHP Notice:  Undefined index: key in Command line code on line 1

当然ですね。

$ php -r '$data = null; echo $data["key"];'

今更ながらに知ったのだけど、NOTICEも何も出ないのね。

決まったフォーマットの連想配列が返ってくることを期待したコードがnullを受け取ると、予期せぬハマりポイントになりかねない。

ちゃんとissetとかで確認するなりしてればいいのだけど あまりよくなかったので追記(後述)、

<?php
$result = hogehoge();
if ($result['errCode']) {}

みたいなゆるふわコードが紛れていると、問題箇所が特定しづらかったりしますね。

(2011/08/25追記)

コメント頂いたのでもう少し見たら、nullにisset使ってもダメでした。

$ php -r '$data = null; echo (isset($data["key"])) ? "exists\n" : "none\n";'
none

array_key_existsだと、WARNINGが走るのでより「想定外」であることが明確になる。

$ php -r '$data = null; echo (array_key_exists("key", $data)) ? "exists\n" : "none\n";' 
PHP Warning:  array_key_exists(): The second argument should be either an array or an object in Command line code on line 1

Warning: array_key_exists(): The second argument should be either an array or an object in Command line code on line 1
none

本来的にはnullが返ってくるのを想定して、is_nullであらかじめ対応しておくとか、is_arrayでちゃんと配列が返ってくるのを確認するというのがあるべき姿か。

2011-05-20

CakePHPがコンポーネントを読み込む仕組みを調べてみた

SecurityComponentのチェックにかかってにっちもさっちもいかなくなったんだが、そもそも基本的な動作フローを理解していないのでどこから手を付けていいのか分からない。というわけで、コンポーネントが読み込まれる仕組みを調べてみた。

対象バージョンは1.3.6だけど、2011/05/19時点のgithubのmasterを見てもこのあたりは変わっていないらしい。


■Dispatcherから始める

コケたところでdebug_backtraceを取って、そこからさかのぼってみる。

Dispatcher(cake/dispatcher.php)のdispatchメソッドの最後で$this->_invokeが呼ばれていて、ここから読み始めることにした。

アクションメソッドを呼び出す前に以下の処理があり、ここでコンポーネントの読み込みと初期化を行っているようだ。

<?php
  function _invoke(&$controller, $params) {
    $controller->constructClasses();
    $controller->startupProcess();

■Controllerを読む

Controller(cake/libs/controller/controller.php)の中を見る。まずはControllerの中で定義されているComponent関連と思われるメンバ変数の定義。

<?php
/**
 * Instance of Component used to handle callbacks.
 *
 * @var string
 * @access public
 */
    var $Component = null;

/**
 * Array containing the names of components this controller uses. Component names
 * should not contain the "Component" portion of the classname.
 *
 * Example: `var $components = array('Session', 'RequestHandler', 'Acl');`
 *
 * @var array
 * @access public
 * @link http://book.cakephp.org/view/961/components-helpers-and-uses
 */
    var $components = array('Session');

componentsとComponentは別物という扱いのようだ。componentsに列挙されたコンポーネントを回して、それぞれがコールバックを叩くというイメージを持っていたが、どうやら違うらしい。

Componentがstringということは、callbackを持てるコンポーネントは1つだけで、その名前を入れるとかだろうか。

$this->Componentはコンストラクタの中で以下のように初期化している。

<?php
    $this->Component =& new Component();

…えっと、なんかインスタンス突っ込んでるんだけど「@var string」ってコメントの記述はドキュメントバグでしょうか。


気を取り直して、Dispatcherの_invokeで呼ばれている処理をそれぞれ追ってみる。まずはconstructClasses()から。ここでは、

<?php
    $this->Component->init($this);

してるだけ。そして、startupProcess()の全処理。

<?php
  function startupProcess() {
    $this->Component->initialize($this);
    $this->beforeFilter();
    $this->Component->triggerCallback('startup', $this);
  }

まとめると、beforeFilterで特別なことをしなければ、以下のフローで処理されていることになる。

<?php
$this->Component =& new Component();
$this->Component->init($this);
$this->Component->initialize($this);
$this->Component->triggerCallback('startup', $this);

■Componentを読む

Controller内のComponentとcomponentsの関係が怪しい感じなので、コメントを確認してみると、

<?php
/**
 * Handler for Controller::$components
 *
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @link          http://book.cakephp.org/view/993/Components
 */

という、やっぱり怪しいことが書いてある。

SecurityComponentiを確認してみると、Componentを継承しておらず、Componentと同じくObjectを継承している。

Componentクラスはコンポーネントの基底クラスではなく、Controllerがcomponentsを管理する際に必要な処理をまとめるための物らしい。こんなの絶対おかしいよ。

●init(&$controller)

controllerのcomponentsで指定されたコンポーネントを順次ロードする。実際の処理は_loadComponents()が受け持っており、このあたりの実装は相当気持ち悪い。

コンポーネントからコンポーネントを呼ぶことにも対応しており、controllerから直接呼ばれたコンポーネントは$this->_primary[]に入っていく。

$this->_primaryが何者かというと、こういうことらしい。

<?php
/**
 * List of components attached directly to the controller, which callbacks
 * should be executed on.
 *
 * @var object
 * @access protected
 */
    var $_primary = array();

「@var object」って書いておいて、すぐに「var $_primary = array();」で初期化しているが気にしてはいけない。

●initialize(&$controller)

コンポーネントのinitialize()を呼ぶ。

●triggerCallback($callback, &$controller)

$this->_primaryに入っているコンポーネントで$callbackに該当するメソッドが定義されていれば、それを順に呼んでいく。

今追っているフローだと、callbackはstartupなので、各コンポーネントのstartupメソッドを呼んでいる。

前述の「componentsに列挙されたコンポーネントを回して、それぞれがコールバックを叩く」というイメージは間違ってなくて、それを担当するのがComponentだったということ。

ざっくりした流れは追えたので、今日はここまで。

2011-05-19

CentOS5.5でCakePHP1.3系のInflector::slugを正常動作させる方法

CakePHPでラジオボタンを出そうと思ったら、PCREとかPHPのビルドを考え直すハメになった」の続報です。CentOS5.5環境でPCREのUnicode文字プロパティを正常動作させる方法について改めてまとめます。

動作確認には、Inflector::slugに含まれる問題のパターンでpreg_replaceを実行しました。

<?php
php -r "echo preg_replace('/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu', '', 'Hoge').PHP_EOL;"

これを実行して「Hoge」が返ってくればOK、「o」になったらダメと判断します。


yumで最新版のPCREを落としてくる

昨日のエントリに追記したように、pcreパッケージのChangelogから--enable-unicode-propertiesオプションが有効化されたことが確認出来ます。

&#42; Wed Jul 21 2010 Petr Pisar <ppisar@redhat.com> - 6.6-6
- Enable Unicode properties (Resolves: #457064)

具体的には、pcre-6.6-6以降をインストールすれば解決します。2011/05/19現在では、pcre-6.6-6.el5_6.1がリリースされています。

この場合、PHPビルドしなおす必要があるだろうなと思って暗澹たる気持ちでいたのですが、前述のpreg_replaceが正常動作したので問題ないようです。PCREは動的にリンクされていたのですね。

RHEL5/CentOS5について言うと、標準のPHP 5.1.6はシステム標準のlibpcre.so.0を動的リンクしており、システム標準のpcreパッケージは前述の通り「No Unicode properties support」でコンパイルされていますので、RHEL5/CentOS5標準のPHPではpreg系関数Unicode文字プロパティが使えません。

hnwさんのエントリにもしっかり記述されていました。

パッケージのアップデートが可能であれば、この方法が簡単でいいですね。今回はこれで乗り切ろうと思います。


PHPバンドルのPCREを使う

ブコメkoyhogeさんからアドバイス頂きました。ありがとうございます。

PHPビルドするときに、PHPバンドルのPCREを使うようにすればいいんじゃね?

今までバンドル版のPCREを使ったことがなかった、というか恥ずかしながら考えたこともなかったのですが、改めて確認してみました。

バンドルPCREを使うには、PHPコンパイルオプションに「--with-pcre-regex=dir」を含めないようにすればOKです。

PCREのインストール手順のページに、バンドルPCREのバージョンについて記載があります。

少し前のバージョンになりますが、手元のPHP5.2.12をバンドルPCREを使ってビルドしたところ、以下の結果が得られました(php -iの結果より抜粋)。

pcre

PCRE (Perl Compatible Regular Expressions) Support => enabled
PCRE Library Version => 7.9 2009-04-11

Directive => Local Value => Master Value
pcre.backtrack_limit => 100000 => 100000
pcre.recursion_limit => 100000 => 100000

マニュアルの記述から、PHP5.2系はPCRE7.8で打ち止めかと思いましたが、リリース時点の最新版をバンドルしているんでしょうかね。

CentOSの標準パッケージがPCRE6.6なので、バンドル版の方がかなり新しいですね。Changelogを追う元気がありませんが、PCREの新機能を積極的に利用されている方は検討してみてはいかがでしょうか(何とかメソッド)。

なお、この場合はpcreのパッケージ自体は古い物が残っていますので、pcretestコマンドの実行結果はPHPに組み込まれたPCREの設定とは異なります。「バンドルのPCREを使っている」という認識は関係者間でしっかり共有しておきましょう。

2011-05-18

iPhone版Skypeで長いURLを含むチャットを開くとアプリケーションが落ちる

タイトルの通り。バージョン3.0.1で確認。Copyright見ると2003-2010とあるが、今年に入ってからバージョン上がってないのかね。

「ご意見を送信」からバグ報告してみた。

CakePHPでラジオボタンを出そうと思ったら、PCREとかPHPのビルドを考え直すハメになった

(2011/05/18追記)末尾にCentOSの標準RPMの更新について書きました

(2011/05/19追記)続編書きました CentOS5.5でCakePHP1.3系のInflector::slugを正常動作させる方法

CakePHPのFormHelperでラジオボタンを出してみたところ、label要素がうまく動かない症状に遭遇しました。で、調べていくと斜め上な展開を見せたのでブログにまとめてみる。

検証環境はCentOS5.5 x86_64、PHPは5.2.12(自前RPM)です。CakePHPは2011/05/18現在のgithubのmasterで確認。

ラジオボタンがうまく選択できない

CakePHPのFormHeplerでラジオボタンを出そうとして、http://book.cakephp.org/view/1429/radioのサンプルを動かしてみたところ、label要素が思うように動作しない現象を確認。

<?php
$options=array('M'=>'Male','F'=>'Female');
$attributes=array('legend'=>false);
echo $this->Form->radio('gender',$options,$attributes);
?>

出力されたHTMLを見てみるとこんな感じ。

<input type="radio" name="data[Hoge][gender]" id="HogeGender" value="0" checked="checked"  /><label for="HogeGender">Male</label>
<input type="radio" name="data[Hoge][gender]" id="HogeGender" value="1"  /><label for="HogeGender">Female</label>

input要素のidの振られ方に問題があるらしい。

ということで、cake/libs/view/helpers/form.phpを見てみると、radioメソッドで以下のようにidを決定していることが確認された。

<?php
1094       $tagName = Inflector::camelize(
1095         $attributes['id'] . '_' . Inflector::slug($optValue)
1096       );

■Inflector::slug

Inflector::slugを通すとダメになるようなので、cake/libs/inflector.phpを確認。

<?php
617     $merge = array(
618       '/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
619       '/\\s+/' => $replacement,
620       sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '',
621     );

この1つ目の正規表現がうまく働いていないようだ。パターンにはUnicode文字プロパティが使用されていて、このへんが怪しい。詳細は以下。

調べてみると、CentOS標準のPCREを用いてPHPビルドすると、CakePHP1.3系でこの問題が発生するらしい。


#582 function slug in inflector.php - CakePHP - cakephp」でこの件に関する議論が確認できるのだが、「それお前のPCREがぶっ壊れてるだけだから」の一言で終了している。

手元の環境で確認してみると、確かにこれが原因のようだ。

$ pcretest -C
PCRE version 6.6 06-Feb-2006
Compiled with
  UTF-8 support
  No Unicode properties support
  Newline character is LF
  Internal link size = 2
  POSIX malloc threshold = 10
  Default match limit = 10000000
  Default recursion depth limit = 10000000
  Match recursion uses stack

utf-8が有効な場合に、「No Unicode properties support」だとダメらしい。

1.2では問題なくて、1.3でUnicode文字列に対応させた際にこの問題が発生したようだ。

Inflector::slugがどの程度使われているのかを確認したところ、現在使用中のバージョンでは以下の結果が得られた。

$ find cake -name '*.php'|xargs grep 'Inflector::slug'|grep -v 'cake/tests'|sort
cake/console/libs/tasks/model.php:                              $validatorName = Inflector::slug($choice);
cake/console/templates/skel/config/core.php: *          'prefix' => Inflector::slug(APP_DIR) . '_', //[optional]  prefix every cache file with this string
cake/console/templates/skel/config/core.php: *          'prefix' => Inflector::slug(APP_DIR) . '_', //[optional]  prefix every cache file with this string
cake/console/templates/skel/config/core.php: *          'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
cake/dispatcher.php:                    $path = strtolower(Inflector::slug($path));
cake/libs/cache/apc.php:                parent::init(array_merge(array('engine' => 'Apc', 'prefix' => Inflector::slug(APP_DIR) . '_'), $settings));
cake/libs/cache/memcache.php:                   'prefix' => Inflector::slug(APP_DIR) . '_', 
cake/libs/cache/xcache.php:                     'engine' => 'Xcache', 'prefix' => Inflector::slug(APP_DIR) . '_', 'PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'password'
cake/libs/model/cake_schema.php:                        $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
cake/libs/view/helpers/cache.php:               $cache = strtolower(Inflector::slug($path));
cake/libs/view/helpers/form.php:                                                        $this->model() . '_' . $this->field().'_'.Inflector::slug($name)
cake/libs/view/helpers/form.php:                                $attributes['id'] . '_' . Inflector::slug($optValue)
cake/libs/view/view.php:                                $cacheFile = 'element_' . $key . '_' . $plugin . Inflector::slug($name);
cake/libs/view/view.php:                                $key = Inflector::slug($params['cache']['key']);
cake/libs/xml.php:                      $name = Inflector::slug(Inflector::underscore($name));
cake/libs/xml.php: *   to have its tags run through Inflector::slug().  Defaults to true

■対策

  1. PCREを「--enable-utf8 --enable-unicode-properties」でビルドして、PHPRPMも作り直す
  2. inflector.phpのslugメソッドを直接書き換える
  3. radio()に渡す$optionsのキーを適当に水増ししてslugの結果をユニークにする

1番が正当な解決策なのだろうが、ラジオボタンを動作させるためのコストとしては巨大すぎる。アプリケーションの他の部分に影響が出る可能性も含めて、出来れば避けたい。現時点で正常動作していない可能性は十分にあるのだが、そこも含めて検証するだけの時間をかけたくない。

2番がわりと妥当な気がして、slugメソッドだけ1.2から持ってくる、あるいは正規表現のパターンを変更するというのがわりとよさそう。元は1.2で動いていたアプリケーションなので、そこで問題が出る可能性が低いというのもある。

3番は後からvalueを直す必要があったり、対応として場当たり的すぎるが「このラジオボタンをどうにかしたい」という一点に絞れば影響範囲は最小。


■コアモジュールの置き換え

現在の状況から、inflector.phpを差し替えることが適当と思われたので、具体的な方法を模索してみた。cakeディレクトリ以下のファイルを書き換えるのは対応として筋が悪すぎるので避けたい。バージョンアップしたらそこで試合終了ですよ。

リポジトリには、app/libsという思わせぶりなディレクトリがあって、ここにファイルを置いたら優先して読んでくれるのではと期待したがそんなことはなかった。

app/webroot/index.phpcake/bootstrap.phpを呼んでいて、その中で固定パスでinflector.phpその他をrequireしているだけで、ここをプラガブルに運用出来る仕組みは提供されていない。

Cake使う時には「何かあったらコアの挙動を変えればOK」という空気感らしいが、その時のベストプラクティスは無いものだろうか…

cake/bootstrap.phpの処理自体はシンプルなので、わざわざ呼ばずにapp/webroot/index.phpから同等+αの処理を直接書いてやるというのも一つの手か。

どうにも「これでいいのだ」と思えるすっきりした解法が見当たらない。


■まとめ

PHP4をサポートしているのに、普及率の高いディストリビューションであるCentOSのPCREは切り捨てるというのは納得がいかない。「誰でも堅牢なアプリケーションがRapidに書けるよ」って売り文句のフレームワークがPCREのリビルドを要求するってどうなの。

ラジオボタンに適切なlabelを振りたいと思ったらPCREのビルドが必要というのも、問題の芋づる感がひどい。

CakePHPを使っている人が全員PCREを自分でビルドしているとは到底思えないのだが、皆どうしているのだろうか…


■参考


■追記:CentOS標準のRPMが更新されていた件について

SRPMからSPECを取り出して覗いてみたところ、

 50 %build
 51 %configure --enable-utf8 --enable-unicode-properties

となっていることを確認しました。ChangeLogには

102 * Wed Jul 21 2010 Petr Pisar <ppisar@redhat.com> - 6.6-6
103 - Enable Unicode properties (Resolves: #457064)

とあるので、7月の時点でコンパイルオプションが修正されたようです。cobblerでミラーしてバージョン固定してたから気付かなかった。

結果論ではあるが、最新のPCREを使えば本件は解決するようです(まだ試してない)。とはいえ、すぐ対応できる環境ばかりではないだろうから、悩ましい問題であることは変りないのですが。

2011-05-05

ip_conntrack_maxの設定値を監視するNagiosプラグインを書いた

どうというものでもないが、せっかくだから記録として残しておこう。NRPE経由での利用を前提としています。

iptablesの設定を変更するのに、/etc/sysconfig/iptablesを更新して

$ sudo /etc/init.d/iptables restart

適用すると、

$ cat /proc/sys/net/ipv4/ip_conntrack_max
65535

とかリセットされて、sysctl -pで再設定するのを忘れてパケットさばききれなくなるとか、悲しい事態に陥ることがあるのだけど、もう少しましなソリューションはないもんでしょうか。

CakePHPで__(0)するとNULLが返ってくる

現時点のgithubのmasterでcake/bascis.phpから。

function __($singular, $return = false) {
	if (!$singular) {
		return;
	}

これはひどい

is_nullとか===とか、ちゃんと使わないと残念なバグを仕込んでしまうという、PHPにおける基本的な教訓を改めて得られますね。

2011-04-28

CakePHPのControllerがディレクトリの階層に対応していた件

(追記)本記事の内容はCakePHP1.3.6ならびに2011/04/28時点でのgituhbの1.2, 1.3ブランチで検証しました。

(2011/05/05追記)コメントで現時点のCake2.0では再帰的な探索を行わないとのご指摘を頂きました。ちゃんと追えてないけど、import周りの処理は大幅に変更されるようです。

CakePHPではControllerは1つのディレクトリに全部入れるのがふつう」という話を聞いて、さすがにそれはないだろうと思って調べたらapp/config/bootstrap.phpに$controllerPathsを書けば探索パスを追加できるという情報を得た。

のだけど、どうにもうまく動かなくてあちこちいじくり回してたらいつの間にか動くようになったり、別のサーバにdeployしたらまた動かなくなったりして挙動が怪しい。

本体のコードを見ると、$controllerPathsなんてどこにも書いてなくて何かが間違っているように見える。

によれば1.3になった時点で$controllerPathsは使えなくなったらしい(それならそれでwarningなりと吐けよと思うのだが)。

で、$controllerPathsが動作することを期待したコードは動かないはずなのだが、目の前には何故か動く環境と動かない環境があって意味がわからないよ。と思ってコードを追ってみるといろいろわかった。


■App::importの処理

App::importはファイルを探索する際の対象パスとして、file_mapキャッシュ(app/tmp/cache/persistent/cake_core_file_map)を参照する。このキャッシュは1行目が有効期限、2行目がシリアライズしたデータになっており、以下のコードを実行することで内容を確認できる。

php -r '$c=explode("\n",file_get_contents("app/tmp/cache/persistent/cake_core_file_map"));var_dump(unserialize($c[1]));

以下はApp::importがControllerを読み込む時のあるユースケースにおけるフローを読んだメモ。

  • file_mapに入ってるなら読み込みを試みる
    • 成功したらそこでreturn
    • 失敗したらfile_mapキャッシュの該当キー削除を予約する
  • file_mapに無い場合はあれこれ通って、App::__findがファイルを探す
    • controllerの探索パスをdir_mapキャッシュ(app/tmp/cache/persistent/cake_core_dir_map)から取得
    • dir_mapにキーが無ければディレクトリを「再帰的に」たどって探索パスのリストを作る($this->__pathsに入れる)
    • 作った探索パスのリストから、Controllerの読み込みを試みる

ざっくりとした流れは「file_map探す → dir_mapをキャッシュから取る、無ければ作る → dir_mapからファイル探索」といったところ。

ここで問題になるのが、dir_mapが正しいことを誰も保証してくれないこと。dir_mapにキーがなければディレクトリを読んでくれるが、古いデータがdir_mapに入っていた時にそれを更新する仕組みがない。


■経緯の妄想

ここから先は調べるのが面倒なので、憶測だけ書き散らかす。誰か詳しく知っていたら教えてください。

  • ある時点まではApp::__findはディレクトリ再帰的にたどる機能を持っていなかった
  • なので、dir_mapが更新されるのはアプリケーションの設計が大きく変わる時だけだった
  • 「それぐらい変わる場合はキャッシュを手動で消せよ」で済んでいた
  • ある時からApp::__findがrecursiveをサポートするようになった
  • dir_mapが更新されるべきケースが出てきた
  • 面倒なので、あるいはそういうケースは特殊なので放置している

App::__findでControllerも再帰的に探索できるという情報が広まっていないようなので、この機能はCake的には位置付けとして重要でないのかも知れない。

そもそも、この機能はドキュメントに載っていないんじゃないかと思ったが、あらためて探してみると、公式ドキュメントの「2.4.1 ファイルとクラス名の規約」に

各ファイルは、 app フォルダ内のそれぞれ適切なフォルダの中かその下(サブフォルダも可)に設置します。

とオマケ程度に触れられているのが確認できた。


このdir_mapが更新されない問題は、1ヶ月ぐらい前の2.0のブランチでも変わっていなかったので、このあたりは「お前がキャッシュ消せば済む話」という扱いなのかも知れない。最新版ではコードの構成が根本的に変わっていたので追いきれていない。


■まとめ