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の戻り値に影響する部分なんかは全然フォローが行き届いていなかったのですが、こうして使ってくださる方がいて、改善されるのは嬉しいものですね。

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

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

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

■これまでの歩み

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

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

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

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

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

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

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

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

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

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

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

■多分、少数派な考え方

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

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

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

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

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

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

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

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

●時間的な制約

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

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

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

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

●仕事の裁量

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

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

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

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

●情報管理統制

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

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

●仕事場的な制約

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

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

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

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

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

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

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

●孤独との戦い

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

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

●お金の話

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

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

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

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

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

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

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

●向き不向きの話

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

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

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

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

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もミラー出来ない。

ググッてみたら、cobblerのMLで既出でした。

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

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

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でちゃんと配列が返ってくるのを確認するというのがあるべき姿か。

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だったということ。

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

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オプションが有効化されたことが確認出来ます。

* Wed Jul 21 2010 Petr Pisar <[email protected]> - 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を使っている」という認識は関係者間でしっかり共有しておきましょう。