スポンサーサイト

0

    一定期間更新がないため広告を表示しています


    • 2014.12.23 Tuesday
    • -
    • -
    • -
    • -
    • -
    • by スポンサードリンク

    Symfony2のsecurityでハマった結果少しだけわかったこと(メモ)

    0
      Symfony¥Bundle¥SecurityBundleを使ってログイン機構を作っているときに、ログイン状態を手動で変更したいことってありますよね!(いきなり力説

      通常はlogin_checkにpostすればいいのですが、ログインするかどうかは任意でログインすれば特典あり、という仕様の時(ECサイトとかECサイトとかECサイトとか)に、どうやって実装するか考えると、方法は二通りあると思います。
      *ログイン希望する/しないを選択するUI(ボタンだったり、ラジオボタンだったり)をトリガーにしてjavascriptで移動先を変える。
      *POSTを受け取ってからログイン希望の場合はログイン、ログイン希望しない場合はログインしないまま処理を続行する。

      このうち、通常のログインアクション外でのログイン状態変更が必要になるのは、後者の実装を選択したケースです。

      通常のアクション内で手動でログインを行うには、下記のようなコードを書けば良いようです。
      // Get the security firewall name, login
      $providerKey = $this->container->getParameter('fos_user.firewall_name');
      $token = new UsernamePasswordToken($user, $password, $providerKey, $user->getRoles());
      $this->get("security.context")->setToken($token);

      // Fire the login event
      $event = new InteractiveLoginEvent($this->getRequest(), $token);
      $this->get("event_dispatcher")->dispatch("security.interactive_login", $event);

      引用元:http://stackoverflow.com/questions/10422251/manual-authentication-check-symfony-2

      一つだけ注意しなければいけないのは、Symfony¥Component¥Core¥Authentication¥Token¥UsernamePasswordTokenのソースにも、上記のコードにも$providerKeyという名前で示されている変数が、security.ymlに記載したprovidersのほうにつけた名前ではなくfirewallのほうにつけた名前だということです。(上記コードのコメントにもfirewall nameと書いてくれていますね)

      ここを間違えると、コード正しく書いたはずなのにいつまで経ってもログイン状態が変更できない…と延々悩むことになりますので注意です。
      以上、私が不注意で無駄にハマったという経験談の披露でしたorz


      composerで管理してるSymfony2のプロジェクトでZendFramework2のZendPdfを使う

      0
        なんでもそろってる(ように見える)Symfony Componentですが、実はPDF関係がありませんw

        で、ZendFrameworkにはZendPdfがあるので、サクッとそっちを使います。
        こういうときPSR-0が非常にありがたいですね♪

        …といってもZendFramework2にはまだ(?)ZendPdfが含まれてないのですね。
        でもgithubにレポジトリがあります。
        https://github.com/zendframework/ZendPdf

        build statusがunknownになってたりとやや不安ではありますが…。
        まぁソースを見たらほとんどzf1でのZendPdfにnamespaceつけただけっぽいので大丈夫でしょう。たぶん。
        ということで、人柱になってみることにしました。


        Symfony2のプロジェクト全体をcomposerで管理しているのでZendPdfのインストールもcomposerを使います。

        自分のプロジェクトのcomposer.jsonを開き、requireの末尾に
        "zendframework/zendpdf": "dev-master"

        を足しました。
        早速 composer.phar update してみると、パッケージが無いよ、と怒られます。

        そこで、repositories設定を足すことにします。
        公式サイトにはtypeをcomposerにしてhttps://packages.zendframework.com/を足せと書いてあります。
        が、これをそのまま加えてcomposer.phar updateすると、パッケージは見つかるものの、バージョンポリシーがどうのこうの(要するにdev以上のステータスのものがないよ)build status: unknownがここで効いてきたっぽいorz
        ここで色々試行錯誤したのですが、最終的にはtypeをvcsにしてgithubのレポジトリのURLをセットしたらcomposer.phar updateでインストールできるようになりました。

            "require" : {
              (略)
              "zendframework/zendpdf": "dev-master"
            },
            "repositories": [
             {
               "type": "vcs",
               "url": "https://github.com/zendframework/ZendPdf"
             }
           ],



        ZendPdf使えたー!やったー!だけで良い人はここまで。以下は余談です。


        ……………
        実は、上記の設定でcomposer.phar updateをするとzendframework/zendframeworkとzendframework/zendpdfの二つのパッケージがインストールされます。

        zendframework/zendframeworkは言わずと知れたZendFramework2の総合パッケージです。容量でかいです。
        開発環境だけならまだ良いですが、ライブラリがやたらと肥大するのはサーバーにリリースする時に不便です。(転送に時間かかったり、サーバーマシンのメモリではcomposerで初期化する時やたらと時間がかかったり)

        でも、ZendPdfのcomposer.jsonを見ると
        https://github.com/zendframework/ZendPdf/blob/master/composer.json
        依存ライブラリはzend-memoryとzend-stdlibだけのようです。
        つまり、最低限zend-stdlibとzend-memoryだけ入れればZendPdfは動く(はず)わけで、他の大量のライブラリは要らないわけです。

        そこで、composer.jsonを下記のように書き換えてみることにしました。
        zend-stdlibとzend-memoryへの依存を明示してあげます。

            "require" : {
              (略)
              "zendframework/zend-stdlib" : ">=2.0.0",

              "zendframework/zend-memory" : ">=2.0.0",
              "zendframework/zendpdf": "dev-master"
            },
            "repositories": [
             {
               "type": "vcs",
               "url": "https://github.com/zendframework/ZendPdf"
             },
             {
                "type": "composer",
                "url": "https://packages.zendframework.com/"
             }
           ],


        この状態でcomposer.phar updateすることで、無事にzend-stdlib,zend-memory,zendpdfだけが入りました。


        既存データベースからDB構造を持ってきて、yml状態で微調整してからエンティティを作る方法のメモ

        0
          Symfony日本ユーザー会にあるクックブックの翻訳
          既にあるデータベースからエンティティを生成する方法
          http://docs.symfony.gr.jp/symfony2/cookbook/doctrine/reverse_engineering.html
          がいまいちうまくいかなかったので試行錯誤して成功した方法を忘れないうちにメモ。
          (なんか、php app/console doctrine:mapping:import AcmeDemoBundle annotation を実行するだけでymlへのconvert工程を入れなくてもDBの内容でエンティティが作られちゃうみたい?)

          1.まず、既存のDBからymlを作る。
          php app/console doctrine:mapping:convert --from-database yml ./src/Acme/DemoBundle/Resources/config/doctrine
          上記クックブックだとconfig/doctrine/metadata/ormに置けと書いてありますが、Symfony2.1.xではその場所だとmetadataが認識されなかった。
          userというテーブルがあるとして、
          src/Acme/DemoBundle/Resources/config/doctrine/Hoge.orm.yml
          にymlのマッピングファイルができる。

          2.ymlの冒頭のクラス名を修正する
          生成されたymlのマッピングは
          Hoge:
            type: entity
            table: hoge
          のようになってるので
          Acme¥DemoBundle¥Entity¥Hoge:
            type: entity
            table: hoge
          のようにネームスペースをつけてやる。

          3.ymlを好きなように調整する
          特に調整したいところがなければこの項目はスルーで。
          前時代の負の遺産でカラム名が変だったり、テーブル名に変なprefixやsuffixがついてたりとかするのをせめてプロパティ名だけは現代的にしたりとか^^;

          3.ymlをもとにannotationつきのエンティティを自動生成
          php app/console doctrine:mapping:convert annotation AcmeDemoBundle
          を実行すると、調整後のymlの内容に従ってアノテーションつきのEntityが作られます。
          快適な開発が始められます♪

          なお、もしmappingを引き続きyamlで管理したい場合は
          php app/console doctrine:generate:entities AcmeDemoBundle
          のほうを実行します。

          ーーーーーーーーーーー
          ちなみに。
          Symfony Advent Calendar JP 2012の執筆者がまだ足りないみたいですよ☆
          我こそはと言う方、ぜひぜひ〜


          Symfony Advent Calendar JP day 18 - symfony1 vs Symfony2 フォームを使った検索の書き方比較

          0
            Symfony Advent Calendar JP 2012 18日目です。
            下っ端(むしろ幽霊部員)なのに、ありえない5周目の執筆です(汗
            今回は、symfony1 vs Symfony2 検索フォームの書き方比較と題して、Symfony2用の検索フォームの書き方について書きます。(私のような)symfony1→Symfony2に切り替え真っ最中の方のご参考になればと思います。

            1.検索フォームを表すクラスについて
            symfony1では、Hogeというモデルがある場合、doctrine:build --all-class(もしくは--filtersもしくは--all)によってlib/filter/doctrineに自動生成されるHogeFormFilterというクラスを使用するのが一般的でした。
            HogeFormFilterはsfFormFilterのサブクラスで、通常のフォームを書くときと同様にconfigure(),setup()メソッド内でwidgetとvalidatorをセットすることができ、bind()で入力値を受け取ります。

            一方、Symfony2では検索フォーム専用のフォームクラスというものはありません。
            通常の更新用フォームと同じようにほにゃららTypeクラスを作り、buildForm()で各項目をadd()していきます(もしくはFormBuilderにaddして直でフォームを作る)。
            なお、クラス名に特に決まりがあるわけではないのですが、私が書くときは、Hogeというエンティティがある場合、HogeTypeを登録・更新用、HogeSearchTypeを検索用のようにしています。

            2.csrfトークンチェックを切る
            csrfトークンがついたままだと、何かの検索結果ページをメールやtwitterやredmineでシェアするときに不便ですよね。検索フォームは何かデータの登録・更新・削除を行うフォームではないのでcsrfトークンのチェックは省いても大丈夫だと一般に(?)言われています。
            そこで、symfony1とSymfony2それぞれのやり方で検索フォームのcsrfトークンチェックをOFFにする方法を見てみます。

            symfony1では、HogeFormFilterはsfFormFilterのサブクラス、sfFormFilterはsfFormサブクラスなので、デフォルトの状態ではcsrfトークンチェックが有効になっています。OFFにするには、configure()かsetup()の中で$this->disableLocalCSRFProtection()を呼び出します。(HogeFormFilterをnewするときにコンストラクタの第三引数をfalseにするという方法もあり)

            一方、Symfony2ではsetDefaultOptions()メソッドを定義してcsrf_protectionオプションをfalseにすることにより、csrfトークンのチェックをOFFにできます。
            #HogeSearchType.php

                public function setDefaultOptions(¥Symfony¥Component¥OptionsResolver¥OptionsResolverInterface $resolver)
                {
                    $resolver->setDefaults(array(
                        'csrf_protection' => false,
                    ));
                }


            3.実際に検索を行う方法
            ここまででフォームHTMLの出力と入力値のバリデーションまでは行えるようになりましたが、いよいよ、実際に入力値による検索を実装する方法が問題となります。

            symfony1では、HogeFormFilter::getQuery()を呼び出すことで、入力値を使った検索用のクエリ(Doctrine_Query)が自動で生成されました。(生成されるクエリ内容をカスタマイズしたい場合はフォームにaddXXXColumnQuery()メソッドを追加することで可能でした)

            これに対し、Symfony2では、検索フォーム自体は登録・更新等に使うのと同じ単なるフォーム生成と入力値の検証のみを担当し、クエリ生成部分はコントローラ側に任されます。(おそらく、symfony1のような実装は、データベースとフォームの結合が密になりすぎて良くないと考えられたのでしょう。)
            getData()によりフォームから取り出した検証済の入力値を使って、コントローラ側で自前で検索クエリを作成するようになっています。
            #コントローラ。$formがnameという項目を持つHogeTypeから作られたフォームとして
            $data = $form->getData(); //array('name' => 'foo')
            $query = $entityManager->getRepository()->createQueryBuilder('r')->where('r.name = :name')->setParameter('name', $data['name'])->getQuery(); //検索用クエリ
            $entities = $query->getResults(); //検索結果

            これだとコントローラの見通しが悪くなり、複数の画面で検索処理を書く場合に不便なため、私はカスタムEntityRepositoryに専用メソッドを追加して実装するようにしています。
            #コントローラ。$formがnameという項目を持つHogeTypeから作られたフォームとして
            $data = $form->getData(); //array('name' => 'foo')
            $query = $entityManager->getRepository('AcmeDemoBundle:Hoge')->generateSearchQuery($data); //検索用クエリ
            $entities = $query->getResult(); //検索結果

            #HogeRepository.php
            public function generateSearchQuery($data)
            {
              $qb = $this->createQueryBuilder('r');
              $qb->where('r.name = :name')->setParameter('name', $data['name']);

              return $qb->getQuery();
            }

            #正直なところ、どっぷりsymfony1脳なせいか、Symfony2の検索実装方法は使いづらいと感じてしまっているのが現状だったり。。。


            Symfony Advent Calendar JP 2012 day 14 - vendorをcomposerで管理しているプロジェクトにcomposerを使わずにバンドルを追加したときのautoloadの書き方

            0
              Symfony Advent Calendar JP 2012 14日目です。

              composerでプロジェクトを始めるとapp/autoload.phpにある$loaderは¥Composer¥Autoload¥ClassLoaderになってしまい、registerNamespaces()メソッドが無いので、通常サードパーティのバンドルを入れた際に使うapp/autoload.phpへネームスペースを追加する書き方ではオートロードが動きません。

              具体的には、
              vendor/My/CoolBundle
              というバンドルを追加したいとします。
              たとえば、git submoduleを使う場合、
              git submodule add ~~~~/MyCoolBundle.git vendor/My/CoolBundle
              でサブモジュールとして追加します。
              或いは、アーカイブをダウンロードしてきた場合はアーカイブをvendor/My/CoolBundleに展開します。

              ここで、通常なら
              1.app/autoload.phpに
              $loader->registerNamespaces(array('My¥¥CoolBundle' => __DIR__.'/vendor/'));
              を追加
              2.app/AppKernel.phpで$bundlesに
              new My¥CoolBundle¥MyCoolBundle()
              を追加
              と進むわけですが、composer利用のプロジェクトだと$loader->registerNamespaces()が*必ず*こけます。

              これはapp/autoload.phpにある$loaderがSymfony¥Component¥ClassLoader¥UniversalClassLoaderのインスタンスではなく、Composer¥Autoload¥ClassLoaderのインスタンスであるためです。

              この時点で
              vendor/composer/ClassLoader.php

              vendor/composer/autoload_real.php
              をよく見ると
              $loader->add($prefix, $path)
              という形式で$loaderにネームスペースを追加できることがわかります。

              そこで、真似をしてapp/autoload.phpには
              $loader = require __DIR__.'/../vendor/autoload.php';
              の次の行辺りに
              $loader->add('My¥¥CoolBundle', __DIR__.'/../vendor/');
              を足してやります。
              こうすると、composerのオートローダーが手動で追加したバンドルも見つけてオートロードできるようになり、無事に使えるようになりました。


              Symfony Advent Calendar JP 2012 day 12 - symfony1とSymfony2でmany to manyを保存するフォーム・モデルの書き方比較

              0
                Symfony Advent Calendar JP 2012 12日目の記事です。

                symfony1+doctrine1では多対多のリレーションを扱うために、中間のリレーションテーブルを別途自前でモデルとして定義する必要がありました。
                たとえば、UserとGroupの多対多を扱うためには、UserGroupモデルを作り、User-UserGroup,Group-UserGroupの二つの一対多として扱うのです。
                https://github.com/doctrine/doctrine1-documentation/blame/master/source/ja/manual/defining-models.rst#L878

                当然の帰結として、フォームを書くときも自前です。
                UserFormにGroupのチェックボックス(又はmultipleなselect)を追加し(ここまでは簡単)、Group.idの配列からUserGroupを作って保存する処理(ここが面倒)を実装しなければなりませんでした。

                一方Symfony2+Doctrine2では多対多のリレーション専用の書き方が決められています。
                エンティティにアノテーションで書くと下記のようになります。
                #Acme/DemoBundle/Entity/User.php

                use Doctrine¥ORM¥Mapping as ORM;
                use Doctrine¥Common¥Collections¥ArrayCollection;

                class User
                {

                    /**
                     * @ORM¥ManyToMany(targetEntity="Group")
                     * @ORM¥JoinTable(name="users_groups",
                     *      joinColumns={@ORM¥JoinColumn(name="user_id", referencedColumnName="id")},
                     *      inverseJoinColumns={@ORM¥JoinColumn(name="group_id", referencedColumnName="id")}
                     *      )
                     **/
                    private $groups;

                    public function __construct()
                    {
                        $this->groups = new ArrayCollection();
                    }

                yml,xmlでの書き方はDoctrine2のmappingのドキュメントを参照してください。
                http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-many-unidirectional

                そして、嬉しいことに、このエンティティをdataとして指定してフォームを作ると、今まで手動で実装していた
                *多対多の相手先のチェックボックス又はmultipleなコンボボックス
                *リレーションテーブルへの保存
                を自動でやってくれるのです。

                #Acme/DemoBundle/Form/UserType.php
                class UserType extends AbstractType
                {
                    public function buildForm(FormBuilderInterface $builder, array $options)
                    {
                        $builder
                            ->add('groups', 'entity', array(
                                'class' => 'AcmeDemoBundle:Group',
                                'expanded' => true,  //checkboxにしたければtrue,multiple selectにしたければfalse←symfony1でのsfWidgetFormDoctrineChoiceと同じ
                                'multiple' => true,
                            ));
                    }
                }
                とても簡単ですね:D
                怠け者の私がこれだけのために今後はsymfony1でなくSymfony2を提案しようと思ってしまったぐらいでした。



                Symfony Advent Calendar JP 2012 day 10 - Symfony2で翻訳ファイルを使ってフォームを日本語化する方法

                0
                   Symfony Advent Calendar JP 2012の10日目です。

                  このブログを読んでいる大半の開発者は、日本人が使うための日本語表示のWEBアプリケーションを作っていることと思います。
                  Symfony2のフォームでは、labelオプションによりフォーム要素のラベルとして日本語を指定することができます。
                  #SomeFormType
                  $builder
                    ->add('name', 'text', array('label' => '名前'))

                  #edit.html.twig
                  {{ form_widget(form) }} →nameのラベルが「名前」になる

                  この方法は新規構築の時には良いのですが、後から改修するときにたとえ些細な文言の変更でも一々クラスの中身を弄らなければならないですし、万が一後から国際化する必要が発生したときに膨大な修正作業が発生して効率が悪くなります。そこで、既にお馴染みの言語ファイルを使った翻訳機能を利用します。

                  フォームの日本語化のために翻訳(i18n)機能を使う一番簡単な方法は、messages.ja.xlfに翻訳内容を書くことです。
                  #messages.ja.xlf
                  <trans-unit id="12345">
                    <source>Name</source>
                    <target>名前</target>
                  </trans-unit>

                  #edit.html.twig
                  {{ form_widget(form) }} →ラベルが「名前」になる

                  ただ、これではたくさんのフォームを使うWEBアプリケーション開発の際に不便ですよね。
                  ある画面では「名前」だったnameが別の画面では「会社名」かもしれないですから。
                  symfony1.2以降のsfFormでも、
                  #SomeForm.class.php
                  $this->getWidgetSchema()->getFormFormatter()->setTranslationCatalogue('someform');
                  のようにすることで、使用する翻訳ファイルをフォームごとに切り替えられるようになっていました。
                  Symfony2でもtwigテンプレート上でフォーム描画用のヘルパーの第二引数の連想配列で、translation_domainという項目を渡すことで、同様に使用したい翻訳ファイルを指定することができます。
                  #edit.html.twig
                  {{ form_widget(form, { 'translation_domain': 'someform' }) }} →「Name」!?

                  …指定することはできるのですが、やってみると実際には各行に適用されません。
                  #edit.html.twig
                  {{ form_row(form.foo, { 'translation_domain': 'someform' }) }}
                  {{ form_row(form.name, { 'translation_domain': 'someform' }) }} →「名前」
                  {{ form_row(form.bar, { 'translation_domain': 'someform' }) }}
                  なら適用されます。
                  項目数がそれほど多くないフォームなら良いのですが、いくつも項目があるフォームについてこれを書くのは面倒ですね。

                  原因としては、デフォルトのフォームテーマ用のテンプレートform_div_layout.html.twigにおいて、form_widgetのオプションがform_rowには引き渡されないことにあります。
                  #vendor/…/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
                  {% block form_rows %}
                  {% spaceless %}
                      {% for child in form %}
                          {{ form_row(child) }}
                      {% endfor %}
                  {% endspaceless %}
                  {% endblock form_rows %}

                  つまり、form_row呼び出し時にtranslation_domainをform_rowに渡すことができるようにフォームテーマをカスタマイズしてやれば、{{ form_widget(form, { 'translation_domain': 'someform' }) }}だけでもすべての項目に翻訳ファイル指定が効くことになります。
                  {% block form_rows %}
                  {% spaceless %}
                      {% for child in form %}
                          {{ form_row(child, { 'translation_domain': translation_domain }) }}
                          {# ※form_rows描画時はtranslation_domainという変数がform_widgetから渡された翻訳ドメイン名を持っています #}
                      {% endfor %}
                  {% endspaceless %}
                  {% endblock form_rows %}

                  フォームのテーマ化についてはSymfonyユーザー会の日本語ドキュメントを参照してください。
                  http://docs.symfony.gr.jp/symfony2/cookbook/form/form_customization.html#twig


                  Symfony2.2でCrocosSecurityBundleを使う

                  0
                    クロコスさんがgithubに公開してくださっているCrocosSecurityBundle、公式のsecurityの説明読んで頭がこんがらがった私が飛びつきました。
                    親切なドキュメントがあるのがいいよね!
                    遊びでSymfony2を触ってる時から何度か使わせてもらっていたのですが、今回業務でログイン・ログアウト機能が必要になった時にもお世話になることにしました。

                    ところが、早速vendorに入れてAppKernel.phpに追加したところ、エラー。
                    というのも、私はSymfony2.2を使っているため、微妙にコアのsessionサービスのクラスにつけられたnamespaceが変わってしまったようなのです。
                    CrocosSecurityBundle側が
                    ¥Symfony¥Component¥HttpFoundation¥Session
                    を要求しているのに対し、
                    ¥Symfony¥Component¥HttpFoundation¥Session¥Session
                    が渡されているというエラーでした。

                    そこで、
                    use ¥Symfony¥Component¥HttpFoundation¥Session;
                    となっている以下の3ファイル
                    Crocos/SecurityBundle/Security/PreviousUrlHolder.php
                    Crocos/SecurityBundle/Security/AuthLogic/SessionAuth.php
                    Crocos/SecurityBundle/Security/AuthLogic/SessionEntityAuth.php
                    について
                    use ¥Symfony¥Component¥HttpFoundation¥Session¥Session;
                    に変更したところ無事にSymfony2.2でもCrocosSecurityBundleが使えました。

                    ホントはpull requestしようかと思ったのですが、私がsessionしか使っておらず、ほかにも2.2になってコアのサービスが変更になっているところがあるかどうか検証もチェックもできてないので、とりあえず自分用メモ的にここに書きました。


                    Symfony Advent Calendar JP 2012 day 5 - symfony1.xの自作プラグインをSymfony2用のbundleに書き直す方法

                    0
                       Symfony Advent Calendar JP 2012の5日目です。

                      Symfony2のstandard edition(いわゆるフルスタックWEBフレームワークとしてのSymfony2)では様々な機能を「バンドル」として作成するのは有名(?)ですが、symfony1でも再利用可能な機能のまとまりを「プラグイン」という形で作っておくことができました。
                      http://www.symfony-project.org/plugins/

                      しかし、このプラグインにはいくつか難点がありました。
                      (1)どんなに便利でもsymfony外で再利用できない
                      そのプラグインの書き方にもよるのですが、autoloadや各種設定・ヘルパーなど、symfony1の恩恵を受けている部分が逆に足かせとなって、symfonyの外で再利用するのは難しくなります。
                      そうすると、「こんな小さなシステムにもsymfony使っちゃうの?」と言われつつも既にほかのオープンソースのWEBアプリ(典型的にはWordPress等)で実装済の機能部分をsymfony内に再現するために車輪を再発明する羽目になるか、もしくは、せっかくsymfonyプラグインを作ったのに他システム用に同じような処理を再度書く羽目に陥るわけです。

                      (2)プラグイン同士の依存関係がわかりにくい。もしくは他プラグインやappsに依存する部分を再利用時に毎回カスタマイズしないといけない
                      オープンソースのプラグインのようにドキュメントがしっかりついていれば良いのですが、往々にしてドキュメントが不足しがちな自作プラグインでは、ほかに何が必要なのか後からではわかりにくくなることがあります。
                      たとえばmyUserが持っているメソッドがプロジェクトによって微妙に異なったり、そのプラグインで扱う似たような内容を表すmodelの似たようなカラムがプロジェクトによって微妙に異なったり、といったほんの小さな差異なのですが、導入するプロジェクトに合わせて再利用するプラグインを毎回カスタマイズするというのは、やはり面倒ですし、バグやエラーも起こりやすくなります。

                      そのため、Symfony2では上記のような難点を極力減らすことができる「バンドル」に変わったわけです。(と思います)

                      ***

                      ここで、一つ問題が発生します。
                      symfony1で作成したプラグインはSymfony2のバンドルとして再利用できるか?当然NOです。

                      symfony1用に作成した便利なプラグインはSymfony2用に新たにバンドルとして作成しなおさなければなりません。
                      そこで、私がsymfony1用に作成してあったjpHolidayPluginをSymfony2用のNanawebJapaneseHolidayBundleとして作成しなおした体験談をもとに、プラグインをバンドルとして書き換える方法を書いておきたいと思います。(前置きがやたらと長くてスミマセン)

                      まず、jpHolidayPluginは、日本の祝祭日を計算・判定するためのプラグインで、一つの設定ファイルと一つのクラスだけで成り立っています。
                      https://github.com/77web/jpHolidayPlugin

                      NanawebJapaneseHolidayBundleも同様に、コントローラやほかのバンドルのクラスからサービスとして呼び出すことで日本の祝祭日を計算・判定することができるものです。
                      https://github.com/77web/NanawebJapaneseHolidayBundle

                      (1)ファイルの配置と名称を変えて、クラスにはnamespaceを付ける。
                      config/***.ymlのようにconfig配下にあった設定ファイルはResources/configの下に移動します。
                      lib/***/***.class.phpのようにlibの下に種類別ディレクトリに入っていたクラスファイルはBundle直下の種類別ディレクトリに置くようにします。←namespaceを工夫してつけるなら実はこれはやらなくてもOKだったり
                      また、ファイル名はFooBar.class.phpのようになっているのをFooBar.phpのように変更します。
                      更に、各クラスにnamespaceをつけて、クラス名をlowerCamelでなくUpperCamelに変更します。
                      https://github.com/77web/jpHolidayPlugin/blob/master/lib/jpHoliday.class.php

                      https://github.com/77web/NanawebJapaneseHolidayBundle/blob/master/JapaneseHoliday/JapaneseHoliday.php

                      ※今回はデータベースアクセスを伴わないプラグイン/バンドルのため省略していますが、もしデータベーススキーマを持つプラグインをバンドルに書き換えるときはmodel,form,filterはそのまま配置と名前だけ変えてもだめなので、完全に新しいバンドルを作るほうが早いかもしれません。

                      (2)lime,sfBrowserを使ったテストはphpunitを使うように書き換える。
                      limeやphpunitのテストの書き方については他の方のブログや本家のドキュメントを参照してください。
                      https://github.com/77web/NanawebJapaneseHolidayBundle/blob/master/Tests/JapaneseHoliday/JapaneseHolidayTest.php
                      ※jpHolidayPluginはテストコードをコミットしてないので比較対象がなくてすみません。

                      (3)中身をSymfony2に対応させる
                      sfConfig,sfContext等を使っている部分をSymfony2に対応した書き方になおします。

                      jpHolidayPluginでは、sfConfig::get()で祝日リストの設定ファイルを読みこんで利用していたため、Symfony¥Component¥Yaml¥Yamlを使って直接設定ファイルをパースさせるように書き直しました。また、クラスのインスタンスをサービスとして持つように変更したため、staticだった各メソッドをstaticではなくしました。

                      これで完了です!
                      #コントローラやモデル(Entity)のあるプラグインの場合は(3)の部分がかなり重い作業になるかと思いますが…。


                      メインのビューからrenderされた先のコントローラで元リクエストのコントローラ名を取得する

                      0
                        この記事はSymfony Advent Calendar JP 2012ではありません(笑)
                        今年はまだまだ参加者が不足しています。Symfony/symfonyを触ってる皆さん、積極的に参加してください!!私も明後日、執筆予定です!

                        Symfony2では、メインのコントローラのビュー(twig)から
                        {% render 'バンドル名:コントローラ名:アクション名' %}
                        とすることで、ほかのコントローラの出力結果を読み込ませることができます。
                        symfony1.xでいう「コンポーネント」と同じですね。

                        で、「元々のメインのコントローラは何なのか?」を読み込まれた側のコントローラから知りたい場面があります。(典型的には、グローバルナビを読み込んで、メニュー中のどの項目が現在アクティブなのかを表示するとか。)

                        仮に、default/indexのビューからfoo/barを呼び出したとします。

                        symfony1.xでは、下記のようにすることで呼び出し元を簡単に調べることができました。
                        <pre>
                        class fooComponents extends sfComponent
                        {
                          public function executeBar($request)
                          {
                            $currentModule = $request->getParameter('module'); //default
                            $currentAction = $request->getParameter('action'); //index
                            //default/indexだとわかる
                          }
                        }
                        </pre>

                        ところが、Symfony2ではうまくいきません。
                        <pre>
                        class FooController
                        {
                          public function barAction()
                          {
                            $currentController = $this->container->get('request')->get('_controller'); //AcmeDemoBundle:Foo:bar
                            $currentRoute = $this->container->get('request')->get('_route'); //_internal
                            //呼び出し元のdefault/indexに関する情報でなくfoo/barに関する情報
                            //呼び出し元はどこだかわからない
                          }
                        }
                        </pre>

                        呼び出された先のコントローラーで呼び出し元を判別できるようにするには、呼び出し時に一工夫してやる必要があります。
                        <pre>
                        {% render AcmeDemoBundle:foo:bar with  with { 'originalRequest': app.request } %}
                        </pre>
                        のようにoriginalRequest(名前は好きにつけてください)という名前を付けてapp.requestをrender時のパラメータとして渡し、コントローラでは引数として受け取った$originalRequestからコントローラー名やルーティング名を取得すると…
                        <pre>
                        class FooController
                        {
                          public function barAction($originalRequest)
                          {
                            $currentController = $originalRequest->get('_controller'); //AcmeDemoBundle:Default:index
                            $currentRoute = $originalRequest->get('_route'); //default_index
                            //呼び出し元がどこかわかった!
                          }
                        }
                        </pre>

                        まだまだSymfony2は初心者レベルなので、もしかしたら他にもっと良いやり方があるかもしれません。
                        #ご存知の方いらっしゃったら教えてください^^;



                        PR

                        calendar

                        S M T W T F S
                             12
                        3456789
                        10111213141516
                        17181920212223
                        24252627282930
                        << September 2017 >>

                        twitter

                        selected entries

                        categories

                        archives

                        recent comment

                        • 結局CodeIgniter用汎用Modelクラス&汎用CRUDスクリプトを書きました
                          プログラマー
                        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
                          よし
                        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
                          ななうぇぶ
                        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
                          よし
                        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
                          よし
                        • WindowsのPCで開発するphperがxhprofを使う方法
                          ななうぇぶ
                        • WindowsのPCで開発するphperがxhprofを使う方法
                          川本
                        • [バッドノウハウ]Symfony2で別テーブルの集計項目を一覧に含めたいとき
                          よし
                        • Symfony Advent Calendar JP 2012 day 14 - vendorをcomposerで管理しているプロジェクトにcomposerを使わずにバンドルを追加したときのautoloadの書き方
                          77web
                        • Symfony Advent Calendar JP 2012 day 14 - vendorをcomposerで管理しているプロジェクトにcomposerを使わずにバンドルを追加したときのautoloadの書き方
                          ktz

                        recent trackback

                        • HTMLの表(TABLE)のセル(TD)に斜線を引くjavascriptライブラリ slash.js 作っちゃいました
                          常山日記
                        • django対symfony 日本語メール送信(その1 symfony編)
                          CPA-LABテクニカル
                        • CodeIgniterでユーザー認証
                          されどLAMPな日々
                        • 久々にdjangoを最新版にしたらHTMLがエスケープされちゃった!!(解決済み)
                          常山日記
                        • FastCGIを諦めてmod_pythonを使う。Apacheのアップグレード
                          サーバー用語集
                        • さくらインターネット、sqlite3でdjango@CGI版を使う際の設定メモ
                          常山日記
                        • さくらインターネット スタンダードプランでdjango使ってる方、DBは?
                          mitszoの日記
                        • python多次元リストをsort(並べ替え)する方法?
                          mitszoの日記
                        • フォームから送信した値とrequest.POSTの挙動($_POST@PHPとの比較)
                          Humming Via Kitchen
                        • 日本語テキストをtruncate@django(Python全般にも??)
                          常山日記

                        recommend

                        links

                        profile

                        search this site.

                        others

                        mobile

                        qrcode

                        powered

                        無料ブログ作成サービス JUGEM