admin generatorの編集後のリダイレクト先変更

admin generatorのeditアクションで、編集後に再度編集画面ではなく、リストに飛ばしたい要件があったのでメモ。例によってSymfony 1.4+Doctrine+admin generator。

class hogeActions extends autoHogeActions
{
    public function executeUpdate(sfWebRequest $request)
    {
        $this->forward404Unless($request->isMethod('put'));
        
        $this->hoge = $this->getRoute()->getObject();
        $this->form = $this->configuration->getForm($this->hoge);
        
        if ($this->form->bindAndSave($request->getParameter($this->form->getName()), $request->getFiles($this->form->getName()))) {
            $this->redirect('hoge');
        }
        
        $this->setTemplate('edit');
    }
}

executeUpdateをオーバーライドすればOKのようです。ここではisMethodがputなことに注意。


Symfonyでのsave時に独自の処理を挟む

Symfony 1.4+Doctrineで、admin generatorな環境です。editなど、自動生成なので、どこをオーバーライドすれば目的の動作になるのかわかりにくいことがありますね。

例えば、あるモデルのステータスを変更すると同時に、他のモデルに対しても操作したい場合があるとします。

まぁ、mergeFormとかembedFormとか使ってもいいのですが、今回は他テーブルのプライマリキーをフォームから選択させたかったので、汎用性のあるdoSaveでの処理を追加してみました。

symfony Forms in Action | 第11章 – Doctrine との統合 | symfony | Web PHP Framework

class BackendHogeForm extends HogeForm
{
    public function configure()
    {
        parent::configure();

        // 使用するフィールドを指定
        $this->useFields(array('status'));
        
        // 別モデルのIDリストを選択するフォームを作成する
        $choices = array(100 => '100:value1', 101 => '101:value2');
        $this->widgetSchema['other_model_id'] = new sfWidgetFormSelect(array('choices' => $choices));
        $this->validatorSchema['other_model_id'] = new sfValidatorChoice(array('choices' => array_keys($choices)));
    }

    protected function doSave($con = null)
    {
        if ($this->getValue('status') == 'DONE') {
            Doctrine::getTable('OtherModel')->find($this->getValue('other_model_id'))
                ->setIsSelected(TRUE)
                ->save();
        }
        return parent::doSave($con);
    }
}

基本的にはdoSaveをオーバーライドするだけです。ちなみにuseFieldsメソッドを使うと必要なフィールド以外を全部unsetしてくれるので便利です。


PHPのDateTimeの結果が-0001-11-30 00:00:00になる現象について

mySQLなどで、日時を0000-00-00 00:00:00としてデータベースに格納しておくことがありますが、これを読み込んでそのままPHPのDateTimeオブジェクトに渡すと、出力が-0001-11-30 00:00:00になってしまいます。

$a = new DateTime('0000-00-00 00:00:00');
echo $a->format('Y-m-d H:i:s');
// Output: -0001-11-30 00:00:00

PHP :: Bug #42971 :: DataTime::format(): not well formated data ‘0000-00-00 00:00:00’

で、これはバグではないと言われているので、どういうことかと考えてみると、0000-00-00は存在しない0月0日を指定しているので、0月は繰り下がって-1年12月0日、さらに0日も繰り下がって-1年11月30日、となるわけですね。

データベースとPHPの文化の違い、というところでしょうか。

ちなみにDateTime型、コンストラクタにNULLを渡すと現在時刻のインスタンスが生成されるので、データベースの値をNULLにしておくと、現在時刻になってしまいます。うーん…symfonyのDoctrineでNULL判定したい場合はどうすればいいんだ…

$row->getDateTimeObject('deleted_at')

みたいなことがやりたいのですが。

sfDoctrineRecordも

    $type = $this->getTable()->getTypeOf($dateFieldName);
    if ($type == 'date' || $type == 'timestamp')
    {
      return new DateTime($this->get($dateFieldName));
    }

こうなってるからオーバーライドするしかないのかな。


SymfonyのAdmin generatorでリストにフィルタを表示させない方法

Admin generatorを使っていて、リスト表示をするときにフィルタが不要な場合があります。例えば行が少ないとか、すでに決まったフィルタが別にある場合など。フィルタのフォーム自体を消したい場合は以下の方法で。

apps/backend/modules/MODULE_NAME/templates/

_filters.php
を空で作成すればいいだけです。

admin generatorでの開発とはキャッシュを読む作業と見つけたり…

追記

コメントで教えていただいた方法で、もっと簡単に実現できました。filterのclassをfalseにするだけでOKです。

generator:
  class: sfDoctrineGenerator
  param:
    config:
      filter:
        class: false


ImageMagickのPDF変換用gsコマンドを差し替える

例によってRedHat EL4にて、yumでconvertを入れたものの、バージョンが6.4.6と古く、Powerpointで作成したPDFがエラーになる。

エラー内容はこんな感じ。

$ convert -channel RGB ./test.pdf ./test.jpg

   **** This file has a corrupted %%EOF marker, or garbage after the %%EOF.
   **** The file was produced by Microsoft? PowerPoint? 2010:
   **** please notify the author of this software
   **** that the file does not conform to Adobe's published PDF
   **** specification.  Processing of the file will continue normally.

Error: /rangecheck in --.buildshading2--
Operand stack:
   --dict:8/8(L)--   --dict:3/5(L)--   --nostringval--   false   --nostringval--   --nostringval--   --nostringval--   --nostringval--   --dict:3/4(L)--   --nostringval--   --dict:5/5(L)--
Execution stack:
   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   --nostringval--   false   1   %stopped_push   1   3   %oparray_pop   1   3   %oparray_pop   1   3   %oparray_pop   --nostringval--   2   1   2   --nostringval--   %for_pos_int_continue   --nostringval--   --nostringval--   --nostringval--   --nostringval--   1   %stopped_push   --nostringval--   --nostringval--   --nostringval--   1   %stopped_push   --nostringval--   --nostringval--   %array_continue   --nostringval--   false   1   %stopped_push   --nostringval--   %loop_continue   --nostringval--   --nostringval--   --nostringval--   3   10   %oparray_pop   --nostringval--   --nostringval--   false   1   %stopped_push   --nostringval--   --nostringval--
Dictionary stack:
   --dict:1069/1123(ro)(G)--   --dict:0/20(G)--   --dict:93/200(L)--   --dict:93/200(L)--   --dict:97/127(ro)(G)--   --dict:229/230(ro)(G)--   --dict:19/24(L)--   --dict:4/6(L)--   --dict:19/20(L)--   --dict:3/5(L)--
Current allocation mode is local
GNU Ghostscript 7.07: Unrecoverable error, exit code 1
convert: Postscript delegate failed `./test.pdf': No such file or directory @ pdf.c/ReadPDFImage/612.
convert: missing an image filename `./test.jpg' @ convert.c/ConvertImageCommand/2710.

で、見る限り、GhostScriptのエラーのようなので、gsのバージョンを調べてみると、

$ gs -v

GNU Ghostscript 7.07 (2003-05-17)

Copyright (C) 2003 artofcode LLC, Benicia, CA.  All rights reserved.

というわけで、GhostScriptのコマンドの最新版を別にインストールして対応。その際、元のgsコマンドを入れ替えるわけにいかなかったので、ImageMagickから読み込むgsコマンドのファイルだけを指定できないかと思ったら、ImageMagickはXMLでコマンドを定義していたんですね。

ImageMagick • View topic – manually set delegate path to gs (ghostscript)

各種コマンドのオプションがImageMagick-6.4.6/config/delegates.xmlに記述されています。

gsコマンドの箇所を絶対パスに書き換えてやるだけで完了。便利。


Symfonyでメール送信のデバッグをする

Symfony1.4で、メールを送信するコードで、開発中にテンプレートの動作確認をしたい場合があります。Swift Mailerを使っているなら、すべての送信メールのあて先を上書きして特定のメールアドレスにすることができるので、やり方メモ。

The More with symfony book | メール | symfony | Web PHP Framework

設定の仕方は簡単で、アプリケーションのconfig\factories.ymlにdelivery_strategy: single_addressを指定すればOK。同時にdelivery_addressに、あて先メールアドレスを記述します。

dev:
  mailer:
    param:
      delivery_strategy: single_address
      delivery_address: dev@localhost

これで開発環境のローカルユーザdevにメールが送られます。

元々のヘッダ指定 toccbcc のどれに送られたメールなのかを確認できるよう、それぞれ X-Swift-ToX-Swift-CcX-Swift-Bcc というヘッダが追加されます。

あとはローカルのメールをMewとかで読めばOKです。

もちろんdelivery_addressを外部のメールアドレスに指定してもいいですよ。

PayPalの開発環境みたいにメールのSandBoxとか統合されないかなぁ。


複数の入力欄にまたがるValidationのエラーを特定の入力欄に表示させたい場合

sfFormでSymfony 1.4のお話。

PostValidatorのエラーメッセージをどこに出せばいいんだ?という状況で使用できます。

class SomethingInputForm extends BaseForm
{
    public function configure()
    {
        // ...
        $this->validatorSchema->setPostValidator(
            new sfValidatorCallback(array('callback' => array($this, 'myCallbackFunc')))
        );
    }

    public function myCallbackFunc($validator, $values) {
        // 何かバリデーション
        if (!($values['input1'] == $values['input2'] == $values['input3'])) {
            $error = new sfValidatorError($validator, 'error message ...');
            throw new sfValidatorErrorSchema($validator, array('input1' => $error)); 
        }
        
        return $values;
    }
}

こんな感じで、input1のエラーとして出力できるので、テンプレートのエラー表示が楽になります。


Doctrine_Collectionをループする

Symfony 1.4+Doctrine
やたら不便(だと個人的には思っている)なDoctrineの、SELECTに関するメモ。

やりたかったことは、ある条件で抽出した複数行に1行ずつ処理を加えて書き戻すというフロー。
fetchArray()とかでは配列しか返ってこないので意味がなく、fetchOne()では先頭行しか返ってこない。
fetchOne()を複数回実行すればいいのかと思ったら無限ループに陥った。

findByではorderByをするのにフックを使わないといけないらしいのでパス。

一応、以下の方法で解決しました。

$collection = Doctrine_Query::create()
    ->select('u.*')
    ->from('Users u')
    ->where('u.flag = ?', '1')
    ->orderBy('u.id')
    ->execute();

var_dump(get_class($collection)); // Doctrine_Collection

$iter = $collection->getIterator();
var_dump(get_class($iter)); // ArrayIterator

while ($record = $iter->current()) {
    var_dump(get_class($record)); // Users
    var_dump($record->getId());
    $iter->next();
}

next()呼ばないといけないのは面倒だなと思ったら、foreachで使えたらしい。

$collection = Doctrine_Query::create()
    ->select('u.*')
    ->from('Users u')
    ->where('u.flag = ?', '1')
    ->orderBy('u.id')
    ->execute();

var_dump(get_class($collection)); // Doctrine_Collection

$iter = $collection->getIterator();
var_dump(get_class($iter)); // ArrayIterator

foreach ($iter as $record) {
    var_dump(get_class($record)); // Users
    var_dump($record->getId());
}

これでかなりマシになった。

最終的にはこれで。

$collection = Doctrine_Query::create()
    ->select('u.*')
    ->from('Users u')
    ->where('u.flag = ?', '1')
    ->orderBy('u.id')
    ->execute();

var_dump(get_class($collection)); // Doctrine_Collection

foreach ($collection->getIterator() as $record) {
    var_dump(get_class($record)); // Users
    var_dump($record->getId());
}

あとはループ内で$record[‘data’] = ‘hoge’;$record->save();とかしておけばOK。

追記:2010/03/30

全然違った。もっと簡単にできました。getIterator不要でした。

foreach ($collection as $record) {
    $record['data'] = 'hoge';
    $record->save();
}

慣れてくると意外と便利な気がしなくもないDoctrine。結合系と特に。


DELLモニタ2407WFP電源ボタンの直し方

DELLの2407WFPというモニタをずっと使っているのだけど、1年くらい前から電源ボタンを押しても反応しない状況になって困っていました。

幸い電源は入った状態だったので、以降は入力切替をS-VIDEOなどにして、自動スタンバイさせていました。

電源ボタンは、押してもクリック感がなく、最初から押し込まれているような状態。

分解して直そうと試みるも、裏のネジを外した後の分解方法がわからず…

同等の現象を検索してみたところ、

価格.com – 『皆さんのモニターの主電源ボタン壊れませんか?』 DELL 2408WFP のクチコミ掲示板

電源ボタンマークの「|」の部分が
時計でいう12時の向きにぴったりあってないとクリック
できません。

!?

確かに自分のモニタを見ると電源ボタンのマークが斜めになっている…

P1030002_s

テープなどで電源ボタンを粘着させて回転させてみる…

P1030001_s

できたーーーー!

発見した人すごいな。

今はポストイットで固定してます。

P1030003_s


Linuxで複数ユーザで使えるリモートデスクトップ環境を作る

今回やりたかったのは、社内で複数の人が一台の共有マシンを使って、とある時間のかかる処理をするための環境作成です。

最初はxinetd+Xvfb+x11vncでgdmをあーやってこーやって…って色々試していたのですが、Windowsのリモートデスクトップ接続をクライアントに使えた方が便利だなぁとか思ってRDPで転送する方法がないか調べたところ、むしろさっきまで小難しくやろうとしていたすべてをまるっとまとめてやってくれるxrdpというものを発見!

これならシンクライアントっぽいものが一瞬で作れる!ということでさっそく導入してみたので手順解説します。

WS000001

このxrdp、デフォルトのままならリモートデスクトップユーザからシステムに変更を加える作業はできないので、今回の用途に最適です。

xrdp | Get xrdp at SourceForge.net

ユーザの追加も、通常通りLinuxにユーザ追加するだけで、リモートデスクトップログインできるようになります。

VNCに対する利点は多分こんなところ

  • 画像解像度がクライアント側から変更できる
    WS000000
  • Windowsユーザに追加クライアントをインストールしてもらう必要がない

設定にあたってはこちらのブログのエントリが大変参考になりました。これがないとまともにキーボード使えませんでした。

サーバはUbuntu Server 9.10(Karmic Koala)を使いました。別にUbuntuデスクトップ版でも良かった説。

とりあえずインストール時にこれらを選択しておきます。インストール後なら$ sudo taskselでもOK。

  • Basic Ubuntu server
  • OpenSSH Server
  • Ubuntu desktop

xrdpですが、aptやSourceForgeからtar.gzでダウンロードできるものはバージョンが古く、Windows7のリモートデスクトップ接続クライアントに対応していません。なので、cvsレポジトリから最新を持ってくる必要があります。

今回チェックアウトしたものは2009-12-26に最終更新されたバージョンです。

まずは下準備とチェックアウトまで。

$ sudo aptitude update
$ sudo aptitude install cvs autoconf libtool libssl-dev libpam0g-dev libx11-dev libxfixes-dev tightvncserver scim scim-anthy
$ cd ~/
$ mkdir xrdp
$ cd xrdp
$ cvs -d:pserver:anonymous@xrdp.cvs.sourceforge.net:/cvsroot/xrdp login
$ cvs -z3 -d:pserver:anonymous@xrdp.cvs.sourceforge.net:/cvsroot/xrdp co .
$ cd xrdp

で、前述のブログを参考にいくつかファイルを修正します。

$ vi xrdp/lang.c
          if ((code >= 0) && (code < 128))


          if ((code >= 0) && (code < 135))

に。

$ vi xrdp/xrdp_types.h
    struct xrdp_key_info keys_noshift[128];
    struct xrdp_key_info keys_shift[128];
    struct xrdp_key_info keys_altgr[128];
    struct xrdp_key_info keys_capslock[128];
    struct xrdp_key_info keys_shiftcapslock[128];

    struct xrdp_key_info keys_noshift[135];
    struct xrdp_key_info keys_shift[135];
    struct xrdp_key_info keys_altgr[135];
    struct xrdp_key_info keys_capslock[135];
    struct xrdp_key_info keys_shiftcapslock[135];

に。

$ ./bootstrap
$ ./configure
$ make
$ sudo make install

次は日本語キーボード用にキーマップを作成します。キーマップファイルは/usr/local/bin/xrdp-genkeymapを使って作ることもできるのですが、「\」や「_」のキーが効かなくなる現象があるので、さきほどのエントリからコピーしてきます。

とは言ってもファイル単体では公開されていないので、xrdp-0.5.0-instfiles.patchから当該部分を引っこ抜いてくる形になります。

ちなみに自分で生成する場合は、GUI上の端末から(ここ重要)
$ sudo /usr/local/bin/xrdp-genkeymap /etc/xrdp/km-0411.ini
でOKです。

それ以外は普通にSSHから作業できます。認識する名前別にキーマップの複製。lnでもいいかも。

$ cd /etc/xrdp/
$ sudo cp km-0411.ini km-e0200411.ini
$ sudo cp km-0411.ini km-e0210411.ini
$ sudo cp km-0411.ini km-e0010411.ini
$ sudo vi /etc/xrdp/startwm.sh

startwm.shの内容は前述のエントリからまるごとコピペして上書きします。が、Ubuntuで試したところ、ログイン後にキー配列がめちゃくちゃになる現象(「qwerty」が「c.gvn 」になる)があるので、一行追加して対策します。

export XKL_XMODMAP_DISABLE=1

を、for WindowManager in $SESSIONSの前にでも入れておきます。

で、あとは起動するだけです。

$ sudo /etc/xrdp/xrdp.sh start

WS000002

複数のユーザが同時にログインしても問題なく動作します。1200×1920とかの解像度でもストレスなく作業できました。

Mac OS Xな人々にはRemote Desktop Connection Client for Mac 2もあるのでばっちりです。

追記:あ、右シフトキーが効かない現象がありました。謎。