Hatena::ブログ(Diary)

てつじんにっき

2011-08-14

[][] Emacsphp-modeでalignする その2

4年越しですが Emacsのphp-modeでalignする - てつじんにっき で書いていたものの挙動が気になったので修正し、requireして使うよう改良してphp-align.elという名称でgithubにあげてみました。

no title


話が変わりますが最近php-modeは https://github.com/rradonic/php-mode を使っています。オリジナルに比べて色々と機能追加やバグ修正されているようです。*1

と、していたら https://github.com/ejmr/php-mode の方がEmacs同梱になるというのをブコメで知りました。

b:id:tomoya emacs, php

PHP5.4 対応版。php-mode-version-number は 1.5.1 になってる。と思ったら、あっという間に 1.6 へ。そして Emacs 同梱へ http://comments.gmane.org/gmane.emacs.devel/142507 2011/07/26

はてなブックマーク - ejmr/php-mode - GitHub

こちらも5.4対応など色々と機能追加されているようです。

乗り換えようかな。

*1:5.4のtraitも簡単なパッチを送ったらマージしてくれました

2011-06-05

[][][] PHPer.jpでSymfony2のblogチュートリアルを動かすためにやったこと

phper.jp | 523: Origin is unreachablePage not found · GitHub Pagesを動してみたので、雑なまとめですが、やった設定の備忘録。

最終的に下記のような構成に。

/path/to/approot/
 -  .phper/        # phper.jp用設定ファイル群
 -  Symfony/       # blogチュートリアルアプリケーション
 -  phpMyAdmin/    # 管理用phpMyAdmin

1. phper.jpに登録

http://phper.jp/quickstartを見てユーザ登録〜デプロイまで進める。

ドキュメントルートは Symfony/web にしておく。


2. phperコマンドのインストール

Ruby製のphperというCLIツールが用意されているので、no titleを参考にRuby環境整えて

$ gem install phper

で、phperコマンドをインストール


3. blogチュートリアルの作成

ローカルでblogチュートリアルを読み進めながら、/path/to/approot/Symfony下に作成。


4. phper.jp用に設定の修正


Symfony/web/app_dev.phpSymfony/web/config.phpのアクセス制限変更

no titleの構成を見るとフロントにproxyが居るので、

$_SERVER['REMOTE_ADDR']だけじゃなくて、$_SERVER['HTTP_X_FORWARDED_FOR']も見てやるようにする。

# /path/to/approot/Symfony/web/app_dev.php
# /path/to/approot/Symfony/web/config.php
<?php
...
$remoteIpAddresses = array_merge(
    (array)@$_SERVER['REMOTE_ADDR'],
    array_map('trim', explode(',', @$_SERVER['HTTP_X_FORWARDED_FOR']))
);
$allowIpAddresses  = array(
    '127.0.0.1',
    '::1',
    'your-global-ip-address', // 自分の接続元グローバルIP
);
if (count(array_intersect($remoteIpAddresses, $allowIpAddresses)) === 0) {
    header('HTTP/1.0 403 Forbidden');
    die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
...

■ .gitignore

下記をgitの管理対象外にする。

# /path/to/approot/Symfony/.gitignore
*/logs/*
*/cache/*
app/config/parameters.ini

parameters.iniはローカル、phper.jp上で異なるのでgit管理対象外として、下記のようにした。

# /path/to/approot/Symfony/app/config/parameters.phper.ini
[parameters]
    database_driver   = pdo_mysql
    database_host     = db.phper.jp
    database_name     = yourname_appname
    database_user     = yourname
    database_password = password

mysqlの接続情報などは、phper infoで確認できる。

$ phper info
yourname-appname
--> gitosis@git.phper.jp:yourname/appname.git
--> mysql://yourname:password@db.phper.jp/yourname_appname

5. phpMyAdminを用意する

MySQLコマンドラインクライアントが使えないのでphpMyAdminを入れる。

http://www.phpmyadmin.net/home_page/downloads.phpからDownloadして/path/to/approot/phpMyAdminに設置する。


また、できればphpMyAdminにはhttpsでアクセスしたい。

no titleを見ると、phper.jpではエッジレイヤのNginxがSSLアクセラレーションするようなので、

SSL-Proxy下でphpMyAdminSSLで動かす設定も入れておいた。


config.sample.inc.phpをコピーしてconfig.inc.phpを作成。下記を設定。

# /path/to/approot/phpMyAdmin/config.inc.php
<?php
..
// 書き換え
$cfg['Servers'][$i]['host'] = 'db.phper.jp';
// SSL-Proxy下でSSLで動かす設定。 http://wiki.phpmyadmin.net/pma/Config#PmaAbsoluteUri
$cfg['PmaAbsoluteUri'] = 'https://yourappname.phper.jp/pma/';

6. phper設定

プロジェクト直下の.phper/ディレクトリにいろいろとスクリプトや設定を記述できる。

詳しくはマニュアル参照。http://phper.jp/manual


■ deployの設定: /path/to/approot/.phper/deploy
#!/bin/sh
ROOT_DIR=$(cd $(dirname $0)/..; pwd)
# キャッシュの削除
rm -rf $ROOT_DIR/Symfony/app/cache/*
# parameters.iniのシンボリックリンク作成
ln -sf $ROOT_DIR/Symfony/app/config/parameters.phper.ini $ROOT_DIR/Symfony/app/config/parameters.ini

rsync除外設定: /path/to/approot/.phper/rsync_exclude.txt
Symfony/app/cache/*
Symfony/app/logs/*

アプリケーションサーバApacheの設定: /path/to/approot/.phper/httpd.conf

/pmaでphpMyAdminにアクセス。後、Digest認証も一応かけておく。

Alias /pma "/var/www/sites/yourname-appname/phpMyAdmin"
<Location pma>
    AuthType Digest
    AuthName "Secret Zone"
    AuthDigestDomain pma
    AuthDigestProvider file
    AuthUserFile  "/var/www/sites/yourname-appname/.phper/.htdigest"	
    Require valid-user
</Location>
$ htdigest -c /path/to/approot/.phper/.htdigest "Secret Zone" user
New password: ****
Re-type new password: ****

7. デプロイする

$ git push

8. phper.jp上のデータベースschema定義を流しこむ

blogチュートリアルschema定義をdumpして、phpMyAdminから流しこむ。

$ app/console doctrine:schema:create --dump-sql
ATTENTION: This operation should not be executed in an production enviroment.

CREATE TABLE Post (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, body LONGTEXT NOT NULL, createdAt DATETIME NOT NULL, updatedAt DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB

後はブラウザから動作確認して動くか確認。


実際はこんなにすんなり行かずに、トライアンドエラーを繰り返しました。PaaS楽しい!!

*1gitに登録するのも嫌だが他の方法が思いつかなかったので

2011-05-23

[][][] symfony1.4+Doctrineでbuild-formsで生成されるコードに独自WidgetやValidatorをセットする

開発が進むに連れてフォームで

  • 表示するエラーメッセージを全体的に変更したい
  • 全角英数を半角英数に変換したい
  • その他色々と機能を拡張したい

などと、全体的な挙動を変えたいことが多くて、

$ symfony doctrine:build-forms

で生成される、lib/form/doctrine/base/Base***Form.class.php 内のWidget, Validatorを独自拡張したものに変更できないかなと思う事が多く、調べてみました。*1

$ symfony doctrine:build-forms --generator-class=myDoctrineFormGenerator

と、build-formsの--generator-classにクラス名を渡してGeneratorクラスでいろいろとやれば良いのですが、

オプション付けるのを忘れたり、他の人も触ることを考えるとデフォルト値を置き換えられないかなーと思って調べました。

既存taskを継承すれば良いかなと思い継承しましたが下記のようなエラーが。

$ cat lib/task/myDoctrineBuildFormsTask.class.php 
<?php

class myDoctrineBuildFormsTask extends sfDoctrineBuildFormsTask
{
}
$ symfony doctrine:build-forms
PHP Fatal error:  Uncaught exception 'sfCommandException' with message 'The task named "doctrine:build-forms" in "myDoctrineBuildFormsTask" task is already registered by the "sfDoctrineBuildFormsTask" task.' in /usr/local/apps/example/lib/vendor/symfony/lib/command/sfCommandApplication.class.php:142

...と、taskの正しい拡張方法がわかりませんでした。



前置きが長くなりましたが無理やりオプション書き換えてみました。BKっぽいですがメモ。


config/ProjectConfiguration.class.php

command.filter_optionsイベントを捕捉して、generator-classを置き換えます。

<?php

require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->dispatcher->connect(
      'command.filter_options',
      array($this, 'commandFilterOptionsEventHook')
    );
  }

  public function commandFilterOptionsEventHook(sfEvent $event, $options)
  {
    $task = $event->getSubject();
    $parameters = $event->getParameters();
    $commandManager = $parameters['command_manager'];
    if ($task->getFullName() === 'doctrine:build-forms') {
      $commandManager->getOptionSet()
        ->getOption('generator-class')
        ->setDefault('myDoctrineFormGenerator');
    }
    return $options;
  }
}

lib/generator/myDoctrineFormGenerator.class.php

  • $extendedWidgetFormClasses
  • $extendedValidatorClasses

key=>valueに置き換えたいクラスの定義を書きます。

<?php

class myDoctrineFormGenerator extends sfDoctrineFormGenerator
{
  protected $extendedWidgetFormClasses = array(
    'sfWidgetFormDate' => 'myWidgetFormTextDate', // 日付のinput text入力
  );

  protected $extendedValidatorClasses = array(
    'sfValidatorInteger' => 'myValidatorInteger', // 全角英数->半角英数
    'sfValidatorDate'    => 'myValidatorDate',    // 全角英数->半角英数
  );

  public function getWidgetClassForColumn($column)
  {
    return $this->getExtendedClass(
      parent::getWidgetClassForColumn($column),
      $this->extendedWidgetFormClasses
    );
  }

  public function getValidatorClassForColumn($column)
  {
    return $this->getExtendedClass(
      parent::getValidatorClassForColumn($column),
      $this->extendedValidatorClasses
    );
  }

  protected function getExtendedClass($class, $extendedClasses)
  {
    return isset($extendedClasses[$class]) ? $extendedClasses[$class] : $class;
  }
}

として、やるとsymfony doctrine:build-formsしてやると、生成されるソース内のWidget,Validatorが置き換わります。

Person:
  columns:
    name: string(255)
    date_of_birth: date
    allowance: integer(4)
  public function setup()
  {
    $this->setWidgets(array(
      'id'            => new sfWidgetFormInputHidden(),
      'name'          => new sfWidgetFormInputText(),
      'date_of_birth' => new myWidgetFormTextDate(),
      'allowance'     => new sfWidgetFormInputText(),
    ));

    $this->setValidators(array(
      'id'            => new sfValidatorChoice(array('choices' => array($this->getObject()->get('id')), 'empty_value' => $this->getObject()->get('id'), 'required' => false)),
      'name'          => new sfValidatorString(array('max_length' => 255, 'required' => false)),
      'date_of_birth' => new myValidatorDate(array('required' => false)),
      'allowance'     => new myValidatorInteger(array('required' => false)),
    ));
  ...


ちなみに今回、例として置き換えてみたWidgetとValidatorは以下です。参考までに。

lib/widget/myWidgetFormTextDate.class.php

<?php

class myWidgetFormTextDate extends sfWidgetFormDate
{
  protected function renderDayWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '4';
    $attributes['maxlength'] = 2;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }
  protected function renderMonthWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '4';
    $attributes['maxlength'] = 2;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }

  protected function renderYearWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '6';
    $attributes['maxlength'] = 4;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }

  protected function renderWidgetFormInput($name, $value, $attributes) {
    $widget = new sfWidgetFormInput(array(), $attributes);
    return $widget->render($name, $value);
  }
}

lib/validator/myValidatorDate.class.php

<?php

class myValidatorDate extends sfValidatorDate
{
  protected function doClean($value)
  {
    if (is_string($value)) {
      $value = $this->filter($value);
    } else if (is_array($value)) {
      $value = array_map(array($this, 'filter'), $value);
    }
    return parent::doClean($value);
  }

  protected function filter($value)
  {
    return mb_convert_kana($value, 'a');
  }
}

lib/validator/myValidatorInteger.class.php

<?php

class myValidatorInteger extends sfValidatorInteger
{
  protected function doClean($value)
  {
    if ($value !== null) {
      $value = mb_convert_kana($value, 'a');
    }
    return parent::doClean($value);
  }
}

*1:デフォルトでセットされるsfValidatorString,sfValidatorInteger等を差し替えたい

2011-05-04

[][][] symfony1.4+Doctrineで配列をカンマ区切りで保存する

最近チェックボックスの項目が大量あるフォームを実装していて、綺麗に正規化して実装するのも大変だなーと思って、valueを「,」区切りで文字列保存することにしたのでメモ。

valueに「,」が入る可能性などは考えてないのであくまでもシンプルに。


config/doctrine/scheme.yml

schemeの定義例は以下。interestに「,」区切りで保存する。

Question:
  columns:
    name: string(255)
    interest: string(255)

config/ProjectConfiguration.class.php

sfDoctrineRecordを継承して、myDoctrineRecordを定義するのでそちらをmodel生成で読み込むように。

<?php

require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->enablePlugins('sfDoctrinePlugin');
  }

  public function configureDoctrine(Doctrine_Manager $manager)
  {
    sfConfig::set('doctrine_model_builder_options', array(
      'baseClassName' => 'myDoctrineRecord',
    ));
  }
}

lib/doctrine/myDoctrineRecord.class.php

implode, explodeするメソッドを定義。

<?php

class myDoctrineRecord extends sfDoctrineRecord
{
  protected function _getExplode($key, $separator = ',', $load = true)
  {
    $value = $this->_get($key, $load);
    if ($value === '' || $value === null) {
      return array();
    }
    return array_map('trim', explode($separator, $value));
  }

  protected function _setImplode($key, $value, $separator = ',', $load = true)
  {
    if (is_array($value)) {
      $this->_set($key, implode($separator, array_map('trim', $value)), $load);
    } else {
      $this->_set($key, '', $load);
    }
  }
}

コマンドラインでモデルとCRUD画面生成

$ symfony doctrine:build --all
$ symfony doctrine:generate-module frontend question question

lib/model/doctrine/Question.class.php

interestのgetter,setterは前述のmethodを通すようにする。

<?php

class Question extends BaseQuestion
{
  public function getInterest()
  {
    return $this->_getExplode('interest');
  }

  public function setInterest($value)
  {
    $this->_setImplode('interest', $value);
  }
}

lib/model/doctrine/QuestionTable.class.php

interestの定義

<?php

class QuestionTable extends Doctrine_Table
{
  protected static $interests = array(
    '1' => 'Music',
    '2' => 'Sports',
    '3' => 'Programming',
  );

  public static function getInterests()
  {
    return self::$interests;
  }

  public static function getInstance()
  {
    return Doctrine_Core::getTable('Question');
  }
}

lib/form/doctrine/QuestionForm.class.php

intersetのwidget,validatorをQuestionTable::getInterests()にする。

<?php

class QuestionForm extends BaseQuestionForm
{
  public function configure()
  {
    $intersets = QuestionTable::getInterests();
    $this->setWidget('interest', new sfWidgetFormChoice(array(
      'choices'  => $intersets,
      'multiple' => true,
      'expanded' => true,
    )));
    $this->setValidator('interest', new sfValidatorChoice(array(
      'choices'  => array_keys($intersets),
      'multiple' => true,
    )));
  }
}

とするとこうなって、新規作成・編集することができる。

f:id:Tetsujin:20110504144001p:image


getter,setterをオーバライドしているので、form側で特に意識することなくデフォルト値のセット、データの保存ができて便利かなと思っています。

他のプロジェクトでも使えそうな気がするので、scheme.ymlに書いたら自動的にやってくれるようにDoctrineのBehaviorにしてみたい。

2011-03-15

[][] symfonyのproject:deployがやたらと遅い

最近symfony1.4.9を使っていてproject:deployコマンドがやたら遅くて、rsyncってこんな遅かったかな?と思ったらsfFileSystem::execute()が、出力バッファを読み込む度に0.1秒sleepしている。

lib/task/sfFilesystem.class.php
<?php

  public function execute($cmd, $stdoutCallback = null, $stderrCallback = null)
  {
    $this->logSection('exec ', $cmd);

    $descriptorspec = array(
      1 => array('pipe', 'w'), // stdout
      2 => array('pipe', 'w'), // stderr
    );

    $process = proc_open($cmd, $descriptorspec, $pipes);
    if (!is_resource($process))
    {
      throw new RuntimeException('Unable to execute the command.');
    }

    stream_set_blocking($pipes[1], false);
    stream_set_blocking($pipes[2], false);

    $output = '';
    $err = '';
    while (!feof($pipes[1]) || !feof($pipes[2]))
    {
      foreach ($pipes as $key => $pipe)
      {
        if (!$line = fread($pipe, 128))
        {
          continue;
        }

        if (1 == $key)
        {
          // stdout
          $output .= $line;
          if ($stdoutCallback)
          {
            call_user_func($stdoutCallback, $line);
          }
        }
        else
        {
          // stderr
          $err .= $line;
          if ($stderrCallback)
          {
            call_user_func($stderrCallback, $line);
          }
        }
      }

      usleep(100000);
    }

    fclose($pipes[1]);
    fclose($pipes[2]);

    if (($return = proc_close($process)) > 0)
    {
      throw new RuntimeException('Problem executing command.', $return);
    }

    return array($output, $err);
  }


元々は、sleep(0.1)となっていたが、実際sleepは効いていなかったバグがあり、

ミリ秒ならusleepでしょ、という修正が入ったときに遅くなった模様。


deploy遅すぎるよ!とreopenしてpatchが添付されているが、特に対応されていない。

取り敢えず添付のpatchを適用すると速度改善されました。直前のfreadで読込めた場合はusleepしなくなります。deployのような出力が多いものだと結構変わりますね。