WEBアプリケーション研究室 開発ノート TOP

WEBアプリケーション研究室 開発ノート 2009年02月

スポンサーサイト

-------- --:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

    このエントリーをはてなブックマークに追加

以前「他アプリケーションのパーシャルを取得する」で他のアプリケーションのContextを作成してパーシャルを取得する方法を紹介しましたが、微妙に重そうだったのでSmartyのfetch()でテキストを取得するのを試して速度を比べてみました。Smartyの設定は


$this->force_compile = false;
$this->compile_check = false;
で試したコードは

for($i = 0; $i < 1000; $i++)
{
$app = new sdApplication('common');
$value = $app->getPartial('mail/message_body', array('message'=>$message));
}

for($i = 0; $i < 1000; $i++)
{
$smarty = new sdSmarty();
$smarty->assign('message', $message);
$value = $smarty->fetch('mail/message_body.tpl');
}
Smarty自体のインスタンスもシングルトンになっていません。毎回新しいインスタンスをnewしています。結果は

sdApplication
time : "0.6793sec"
memory : "5448.7734375KB"

sdSmarty
time : "0.2171sec"
memory : "5393.1953125KB"
やっぱりContextが思いのでしょうか?うちの環境ではこんな感じになりました。今回はSmartyを採用しました。

    このエントリーをはてなブックマークに追加

preg_matchの3番目引数に変数を渡すと、その変数でマッチしたテキストを参照できます。パターン内で()でグルーピングするとそれぞれのグループに$matches[1]とか$matches[2]でアクセスできます。


$str = 'foobar: 12345';
preg_match('/(\w+): (\d+)/', $str, $matches);

var_dump($matches[1]);
var_dump($matches[2]);
これで問題ないのですが、()のグループが多いと何番目とかわかりづらいし、後で増えたりするとめんどくさいです。そこでパターンに下のように書くと連想配列を受け取れます。

$str = 'foobar: 12345';
preg_match('/(?P<name>\w+): (?P<digit>\d+)/', $str, $matches);

var_dump($matches['name']);
var_dump($matches['digit']);

$matchesの中には元の通りの数字の配列も入ってるので、テキストが多い場合多用するとメモリの無駄遣いになるかも・・・
まあ、気にするほどでもないと思うけど。

    このエントリーをはてなブックマークに追加

例えばsfWidgetFormPropelSelectのCriteriaをactionから変更したい時は


$this->form = new ReplyForm();

$widget = $this->form['template_id']->getWidget()->setOption('criteria', $c);

ArrayAccessがimplementsしてあるので、こんな風にアクセスします。
get系のメソッドを一所懸命探してもありません。

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加

actionからパーシャルを取得するのは


$this->getPatial('module/action',array('var'=>$var));
みたいにして取得できますが、メールのテンプレートで、バックエンドとフロントエンドで、共通に使用したいパーシャルがあって、他のアプリケーションのパーシャルを取得できないかと色々試してみました。
パーシャルを取得するのは

$view = new sfPartialView($context, $moduleName, $actionName, '');
$view->setPartialVars($vars);

return $view->render();
のようにsfPartialViewを使えば簡単に取れるのですが、$contextが必要なので他アプリケーションのContextを作る必要があります。
また、一度他のContextを取得するとアクティブなContextが切り替わってしまうので使用した後戻す必要があります。

//現在のアプリケーション名を取得
$current_app = sfContext::getInstance()->getConfiguration()->getApplication();

if(!sfContext::hasInstance($app_name))
{
$debug = sfContext::getInstance()->getConfiguration()->isDebug();
$env = sfContext::getInstance()->getConfiguration()->getEnvironment();
sfContext::createInstance(
ProjectConfiguration::getApplicationConfiguration($app_name, $env, $debug)
);
}

//sfContext::createInstancで新しいアプリ用になっているので一回きりならいりません。
//2度目は新しいほうに切り替える必要があります。
sfContext::switchTo($app_name);

//Contextを取得
$context = sfContext::getInstance($app_name);

$view = new sfPartialView($context, $moduleName, $actionName, '');
$view->setPartialVars($vars);

$body = $view->render();

//元に戻す
sfContext::switchTo($current_app);
sfContext内に複数のインスタンスを持てるようになってるので想定内の使い方だとは思うのですが、なんか微妙な感じですね。実際にはclassを作ってマジックメソッド__callでsfContext::switchToをメソッド呼び出しの前後に行うようにしてみました。

class sdApplication
{
private
$context,
$main_app,
$target_app;

public function __construct($app_name)
{
$this->main_app = sfContext::getInstance()->getConfiguration()->getApplication();
$this->target_app = $app_name;

if(!sfContext::hasInstance($app_name))
{
$debug = sfContext::getInstance()->getConfiguration()->isDebug();
$env = sfContext::getInstance()->getConfiguration()->getEnvironment();
sfContext::createInstance(
ProjectConfiguration::getApplicationConfiguration($app_name, $env, $debug)
);
}

$this->context = sfContext::getInstance($app_name);
sfContext::switchTo($this->main_app);
}

protected function _getPartial($arguments)
{
$templateName = $arguments[0];
$vars = $arguments[1];

$sep = strpos($templateName, '/');
$moduleName = substr($templateName, 0, $sep);
$actionName = '_'.substr($templateName, $sep + 1);

$view = new sfPartialView($this->context, $moduleName, $actionName, '');
$view->setPartialVars($vars);

return $view->render();
}

public function __call($method, $arguments)
{
sfContext::switchTo($this->target_app);
$method = '_'.$method;
$value = $this->$method($arguments);
sfContext::switchTo($this->main_app);

return $value;
}
}
テンプレートエンジンがアプリケーション全体と密接にかかわってるので単独で使用は微妙な感じですね。メール部分だけ、他のテンプレートエンジンを使用してもいいかもしれません。そのうちどっちが軽量か試してみるかも。(25日にSmartyを使って試してみました。そちらの記事もご覧ください

    このエントリーをはてなブックマークに追加

pluginを本家にあげてみたところ1.2系でうまくインストールできませんでした。


symfony plugin:install gmDumpVarPlugin
No release available for plugin "gmDumpVarPlugin"

package.xmlを正しくインストールできるものとよく見比べたところsymfonyのpackageのdependenciesに問題があるのがわかりました。
最初は

<dependencies>
<required>
<php>
<min>5.1.0</min>
</php>
<pearinstaller>
<min>1.4.1</min>
</pearinstaller>
<package>
<name>symfony</name>
<channel>pear.symfony-project.com</channel>
<min>1.1.0</min>
<max>1.2.4</max>
</package>
</required>
</dependencies>

と書いたのですが

<dependencies>
<required>
<php>
<min>5.1.0</min>
</php>
<pearinstaller>
<min>1.4.1</min>
</pearinstaller>
<package>
<name>symfony</name>
<channel>pear.symfony-project.com</channel>
<min>1.1.0</min>
<max>1.3.0</max>
<exclude>1.3.0</exclude>
</package>
</required>
</dependencies>

に直したらインストールできるようになりました。
<max>1.3.0</max>
はmaxのバージョンを含まないようですね。たぶん1.2.5とかにしてもインストールできそうですがsf系のpluginが上記のように書いてあったので真似をしました。

    このエントリーをはてなブックマークに追加

gmDumpVarPlugin


プラグインのページはこちら
以前アップしたGmDisplayAssignVarPluginも本家にアップしました。
使い方はreadmeを参照してください。
以前、GmDisplayAssignVarPluginを使用していただいた人は一旦アンインストールをしてからインストールしてください。

symfony plugin:uninstall GmDisplayAssignVarPlugin
symfony plugin:install gmDumpVarPlugin



[symfony 1.1.6]
[symfony 1.2.4]

    このエントリーをはてなブックマークに追加

gmPackageXmlBuilderPlugin


プラグインのページはこちら
pluginをいくつか作っていますが、どうせなら本家にアップしようとチャレンジしてみました。
以前GmPackageBuilderPluginという同じ機能のpluginをここにアップしましたが、本家を良く見てみると、プラグインの名前は一つを除いて小文字で始まっていました。名前に関する規約は「名前がPluginで終わっていること」という以外、特にそれらしい規約は無いのですがまあ、合わせたほうがいいかと思って名前を変更しました。

plugin登録にそれほど難しいことは無かったのですが、roleという設定があってこれをleaderから変えると編集が出来なくなってしまいます。で、メールサポートでしか戻してもらえません。
(じつはもう一個上げようとして、変えてしまって現在編集不能になってます。現在メールを出して待っているところです)

英語がめちゃくちゃでちょっと恥ずかしいですがreadmeを呼んでいただければ簡単に使えると思います。alpha版なので指定してインストールしてください。なんか、そうしてる人が多かったのでalphaにしてみましたがあんまり意味なさそうなので近々stableにします。以前、GmPackageBuilderPluginを使用していただいた人は一旦アンインストールをしてからインストールしてください。

symfony plugin:uninstall GmPackageBuilderPlugin
symfony plugin:install -s alpha gmPackageXmlBuilderPlugin


[symfony 1.1.6]
[symfony 1.2.4]

    このエントリーをはてなブックマークに追加

わけあってプロジェクト自体のディレクトリ名を変更しました。symfonyがどのsymfonyを見に行くか書いてる場所は


config/ProjectConfiguration.class.php

require_once '/home/sites/symfony/1.2/lib/autoload/sfCoreAutoload.class.php';
だけのようです。ここのパスを書き換えてやれば動きます。
また、propelを使用しているなら

config/propel.ini

propel.output.dir = /home/sites/www.example.com
このディレクトリも変えてやります。
また、これを利用すれば一つのサーバーで複数のバージョンをサイトによって使い分けたり出来ます。上の例だと/home/sites/symfony/1.2に1.2を、/home/sites/symfony/1.1に1.1を入れています。で、init-project時にそれぞれホームへ移動して

/home/sites/symfony/1.1/data/bin/symfony init-project name
/home/sites/symfony/1.2/data/bin/symfony init-project name
とやって、コマンドを打つ時は

./symfony コマンド
でOKです。


[symfony 1.1.6]
[symfony 1.2.4]

    このエントリーをはてなブックマークに追加

このプラグインは本家にアップしました。名前もgmDumpVarPluginに変更しました。
今後こちらはアップしません。
新しいほうをご利用ください。

GmDisplayAssignVarPlugin


私の会社ではデザイナーがテンプレートへの変数の組み込みまでをします。その時割り当てられたる変数をいちいちリスト化して渡すのも面倒なので、画面に表示してみようと思いました。

変数がオブジェクトの時はpublicのメソッドが見れます。返り値は見れません。
getterだったら値を取って表示しようかと思ってやってみましたが、無差別にgetterを呼ぶのも危険だと思ったのでとりあえず見送りました。

インストール
symfony plugin-install http://plugin.gomo.jp/plugins/GmDisplayAssignVarPlugin/GmDisplayAssignVarPlugin-1.0.2.tgz
アップグレードは
symfony plugin-upgrade http://plugin.gomo.jp/plugins/GmDisplayAssignVarPlugin/GmDisplayAssignVarPlugin-1.0.2.tgz

cssとimagesの入ったフォルダをシンボリックリンクする。
ln -s ~/plugins/GmDisplayAssignVarPlugin/web/GmDisplayAssignVar ~/web/GmDisplayAssignVar

~apps/app_name/filters.ymlに
rendering: ~
security: ~

# insert your own filters here
display_var:
class: GmDisplayAssignVarFilter

cache: ~
common: ~
execution: ~

//以下の環境のときのみ表示されます。
$configuration = ProjectConfiguration::getApplicationConfiguration('app_name', 'dev', true);
変数の内容を表示していた部分を単独クラスに出しました。
var_dumpだとブラウザでHTMLソースを表示しないと見づらいし、複雑にネストしたオブジェクトはとても重いので、ちょっと変数をのぞくときに使うといいかもしれません。

GmDebug::dump($var, $key);


1.0.2GmDebug::dump($var, $key)を追加
1.0.1スタイルシートを微調整。

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加

テンプレートでリクエストパラメータを取得するのに


$sf_request->getParameter('name');
と書く代わりに

$sf_params->get('name')
と書けます。$sf_paramsは通常はsfParameterHolderのインスタンスでgetの2番目の引数はデフォルト値です。ところが、エスケーピングをOnにすると

escaping_strategy: on
escaping_method: ESC_SPECIALCHARS
$sf_paramsはsfOutputEscaperObjectDecoratorのインスタンスになります。このgetメソッドの2番の引数はエスケーピング方法でデフォルト値ではありません。
バグなのか意図したことなのかわかりませんが、使いづらいので報告したほうがよさそうです。

$sf_request->getParameter('name');
を使えばどっちの時もデフォルト値なので回避できそうです。

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加

doctrineでモデルを保存しようとしたとき
validator failed on カラム名 (type)のエラー
というエラーが出て、保存ができませんでした。
結論から言うとよくマニュアルを見ていなかっただけなのですが、schemaでmysqlでいうところのvarcharのtypeを指定する時、propelは


name: {type: varchar(255), notnull: true}
でよかったのですがdoctrineでは

name: {type: string(255), notnull: true}
と指定します。doctrineにvarcharという型はないっぽいのでエラーでも出してくれればいいと思うのですが。

[symfony 1.2.4]

    このエントリーをはてなブックマークに追加

まずsfWidgetFormSelectで生成されるselectタグにoptgroupを追加する方法ですが


$choices = array(
'Europe' => array('France' => 'France', 'Spain' => 'Spain', 'Italy' => 'Italy'),
'America' => array('USA' => 'USA', 'Canada' => 'Canada', 'Brazil' => 'Brazil'),
);

$this->widgetSchema['country'] = new sfWidgetFormSelect(array('choices' => $choices));
このようにグループ名をキーにした連想配列をchoicesに渡せば出来ます。
sfWidgetFormPropelSelectはsfWidgetFormSelectの子クラスでchoicesに配列を返すメソッド(getChoices)をセットしてるのでsfWidgetFormPropelSelectを継承したクラスを作ってgetChoicesをオーバーライトしてやれば簡単です。

class sdWidgetFormPropelSelectOptgroup extends sfWidgetFormPropelSelect
{
protected function configure($options = array(), $attributes = array())
{
parent::configure($options, $attributes);
$this->addRequiredOption('optgroup_model');
$this->addOption('optgroup_method', '__toString');
}

public function getChoices()
{
$choices = array();
if (false !== $this->getOption('add_empty'))
{
$choices[''] = true === $this->getOption('add_empty') ? '' : $this->getOption('add_empty');
}

$class = $this->getOption('model').'Peer';

$criteria = is_null($this->getOption('criteria')) ? new Criteria() : clone $this->getOption('criteria');
if ($order = $this->getOption('order_by'))
{
$method = sprintf('add%sOrderByColumn', 0 === strpos(strtoupper($order[1]), 'ASC') ? 'Ascending' : 'Descending');
$criteria->$method(call_user_func(array($class, 'translateFieldName'), $order[0], BasePeer::TYPE_PHPNAME, BasePeer::TYPE_COLNAME));
}

$peer_method = 'doSelectJoin'.$this->getOption('optgroup_model');
$this->checkExistMethod($class, $peer_method);
$objects = call_user_func(array($class, $peer_method), $criteria, $this->getOption('connection'));

$method = $this->getOption('method');
$this->checkExistMethod($this->getOption('model'), $method);

$optgourp_method = $this->getOption('optgroup_method');
$this->checkExistMethod($this->getOption('optgroup_model'), $optgourp_method);

$optgourp_getter = 'get'.$this->getOption('optgroup_model');
$this->checkExistMethod($this->getOption('model'), $optgourp_getter);

foreach ($objects as $object)
{
$optgourp = $object->$optgourp_getter()->$optgourp_method();
$choices[$optgourp][$object->getPrimaryKey()] = $object->$method();
}

return $choices;
}

private function checkExistMethod($class, $method)
{
if (!method_exists($class, $method))
{
throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $class, $method, __CLASS__));
}
}
}
使う時はこんな感じです。

$this->widgetSchema['area'] = new sdWidgetFormPropelSelectOptgroup(array(
'model' => 'Area',
'optgroup_model'=>'MediumArea'
))

optgroup_methodオプションでtoString以外のメソッドを呼び出せます。
並び順は、order_byオプションは複数渡せないようなのでcriteriaにorderをセットしてから渡せばいいと思います。

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加

GmAdminGeneratorPlugin


admin generatorのテーマを作ってみたのでアップします。

インストール&使用方法

symfony plugin-install http://plugin.gomo.jp/plugins/GmAdminGeneratorPlugin/GmAdminGeneratorPlugin-1.4.8.tgz

すでにインストールしてあってupgradeする場合は
symfony plugin-upgrade http://plugin.gomo.jp/plugins/GmAdminGeneratorPlugin/GmAdminGeneratorPlugin-1.4.8.tgz

cssとimagesの入ったフォルダをシンボリックリンクする。
ln -s ~/plugins/GmAdminGeneratorPlugin/web/GmAdminGenerator ~/web/GmAdminGenerator

symfony propel-init-admin --theme=gomo app module Class

■外部キーによるグルーピング
グルーピングは外部キーがURLに無かったときに404にしてしまう機能(limit)とセレクトタグを出して選ばせる機能(group)と二つあります。
limit

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

limit: district_id #グルーピングする外部キーカラム名

group

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

group:
column: district_id #グルーピングする外部キーカラム名
options_order: {column: sort_order, order: asc} #セレクトタグ内の並び順

1.3.0からlimitとgroupは同時に使えるようになりました。
■ドラッグアンドドロップによる並び替え

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

sort:
column: sort_order #並べ替えに使うカラム名を指定
submit_on_drop: off #ドロップと同時にセーブするか、ボタンでセーブするか


■リストのソートリンクを無効化

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

list:
link_for_sort: off


■強制的に今の日時で登録するチェックボックス
例:カラムcreated_atに自動取得のチェックボックスを出す。

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

edit:
fields:
created_at: {get_now_checkbox: 自動取得} #ラベル名を取得


■あるフォームの内容を他のフォームにコピーする。

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

edit:
fields:
frgn:
copy_value:
copy_field: name
regex: ^[A-Za-z0-9ぁ-んー]+$ #この正規表現にマッチするときだけコピーする
interval: 0.1 #チェックするインターバル


■重複登録を防ぐためにチェックする。

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

edit:
duplication_check:
observe_field: name #このカラムのフォームが変更されたら調べに行く
compare_field: frgn #このカラムのフォームの内容でDBを検索
interval: 0.5 #何秒おきに調べるか?
substring_matching: off #部分一致検索を使用するか?offなら前方一致検索


■多対多のリレーションで、ドラッグ&ドロップで並び替え

generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

relation:
title: "路線に駅を登録する" #ページの左上のタイトル
associated_list:
title: "この路線の駅" #関連付けられているリストのタイトル
unassociated_list:
title: "駅リスト" #関連付けられていないリストのタイトル
sort: frgn #リストのオーダーに使うカラム[frgn,desc]も可
search: #リストが多いときに検索できるようにする
field: frgn #検索に使うカラム
interval: 0.5
list_all_default: on #デフォルトで全ての項目を表示するか。offだと検索するまで何も出ません。
substring_matching: on
class: LineStation #中間テーブルのクラス
sort_column: sort_order #並び替え用カラム
submit_on_drop: on #ドロップと同時にセーブするか、ボタンでセーブするか

■多対1と多対多のadmin_select_listに検索


generator:
class: sfPropelAdminGenerator
param:
model_class: Line
theme: gomo

edit:
account_id:
search:
field: [name,desc] #必須です。検索対象カラムと並び順
interval: 0.5 #何秒おきに検索するか?
substring_matching: on #部分一致で検索する(%hoge%かhoge%か?)


Change Log
1.4.8symfonyのバージョンチェックを修正
1.4.7いくつかのPHPショートタグ <? を <?php に変更しました。
1.4.6limit・groupと同時にvalidateを使用するとエラーが出ていたので修正しました。
1.4.5'_*_hearder.php' と '_*_footer.php'にlimitとgourpをアサインしました。sortとrelationにheaderとfooterを追加しました。
1.4.4limitかgroupを使用した時リストでページングがうまく動いていなかったので修正しました。
1.4.3パーシャルとコンポーネントの時もsetterメソッドを作ってしまっていたので修正。純正テーマもそうなってるみたいです。
1.4.2style sheetを微調整しました。
1.4.1displayのcategoryを分けた時にエラーが出ていたので修正。edit.search.カラム名.list_all_defaultは動いていませんでした。実装が困難そうなので一旦削除。
1.4.0多対1と多対多のadmin_select_listに検索をつけました。
1.3.0limitとgroupを同時に使えるようにしました。
1.2.1listSuccessでgroup単独で使ったときprototype.jsを読み込み忘れていたので修正。
1.2.0外部キーでグルーピングをソートの他、編集やリストにも適応、あわせてソート単体ののグルーピングは削除しました。
1.1.0並び替え時に外部キーなどでグルーピングして並び替えも出来るようにしました。
1.0.3!is_null => issetへ
1.0.2メソッド名を動的にしなければならないところが、固定されていたバグを修正。主キーの名前がidに固定されていたので修正。
1.0.1ある条件下でUndefined variableのエラーが出ていたので修正

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加

例えば検索用マスターデータリストを表示する時、一番上に「こだわらない」等の未選択項目を追加したい時があります。そんな時はadd_emptyオプションで表示できます。


public function configure()
{
$this->setWidgets(array(
'area' => new sfWidgetFormPropelSelectMany(array('model' => 'Area')),
));
$this->widgetSchema['area']->setOption('add_empty','こだわらない');
}
しかし、1.1からのフォームシステムはよく出来ていますな。

[symfony 1.1.6]

    このエントリーをはてなブックマークに追加
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。