‘symfony’ タグのついている投稿

symfonyでlessを使えるようにするsfLESSPluginの使い方

Symfony 1.4で作ったWebサイトでbootstrapのカスタマイズをしたくなったのでlessのツールを探していたらsfLESSPluginという便利そうなものを見つけました。

Plugins | sfLESSPlugin | 1.1.0 | symfony | Web PHP Framework
https://github.com/everzet/sfLESSPlugin

lessファイルを置くだけでcssにコンパイルして表示できるプラグインです。

sfLessPhpPluginというものもあったようなのですが、sfLESSPluginになったようです。作者は同じです。

Plugins | sfLessPhpPlugin | 1.3.3 | symfony | Web PHP Framework

$ ./symfony plugin:install sfLESSPlugin

これでインストールはできるのですが、バージョン番号は同じもののgithubにあるファイルより古いので、githubから最新のファイルをインストールします。

ちなみに現時点で最新のコミットは以下ですが、PHP単体でlessをコンパイルできるlessphpが使えるようになっています。古いものはnode.jsが必要です。

https://github.com/everzet/sfLESSPlugin/commit/738e274ed131e9bdfad3bed7c2c4d031baf1511c

$ cd plugins/

$ git clone git://github.com/everzet/sfLESSPlugin.git
Cloning into sfLESSPlugin...
remote: Counting objects: 359, done.
remote: Compressing objects: 100% (176/176), done.
remote: Total 359 (delta 197), reused 317 (delta 161)
Receiving objects: 100% (359/359), 85.71 KiB | 131 KiB/s, done.
Resolving deltas: 100% (197/197), done.

$ cd ..

次にlessファイルを置くディレクトリを作ります。

$ mkdir web/less
$ cd web/less
$ wget https://github.com/twitter/bootstrap/zipball/v2.0.4 -O bootstrap.zip
$ unzip ./bootstrap.zip
$ mv ./twitter-bootstrap-41d3220/less/*.less ./
$ rm -R ./twitter-bootstrap-41d3220
$ rm ./bootstrap.zip
$ cd ../..

gitでダウンロードしたので、プラグインが自動的に有効になりません。

config/ProjectConfiguration.class.php

にenablePluginsを追加してプラグインを有効にします。

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

publish-assetsを実行してwebにプラグインのリンクを作ります。

$ ./symfony plugin:publish-assets

app/frontend/templates/layout.phpを編集します。

<?php use_helper('LESS'); ?>

を先頭に、

<?php include_less_stylesheets() ?>

を<?php include_stylesheets() ?>の代わりに指定します。

app/fronend/config/view.ymlにbootstrapのlessファイルを指定します。

stylesheets:    [bootstrap.less]

ちなみに、既存のcssも混在できます。

stylesheets:    [bootstrap.less, main.css]

その場合、cssファイルは今までどおり/cssから読み込まれます。

sfLESSPluginはブラウザサイドでlessファイルをless.jsで読み込んで解釈する方法と、サーバサイドでCSSとしてコンパイルしておく方法が使えます。

まずはブラウザサイドの動作を確認します。

ここまでで一度ブラウザで表示してみると、

/sfLESSPlugin/js/less-1.1.3.min.jsが404エラーになります。

plugins/sfLESSPlugin/web/jsを見ると、less-1.0.30.min.jsしか入っていないのでバージョンが違っています。

なぜかこのファイルは

plugins/sfLESSPlugin/lib/config/LESSConfig.class.php

でハードコーディングされています。

sfLESSのコンストラクタに独自のLESSConfigオブジェクトを渡せば変更できるようなのですが、それもハードコーディングされていたので諦めてLESSConfig.class.phpを書き換えてしまいます。

先にless.jsをダウンロードしてきます。

LESS « The Dynamic Stylesheet language

現時点では1.3.0が最新でした。

$ cd ./plugins/sfLESSPlugin/web/js/
$ wget http://lesscss.googlecode.com/files/less-1.3.0.min.js
$ cd ../../../../

plugins/sfLESSPlugin/lib/config/LESSConfig.class.php

を書き換えます。

  public function getLessJsPath()
  {
    // return '/sfLESSPlugin/js/less-1.1.3.min.js';
    return '/sfLESSPlugin/js/less-1.3.0.min.js';
  }

これでブラウザサイドでbootstrapをコンパイルする準備ができました。

ブラウザで確認すると、正常にbootstrapのCSSが効いていることがわかると思います。

このままではlessファイルを大量にリクエストすることになるので、次はサーバサイドでコンパイルして使うようにします。

ドキュメントにはnode.jsを使う方法が書いてありますが、せっかく新しいバージョンでlessphpが使えるようになったので、lessphpを試してみます。

まず、lessphpをダウンロードします。

lessphp – LESS compiler in PHP

$ cd ./lib/vendor
$ wget http://leafo.net/lessphp/src/lessphp-0.3.5.tar.gz
$ tar xvzf ./lessphp-0.3.5.tar.gz
$ mv ./lessphp/lessc.inc.php ./
$ rm -R ./lessphp
$ rm ./lessphp-0.3.5.tar.gz
$ cd ../..

必要なのはlessc.inc.phpだけです。また、ファイルはlib/vendor/lessc.inc.phpに置くように決められています。

で、また設定が

plugins/sfLESSPlugin/lib/config/LESSConfig.class.php

にあります。

  // protected $useLessphp = false;
  protected $useLessphp = true;

config/app.ymlに設定を書きます。less.jsを使用せず、コンパイルを有効にします。

  sf_less_plugin:
    compile:              true
    use_js:               false

これで設定は完了なのですが、このままだとcssファイルがweb/cssに書きだされます。

それはいいのですが、他のcssファイルもあるところに書き込まれるのはちょっと怖いので、階層を一段下げます。

$ cd ./web/less/
$ mkdir ./bootstrap
$ mv ./*.less ./bootstrap/
$ cd ../css/
$ mkdir ./bootstrap
$ chmod 777 ./bootstrap

app/fronend/config/view.ymlを修正します。

stylesheets:    [bootstrap/bootstrap.less, main.css]

これで完了です。

レスポンシブデザインを使いたい場合は、

stylesheets:    [bootstrap/bootstrap.less, bootstrap/responsive.less, main.css]

のようにresponsive.lessを追加してあげましょう。

ちなみに生成時にsymfonyのデバッグツールバーにlessのコンパイルにかかった時間が表示されるようになります。

image

2回目以降は生成しませんが、更新の確認をするようなので、本番環境では外しておくことが推奨されています。

一度生成してしまえばあとは通常のCSSとして使えます。

config/app.ymlでprod環境を指定してcompileをfalseにします。

prod:
  sf_less_plugin:
    compile:              false
    use_js:               false

最後にタスクでの生成を確認します。

prod環境の場合はあらかじめタスクでコンパイルしてCSSにしておいたほうが便利だと思います。

初回のタスク実行前にccしておきます。

$ ./symfony cc
$ ./symfony less:compile --clean --compress

これで既存のLESSから書きだしたCSSファイルを削除して、新しく書きだされます。

一応、LESSに対応するCSSファイルしか削除しないようなので安心です。

これでcss/bootstrap/を777にしなくてよくなりますね。

–compressを付けることで容量が少し小さくなります。中身見たらこんな処理でした。

  static public function getCompressedCss($css)
  {
    return str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $css);
  }

あとは好きにbootstrapをカスタマイズできます。


HybridAuthでFacebookログインする際の権限について

HybridAuthの内部で、Facebookのscopeは

email, user_about_me, user_birthday, user_hometown, user_website, offline_access, read_stream, publish_stream, read_friendlists

がデフォルトに設定されています。scopeの設定を空にすると上記のパーミッションが使われるので、ログインに使いたいだけの場合、不要な許可をユーザーに求めることになります。

image

image

詳細なパーミッションの説明は
http://developers.facebook.com/docs/authentication/permissions/
に書かれています。

今回はFacebookをログインするためだけに使います。ここまでの情報は不要なので、Basic Information(基本データ)だけにします。基本的にはscopeを空にすれば良いはずです。

ところが、HybridAuthの内部で文字列をemptyで判断しているため、空にするとデフォルトに上書きされてしまいます。

if( isset( $this->config["scope"] ) && ! empty( $this->config["scope"] ) ){
	$this->scope = $this->config["scope"];
}

対策としては、scopeに空白をひとついれておけばOKです。

"scope"   => " ",

前回の「Symfony 1.4でHybridAuthを使ってtwitter/facebookログインを実装する」でymlにした場合は、

scope: " "

としておきます。

image

これで「このアプリが受け取る情報」の欄を減らすことができました。

ちなみに、一度許可したアプリをFacebook上から削除したい場合は

http://www.facebook.com/settings?tab=applications

から削除できます。

image


Symfony 1.4でHybridAuthを使ってtwitter/facebookログインを実装する

schema.ymlにUserモデルを以下のように作っておきます。

User:
  actAs: { Timestampable: ~ }
  tableName: users
  options:
    type: MyISAM
    collate: utf8_unicode_ci
    charset: utf8
    comment: 'ユーザー'
  columns:
    id:
      type: integer(4)
      unsigned: false
      primary: true
      autoincrement: true
      comment: 'ID'
    name:
      type: string(255)
      fixed: false
      notnull: true
      default: 'no name'
      comment: 'ユーザー名'
    image:
      type: blob
      notnull: false
      comment: 'アイコン画像'
    hybridauth_provider_name:
      type: string(20)
      fixed: false
      notnull: true
      comment: 'HybridAuthプロバイダ'
    hybridauth_provider_uid:
      type: string(255)
      fixed: false
      notnull: true
      comment: 'HybridAuthプロバイダUID'

HybridAuthをダウンロードします。

Download HybridAuth

ちなみにSymfony2用にはHybridAuthのバンドルがあります。1.4用にも欲しい…

$ cd lib/vendor
$ wget http://jaist.dl.sourceforge.net/project/hybridauth/hybridauth-2.0.11.zip
$ unzip ./hybridauth-2.0.11.zip
$ rm ./README.html
$ rm -R ./examples/
$ rm ./hybridauth-2.0.11.zip
$ cd ../..
$ mkdir lib/config

lib/configにsfEnvironmentYamlConfigHandler.class.phpを作成します。sfSimpleYamlConfigHandlerを継承して、環境設定が反映されるように変更します。

<?php

class sfEnvironmentYamlConfigHandler extends sfSimpleYamlConfigHandler
{
    public function execute($configFiles)
    {
        $config = self::getConfiguration($configFiles);
        
        // compile data
        $retval = "<?php\n".
                  "// auto-generated by %s\n".
                  "// date: %s\nreturn %s;\n";
        $retval = sprintf($retval, __CLASS__, date('Y/m/d H:i:s'), var_export($config, true));
        
        return $retval;
    }

    static public function getConfiguration(array $configFiles)
    {
        return self::replaceConstants(self::flattenConfigurationWithEnvironment(self::parseYamls($configFiles)));
    }
}

configにconfig_handlers.ymlを作成します。設定ファイルの処理の仕方をここで指定しています。

config/hybrid_auth.yml:
  class: sfEnvironmentYamlConfigHandler

で、同じくconfigにhybrid_auth.ymlファイルを作成します。

all:
  base_url: http://localhost/login/endpoint
  providers:
    OpenID:
      enabled: true

    Yahoo:
      enabled: true

    AOL:
      enabled: true

    Google:
      enabled: true
      keys:
        id: ""
        secret: ""
      scope: ""

    Facebook:
      enabled: true
      keys:
        id: ""
        secret: ""

      # A comma-separated list of permissions you want to request from the user.
      # See the Facebook docs for a full list of available permissions:
      # http://developers.facebook.com/docs/reference/api/permissions.
      scope: ""

      # The display context to show the authentication page.
      # Options are: page, popup, iframe, touch and wap.
      # Read the Facebook docs for more details:
      # http://developers.facebook.com/docs/reference/dialogs#display.
      # Default: page
      display: ""

    Twitter:
      enabled: true
      keys:
        key: ""
        secret: ""

    # windows live
    Live:
      enabled: true
      keys:
        id: ""
        secret: ""

    MySpace:
      enabled: true
      keys:
        key: ""
        secret: ""

    LinkedIn:
      enabled: true
      keys:
        key: ""
        secret: ""

    Foursquare:
      enabled: true
      keys:
        id: ""
        secret: ""

  # if you want to enable logging, set 'debug_mode' to true
  # then provide a writable file by the web server on "debug_file"
  debug_mode: false
  debug_file: ""

TwitterとFacebookのKeyとSecretにそれぞれOAuthのConsumer KeyとConsumer Secretを入れておきます。TwitterはCallback URLをダミーでも構わないので入れておかないといけないので注意。

Twitter https://dev.twitter.com/apps

Facebook https://developers.facebook.com/apps/

設定内容はHybridAuthのconfig.phpと内容的には同じものです。YAMLに書き換えたのと、devやprodなど環境ごとに設定を切り替えられるようになっています。デフォルトはall以下に書けばOKです。

lib/vendor以下はオートロードされないので、configにautoload.ymlファイルを作成します。

autoload:
  hybridauth:
    name:           hybridauth
    path:           %SF_LIB_DIR%/vendor/hybridauth
    recursive:      true

次にアクション側を実装します。

authモジュールを作ります。

$ ./symfony generate:module frontend auth

routing.ymlにログインとエンドポイントを追加します。

login_endpoint:
  url:   /login/endpoint
  param: { module: auth, action: endPoint }

login:
  url:   /login/:provider
  param: { module: auth, action: index }
  requirements:
    provider: Facebook|Google|Twitter

apps/frontend/lib/myUser.class.phpを編集してIDとユーザー名を保持できるようにします。

<?php

class myUser extends sfBasicSecurityUser
{

    const ATTRIBUTE_NAMESPACE = 'localhost/user/myUser/attributes';

    public function setName($name)
    {
        $this->setAttribute('name', $name, self::ATTRIBUTE_NAMESPACE);
    }

    public function getName()
    {
        return $this->getAttribute('name', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function setId($id)
    {
        $this->setAttribute('id', $id, self::ATTRIBUTE_NAMESPACE);
    }

    public function getId()
    {
        return $this->getAttribute('id', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function setImage($image)
    {
        $this->setAttribute('image', $image, self::ATTRIBUTE_NAMESPACE);
    }

    public function getImage()
    {
        return $this->getAttribute('image', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function getImageBase64()
    {
        return base64_encode($this->getImage());
    }

}

lib/model/doctrine/UserTable.class.phpにプロバイダとUIDでユーザー名を検索するメソッドを実装します。

    public function getUserByProviderAndUid($providerName, $uid)
    {
        return $this->createQuery('u')
            ->where('u.hybridauth_provider_name = ?', $providerName)
            ->andWhere('u.hybridauth_provider_uid = ?', $uid)
            ->fetchOne();
    }

apps/frontend/modules/auth/actions/actions.class.phpを編集します。

<?php

class authActions extends sfActions
{

    public function executeIndex(sfWebRequest $request)
    {
        
        $config = sfContext::getInstance()->getConfigCache()->checkConfig(sfConfig::get('sf_config_dir').'/hybrid_auth.yml');
        
        try{
            $hybridauth = new Hybrid_Auth($config);
            
            $adapter = $hybridauth->authenticate($request->getParameter('provider'));
            
            $user_profile = $adapter->getUserProfile();
            
            $user = Doctrine::getTable('User')->getUserByProviderAndUid($adapter->id, $user_profile->identifier);
            
            if (!$user) {
                // 新規作成
                $user = new User();
                $user->set('name', $user_profile->displayName);
                $user->set('image', file_get_contents($user_profile->photoURL));
                $user->set('hybridauth_provider_name', $adapter->id);
                $user->set('hybridauth_provider_uid', $user_profile->identifier);
                $user->save();
            }
            
            $this->getUser()->setAuthenticated(true);
            $this->getUser()->setId($user->get('id'));
            $this->getUser()->setName($user->get('name'));
            $this->getUser()->setImage($user->get('image'));
            
            $this->redirect('@homepage');
            
        }
        catch( Exception $e ){  
            switch( $e->getCode() ){ 
            case 0 : echo "Unspecified error."; break;
            case 1 : echo "Hybriauth configuration error."; break;
            case 2 : echo "Provider not properly configured."; break;
            case 3 : echo "Unknown or disabled provider."; break;
            case 4 : echo "Missing provider application credentials."; break;
            case 5 : echo "Authentification failed. " 
            . "The user has canceled the authentication or the provider refused the connection."; 
            break;
            case 6 : echo "User profile request failed. Most likely the user is not connected "
            . "to the provider and he should authenticate again."; 
            $twitter->logout(); 
            break;
            case 7 : echo "User not connected to the provider."; 
            $twitter->logout(); 
            break;
            case 8 : echo "Provider does not support this feature."; break;
            } 
            
            // well, basically your should not display this to the end user, just give him a hint and move on..
            echo "<br /><br /><b>Original error message:</b> " . $e->getMessage();  
        }
        
        return sfView::NONE;
    }

    public function executeEndPoint(sfWebRequest $request)
    {
        Hybrid_Endpoint::process();
        
        return sfView::NONE;
    }

}

これでhttp://localhost/login/Twitterにアクセスすれば、Twitterの認証ページへジャンプしてログインができるようになります。

ログイン後のユーザー表示とかは、テンプレートでこんな感じに。

<img src="<?php echo 'data:;base64,' . $sf_user->getImageBase64(); ?>" alt="<?php echo $sf_user->getName(); ?>">&nbsp;
<?php echo $sf_user->getName(); ?>

後でどうせキャッシュしてしまうのでbase64_encodeのオーバーヘッドは無視です。


Silexで外部APIにHTTP Requestする

最近Silex始めました。

まずは外部のWebServerにリクエストを投げる方法です。軽量なWebClientであるBuzzを使います。gitでBuzz本体とProviderをダウンロードします。

$ cd vendor/
$ git clone https://github.com/kriswallsmith/Buzz.git Buzz
$ cd Buzz/
$ git checkout v0.5
$ git submodule update --init
$ cd ..
$ git clone https://github.com/marcw/silex-buzz-extension.git silex-buzz-extension

src/bootstrap.phpに追加します。

$app['autoloader']->registerNamespace('MarcW', __DIR__ . '/../vendor/silex-buzz-extension/lib');

$app->register(new MarcW\Silex\Provider\BuzzServiceProvider(), array(
    'buzz.options' => array(
    ),
    'buzz.class_path' => __DIR__ . '/../vendor/Buzz/lib',
));

これで準備完了です。あとはアプリケーションから

$app['buzz']->getClient()->setTimeout(30);
$response = $app['buzz']->get('http://www.google.co.jp');

echo $app['buzz']->getLastRequest()."\n";
echo $response;

として呼び出すことができます。Buzzのデフォルトのタイムアウトが5秒で、早すぎたので30秒に変更しておきました。


sfGuardFormSigninのメッセージをカスタマイズする

Symfony1.4でsfDoctrineGuardPluginを使っていて、ユーザ名かパスワードが違うときに、

The username and/or password is invalid.

と表示されてしまうので、これを変更したいと思います。それと、ラベルも日本語にしておきたいと思います。

sfGuardFormSigninを継承したフォームクラスをひとつ作ります。今回は/app/front/modules/sfGuardAuth/lib/form/mySigninForm.class.phpに作りました。

<?php

class mySigninForm extends sfGuardFormSignin
{
    public function configure()
    {
        parent::configure();
        
        $this->widgetSchema['username']->setLabel('ユーザID');
        $this->widgetSchema['password']->setLabel('パスワード');
        
        $this->validatorSchema['username']->setMessage('required', '入力して下さい');
        // $this->validatorSchema['username']->setMessage('invalid', 'ユーザIDかパスワードが違います'); // 効かない
        $this->validatorSchema['password']->setMessage('required', '入力して下さい');
        
        $validator = $this->getValidatorSchema()->getPostValidator();
        $validator->setMessage('invalid', 'ユーザIDかパスワードが違います');
    }
}

PostValidatorを取得してくるのが肝です。前述のメッセージはsfGuardValidatorUserで定義されているのですが、このバリデーターはBasesfGuardFormSigninでsetPostValidator(new sfGuardValidatorUser())として呼ばれているためです。

ちなみに、mySigninFormを有効にするには、/app/front/config/app.ymlに

all:
  sf_guard_plugin:
    signin_form: mySigninForm

を追加して、デフォルトのフォームクラスを変更する必要があります。


DoctrineMongoDBBundleでAbstractDoctrineExtensionのエラー

PHP Fatal error:  Class ‘Symfony\\Bridge\\Doctrine\\DependencyInjection\\AbstractDoctrineExtension’ not found in /home/path/to/Symfony/vendor/bundles/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php on line 31

php – DoctrineMongoDBBundle getting a fatal error in Symfony2 – Stack Overflow

masterリポジトリが書き換えられていて、最新バージョンでは動かないのでdepsファイルにDoctrineMongoDBBundleのレポジトリを指定するときに、バージョン番号を指定する必要があるということでした。
DoctrineMongoDBBundleセクションにversion=v2.0.0を追加すればOKです。

[doctrine-mongodb]
    git=http://github.com/doctrine/mongodb.git

[doctrine-mongodb-odm]
    git=http://github.com/doctrine/mongodb-odm.git

[DoctrineMongoDBBundle]
    git=http://github.com/symfony/DoctrineMongoDBBundle.git
    target=/bundles/Symfony/Bundle/DoctrineMongoDBBundle
    version=v2.0.0


Windows版のPHP5.3がc0000005で強制終了する

Symfony 1.4.16をWindowsのPHP 5.3.9で実行したときに、特定の条件で強制終了する現象に悩まされていました。
主にSymfonyタスクでdoctrine:data-dumpなどを実行するときに落ちていました。

色々調べた結果、Symfonyに問題があるわけではなく、/lib/vendor/symfony/lib/config/sfDefineEnvironmentConfigHandler.class.phpがきっちり4096バイトだったということが原因でした。

つまり、PHP5.3.9では、4096バイトのファイルをrequireもしくはincludeするとc0000005(Access Violation)で強制終了します。CLIに限らず、Apache経由でも同じです。

試しに4096バイトの一切コードが書かれていないテキストファイルを作ってrequireしてみると、再現しました。

強制終了時のウィンドウと、内容は以下のとおり。

image

ちなみにモジュール名はnts(Non Thread Safe)版ならphp5.dll、ts(Thread Safe)版ならphp5ts.dllです。
どちらも確認しましたが、どちらも同じ現象です。モジュールは入っていても入っていなくても同じです。

問題の署名:
  問題イベント名:    APPCRASH
  アプリケーション名:    php.exe
  アプリケーションのバージョン:    5.3.9.0
  アプリケーションのタイムスタンプ:    4f0c5cd0
  障害モジュールの名前:    php5.dll
  障害モジュールのバージョン:    5.3.9.0
  障害モジュールのタイムスタンプ:    4f0c5dfb
  例外コード:    c0000005
  例外オフセット:    00090f5b
  OS バージョン:    6.1.7601.2.1.0.256.48
  ロケール ID:    1041
  追加情報 1:    0a9e
  追加情報 2:    0a9e372d3b4ad19135b953a78882e789
  追加情報 3:    0a9e
  追加情報 4:    0a9e372d3b4ad19135b953a78882e789

多分現象はこのバグ報告と同じだと思います。
PHP :: Bug #60758 :: require() crashes Apache

5.4.0RC5でも解決してないみたいですね。報告とほぼ同じですが、これで再現できます。

<?php
file_put_contents('test.txt', str_pad('', 4096));
include('./test.txt');

とりあえず対処法としては改行1文字でも増やしておけばいいってことですか…


Symfonyの機能テストでclickを使わずにmultipartのファイルPOSTをする方法

Symfony 1.4で実装したAPIのFunctionalテストをする際に、ファイルをPOSTする方法に迷ったので調べました。

ファイルのアップロードテストは、通常のWebサイトであれば、

$browser->
    get('/upload')->
    click('Upload', array('form' => array(
        'file1' => sfConfig::get('sf_test_dir').'/datas/test01.jpg',
    )))
;

みたいに書くのですが、テストしたいアプリケーションはAPIなので、GETするページはありません。

sfBrowserBase::clickを参考にして、こんな感じで実装しました。

まずはテスト本体。test/functional/api/hogeActionsTest.php

<?php

include(dirname(__FILE__).'/../../bootstrap/functional.php');

$browser = new MyTestFunctional(new myBrowser());
$browser->loadData();

$browser->
    info('JPEGファイルをアップロードできる')->
    setUploadFile('file1', sfConfig::get('sf_test_dir').'/datas/test01.jpg')->
    post('/upload', array('title' => 'hoge'))->
    
    with('response')->begin()->
        isStatusCode(200)->
    end()->
;

次にmyBrowserを定義します。lib/test/myBrowser.class.php

<?php

class myBrowser extends sfBrowser
{

    public function setUploadFile($key, $filename)
    {
        if (is_readable($filename))
        {
          $fileError = UPLOAD_ERR_OK;
          $fileSize = filesize($filename);
        }
        else
        {
          $fileError = UPLOAD_ERR_NO_FILE;
          $fileSize = 0;
        }
        
        $this->parseArgumentAsArray($key, array(
            'name' => basename($filename),
            'type' => '',
            'tmp_name' => $filename,
            'error' => $fileError,
            'size' => $fileSize,
        ),
        $this->files);
    }

}

sfBrowserBaseクラスのfilesに対して配列でファイルの情報を書き込むと、POST直前に$_FILESに書きこんでくれます。

で、あとはMyでmyBrowser::setUploadFileを呼び出すだけです。lib/test/MyTestFunctional.class.php

<?php

class MyTestFunctional extends sfTestFunctional
{
    public function loadData()
    {
        $doctrine = Doctrine_Manager::getInstance()->getCurrentConnection()->getDbh();
        $doctrine->query('SET FOREIGN_KEY_CHECKS = 0');
        $doctrine->query('TRUNCATE TABLE uploads');
        $doctrine->query('SET FOREIGN_KEY_CHECKS = 1');
        unset($doctrine);
        
        Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures/');
        return $this;
    }

    public function setUploadFile($key, $filename)
    {
        $this->browser->setUploadFile($key, $filename);
        
        return $this;
    }
}

こうやってsetUploadFile(…)->post(…)と呼び出せばOKです。

sfBrowserBase::callされるたびにfilesの中身はクリアされます。


sfFlickrPluginのRequest failed.に対処する

symfony 1.4にて、Flickrから画像を取得して使いたかったので、sfFlickrPluginを使ってみました。

sfFlickrPluginのインストール方法はこちらのサイトを参考にチェックアウトできます。

symfonyでFlickrを使用するためのPluginまとめ – Layer8 Reference

基本的には手動によるセットアップです。
plugins/に展開します。
自分はエクスポートしました。

$ cd plugins/
$ svn export http://svn.symfony-project.com/plugins/sfFlickrPlugin

キーに関してはさきほどのサイトを参考に。

あとは

config/ProjectConfiguration.class.php

に、enablePluginsを追加すればOKです。

  public function setup()
  {
    $this->enablePlugins('sfDoctrinePlugin');
    $this->enablePlugins('sfFlickrPlugin');
  }

で、セットアップ完了。

ここから表題の話。

グループから写真を取得したい場合はgetGroupPhotoListを使います。

が、getGroupPhotoListを実行すると、

Request failed. The requested URL returned error: 413

というエラーで停止。

plugins/sfFlickrPlugin/lib/vendor/Phlickrを最新に差し替えることで解決します。

Phlickrはこちらにあります。

Phlickr | Download Phlickr software for free at SourceForge.net

Phlickrの最新版は0.2.8だったので、丸ごと上書きします。

ファイル一式とFramework、TextUiディレクトリも上書き

それ以外のディレクトリは調べてないのでわからないですが、まぁ、置いておけば良いのでは。

Testsは、いらない雰囲気。

これで正常に動くようになりました。

あと、桁あふれの問題があるので、

PhlickrのbuildImgUrlでPhoto IDが桁あふれ : blog.loadlimit – digital matter –

を参考にして修正してください。


Symfony 1.4からZendFrameworkでPicasaWebAlbumsにアクセスする

symfony-projectのページがGoogle検索に引っかかりにくい(引っかかってもバージョン違ったりする)ので色々キーワード入れてメモ。

ZendFrameworkをSymfonyに導入する方法は以下のJobeetの17日目に書いてありました。

Practical symfony | 17日目: 検索 | symfony | Web PHP Framework

cd lib/vendor/
wget http://framework.zend.com/releases/ZendFramework-1.11.7/ZendFramework-1.11.7.zip
unzip ZendFramework-1.11.7.zip
mv ZendFramework-1.11.7/library/Zend/ .

とりあえずlib/vendorに入れておきました。

あとはconfig/ProjectConfiguration.class.phpに上記ページのコードを貼りつけて、使う直前にProjectConfiguration::registerZend();してやればOK。

Picasaの方は

Zend Framework: Documentation: Picasa Web Albums の使用法 – Zend Framework Manual

このページを参考にしてできます。

丸投げだけど日本語だしわかりやすいので特に解説も不要かな、と…