スポンサーサイト

0

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


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

    Symfony2 Certification不合格体験記。

    0
      この記事はSymfony advent calendar 201423日目の記事です。
      前日は @polidogさんでした。
       

      Symfony Certificationとは?


      Symfonyの開発元であるSensiolabsが実施しているSymfony開発者の資格試験です。
      https://sensiolabs.com/en/symfony/certification.html
      ・CBTで全問選択式(記述問題は無し)
      ・75問
      ・試験時間90分
      ・試験範囲は16のテーマから
      ・合格基準は非公開
      ・最初はヨーロッパまで行かないと受験できない仕組みでしたが、2013年10月から日本各地のピアソン試験センターでいつでも受験できるようになりました(近所のピアソン試験センターはここから検索できます)
      ・階級が2種類あり、試験に合格するとどちらかに認定されます
      ・試験に落ちても1年に2回まで再受験可能
      ・受験料250ユーロ。日本円に換算して約3万6千円(2014/12/23現在のレート1ユーロ=146.11円で計算)…高っ!w

      まぁ日本で受験した人は少ない(下手するといない)んじゃないかという試験です。
      # 合格しても日本では特にメリットが見いだせないですし…^^;
       

      受験のきっかけ


      2014年10月、Symfony 9周年を記念して発表されたTOP150 contributorへの受験バウチャー(高い受験料がタダになるチケット)プレゼントの該当者になったからです。
      ちなみにsymfony/symfonyとsymfony/symfony-docs両方でランクイン(symfony/symfonysymfony/symfony-docs)したんですが、貰えたバウチャーは1枚だけでした。チッ、ケチだな!
      SensioLabsのサイト上で情報を登録してから、メールの指示通りにピアソンVUEにデータ連携して登録すると準備完了です。ちなみに、日本でもポピュラーな資格試験なら日本語UIで登録が出来るようですが、マイナーなSymfonyは全て英語UIでした…。
       

      受験勉強


      この辺りを読んだり
      https://github.com/jmolivas/symfony-certification-guide
      http://www.craftitonline.com/2014/05/the-symfony2-exam-drill-unofficial/
      certificationyという(非公式の)コマンドライン学習ツールを使ったりして勉強しました。
      特にcertificationyは全部で136問の問題から20問ずつ出題され、オフラインでもできるので、暇つぶし(昼休みとか電車移動中とか)にちょこちょこやっているとほぼ全問正解できるところまでやりました。(が、後述するようにそれほど効果なかった模様…orz)
       

      試験の予約


      ピアソンVUEのサイト上で希望するテストセンターの空きを確認し、予約を入れます。
      ※今回タダ券として発行されたバウチャーコードはこの予約の最終段階でようやく使いました。

      私の場合は、アドベントカレンダーに書くために受験を決意したのでw 受験し終わって心の整理が間に合うように12/18にしました。
      (首都圏は大丈夫かもしれませんが)テストセンターによっては平日は夜しかやってなかったり、土日が原則休みだったりと結構枠が狭いので早めに予約を入れたほうが良いです。予約は48時間前までなら無料で変更できるとのこと。
       

      受験


      当日は名古屋市内も9年ぶりの大雪だった日で、「小学校が休校になったらどうしよう」とか「電車が止まって試験センターまで行けなかったらどうしよう」とか不安だったのですが、休校せず電車の遅れもそれほどなく受験自体は無事にできました。
      荷物を預けて指定されたパソコンの前に座って、チュートリアルを読み終わったら試験スタートです。90分間と設定されていますが、早く解き終わってもう直す必要なしになったら時間終了前でも終わりにできます。(90分まるまる座っていると小学校の下校時間までギリギリだったので助かりました)
      試験後はその場ですぐに結果通知を貰えます。
       

      結果


      落ちました(´・ω・`)
      合格基準非公開とあって、私の実際の得点が何点だったとか、あと何点足りなかったといった情報はありません。ただ "Fail" のみ。
      覚えている範囲で答え合わせをしてみると、もともと不安だった認証・キャッシュ周りで間違えたのと、デフォルトのコマンド関係で落とした、と思います(自信なし)
      これから受験を検討する方は、certificationyで出てくるマニアック問題(ほげほげコンポーネントにはクラスがいくつだとか、Symfonyには全部でいくつのコンポーネントがあるとか、twigの使い方とか、個別のコンポーネントのメソッドシグネチャがどうとか)を覚えるよりも、基本的なSymfonyの使い方とHTTP知識を磨いたほうが良さそうです。特に、細かく設定したり実装したりする機会の少ない(1つのプロジェクト内で何回も使うことはまれな)認証関係は基礎知識をよく確認しておいた方が良いでしょう。
      なお、当然のことながら試験の説明・問題文・選択肢すべて英語なので、問題文で何を訊かれているか・各選択肢がどういう意味かを辞書なしで読む程度の英語力は必要です。といっても未翻訳のドキュメントを英語版で読んで(厳密な翻訳でなく)なんとなく意味がわかれば大体それで十分だと思います。
       

      まとめ


      受験料が高く、受かっても特にメリットが無いので自腹で受けるのはちょっと…な試験ですが、もし再度の機会があれば今度こそ知識のあやふやなところをしっかり勉強して臨みたいです。おしまい。


      Doctrineでファイルアップロードを扱う方法 がイケてない件 #symfony_ja

      0
        ユーザーにファイルをアップロードさせてサーバー側に保存する。非常によくある処理です。
        公式のSymfony cookbookには既にそのものズバリなタイトルのドキュメントがあります。

        How to Handle File Uploads with Doctrine
        http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
        日本語訳:Doctrine でファイルアップロードを扱う方法
        http://docs.symfony.gr.jp/symfony2/cookbook/doctrine/file_uploads.html

        日本語版は原文に比べて多少内容が古いですが、説明されている実装方法は同じです。
        最近、Symfony歴の浅いプログラマにSymfonyの使い方を説明する機会がなぜか多く、そのうち一人にファイルアップロードのケースを書いてもらおうと思ったのですが、上記ページを見せて説明していたら心中「なんか違う…」という違和感がフツフツと湧いてきたのです。

        まだまだsymfony1癖が抜けずSymfony2初心者だった頃の私にとっては「素晴らしい!」と思えたのに、なんでだろう?
        というわけで、どこが違和感(不便…メンテしづらい…もやもや…)なのか、今の私が同じ処理を書くとしたらどうなるか考えてみました。

        まず、公式の「Doctrine でファイルアップロードを扱う方法」がイケてないと思う点は、下記の2点です。(あくまで個人的に、です。)
        1. Doctrine2のLifecycleCallback(PrePersist, PreUpdate, PreRemove)を利用しているため、実際の処理(アップロードファイルが一時保存先から恒久的な保存先に移動)がいつ呼ばれるのかわかりにくい。

        2. アップロード先パスがEntityにベタ書きでテスト/変更しづらい。
        アップロード先のディレクトリ(Document::getUploadRootDir())を直接書かざるを得ません。特にサンプルコードのように__DIR__を使って書かれていると、Symfony2 standard editionの中に置いた状態(あるいは同じディレクトリ構造を無理やり作る)じゃないとテストが失敗してしまいます。せっかく再利用可能なバンドルを書いても単独でテストできないことになります。
        それに、ファイルの保存先を設定ファイルで指定することもできなくなります。一見、Document::setUploadRootDir()のようなメソッドと$uploadRootDirプロパティを設定すればいいじゃん、と思うかもしれません。でも、そうすると「エンティティのupload()メソッドを呼ぶ前には必ずsetUploadRootDir()を呼ばなければならない」という決まりが必要になるわけです。データ保存のタイミングで必ず呼ばれるDoctrineのLifecycleCallbackを使う意味が失われてしまいます(せっかくアプリケーション内のどこから$documentを保存してもファイルアップロード周りの処理が行われるようになっているのに、アプリケーション内のどこでも$documentを保存しようとしたらまず先にsetUploadRootDir()を呼ぶ必要が出てくる)。
        #こういう実装ルールって、後々うっかりミスでトラブルになる臭いがしませんか?

        では、「Doctrineでファイルアップロード」をどう実装するか?
        答え:専用のサービスを作りましょう

        アップロードしたファイルの扱いで何が一番面倒かと考えたとき、データベース上のレコードとファイルシステム上のファイルの同期ですよね。ファイル保存したのにレコード保存してない、とか。レコード削除したのにファイル削除してない、とか。
        それ「だけ」を担当するサービスを作ってしまえば、同期の心配はそこにお任せできるわけです。

        サービスで実装した版がこちら

        実際のアップロードファイル移動処理はサービスで行い、コントローラーからはサービスのメソッドを呼び出すだけです。
        ファイルの恒久的保存先が新規Documentの保存後じゃないと決まらない場合(例えば、auto_increment/serialなidを使って保存先ディレクトリを決定する場合。id=1のdocumentのファイルをuploads/documents/1/hoge.jpgやuploads/documents/1.jpgのように保存したいとき)も、処理の順番を書き換えればいいだけです。なんならpersist()→flush()→ファイル移動処理→パス(保存されたファイル名)を変更flush()と2回保存もできちゃいます。LifecycleCallbackのトリッキーな使い方をググる必要はありませんw
        見るからにテストも書きやすいです。
        #そもそも昨今、アップロードしたファイルの恒久的保存先が同じファイルシステム内とも限らない訳で、追加要件が出てきたときにサクッと変更できるのは圧倒的にサービス方式だと思います。

        まぁこれが正解とか公式が間違っているというわけではなく、こういう方法もあるよ(こっちのほうが私はしっくりくるよ)という話でした。


        windows+vagrantでSymfony2プロジェクトの開発環境を作るノウハウ的なもの(Re:Vagrant で Symfony 開発)

        0
          4/19、Symfony勉強会 #9に行ってきました。
          全体の流れや私自身の発表については置いておいて、@karakaramさんのLT「Vagrant で Symfony 開発」に関連して、windows環境でVagrantを使ってSymfony2開発をしてきた珍種の一人として、ぜひ情報共有をしておきたいと思いました。
          (※私自身については、LT冒頭でも言いましたが、最近就職(!)してマカーに転向したので今後あまりwindowsを開発機として使う機会はなくなりました)
           

          前提

          一人で開発
          開発機のOSはWindows7 professional(メモリが64GBというモンスターマシン^p^)
          gitはmsysgit利用
          主に使うフレームワークはsymfony1又はSymfony2 たまにwordpressとかcakeとか
          visual c++等の自力でphp及びその拡張モジュールをビルドできる環境は無い
          新規開発案件(PHP5.4/5.5)と保守案件(PHP5.3)を使い分けたい
          開発に使うIDEはphpstorm
          原則としてTDDするのでphpunitを使いたい(symfony1の場合はlime使う)

          Vagrant1.5未満の場合

          使い方

          *プロジェクトルートをsynced_folderにする
          *コードはホスト側のphpstormで書き、テストはゲスト側のコンソールで手動実行(作業中は、phpstormとmsysgitのGit Bashとvagrant sshの三窓を行ったり来たり)
          *Symfony2のassets:installは常に--symlinkなしで使う
          (*symfony1のplugin:publish-assetsはホスト側で実行する)
          *composer installはゲスト側で実行(synced_folderでホスト側にも共有されてくるので、IDEはそれを参照できる)

          満足

          *本番とのphpバージョン差異に悩まされることがなくなった
          *windows版php特有の問題(strftimeのフォーマット文字列とか)に悩まされることがなくなった
          *よく言われる速度問題はホスト側の超性能でカバーできてた(と、思う。特に速度で不満感じたことなし)

          不満

          *jsやcssを更新したときにassets:installを手動実行するのが面倒 *シンボリックリンクが必須のバンドルが使えない

          Vagrant1.5以後の場合

          使い方

          *プロジェクトルートをtype: "rsync"でsynced_folderとして指定
          *ホスト側のwindowsはcwrsyncを利用してrsyncコマンドを使えるようにしておく
          *.git .idea app/cache/* app/log/* bin vendor web/bundlesを:rsync__excludeに指定(assetic使う場合はweb/assetic web/js web/cssも除外した方が良いかも)
          *利用するboxにもよるが、hashicorp/precise64の場合は、初回vagrant up時にいったんVagrantfile上のsynced_folder設定とprovision設定をコメントアウトし、ゲスト側rsyncのバージョンをホスト側rsyncのバージョンと合わせてから、synced_folder設定とprovision設定を再度有効化してvagrant reload --provisionを実行する
          *作業中に必要なウィンドウがphpstormとmsysgitのGit Bashとvagrant sshの三窓から、rsync-auto専用のGit Bashも加えた四窓に増えた
          *composer installはゲスト側のプロジェクトルートで実行後、--no-scriptつきでゲスト側の/vagrantでも実行する(IDEで補完を利用するために必要)

          満足

          *シンボリックリンクが必須の一部のバンドルを使っていてもwindowsホストで問題なく開発できるようになった

          不満

          *rsync-autoにしておくと、excludeにしているにも関わらず時々ゲスト側だけにあってホスト側にないファイル・ディレクトリ(vendorなど)が削除されてしまうことがある

          トラブルシューティング

          Vagrant1.5.2以下でrsyncしたディレクトリのパーミッションがおかしい場合→https://github.com/mitchellh/vagrant/issues/3256
          Vagrant1.5.2以下でrsyncがNo such file or directoryで失敗する場合→https://github.com/mitchellh/vagrant/issues/3230(4/21 12:40訂正1.5.3でも発生してました)



          利用バンドル数4〜10ぐらいの複数案件で試したのですが、rsyncを有効にしてsynced_folderの共有が速くなったという感じは特にありません。多少は速くなってるのかもしれませんが、少なくとも体感できるレベルではないです。(もともとのPCスペックも関係あるかもですが)
          今後、Vagrantに双方向rsyncが理想的な形で実装されればいいですが、現時点ではいろいろ手動だったり注意が必要だったりで、素人にはおすすめできない(キリッ)ってやつかもしれません。個人的には双方向rsyncでホスト側での変更内容を都合よく優先してくれっていうのは無理じゃね?と心配してます。


          今すぐSymfony2.4でKnpPaginatorBundleを使うためのcomposer.json設定

          0
            2013/12/3に、Symfony2.4が正式にstable(安定版)としてリリースされました。
            http://symfony.com/blog/symfony-2-4-0-released

            デバッグツールバーのFormパネル、セキュリティ(認証)カスタマイズの柔軟性アップなど、(開発者に)嬉しい機能が新たに追加されています。
            私も早速、Symfony2.3を使って開発したプロジェクトをSymfony2.4に対応させてみました。(諸事情あって本番サーバーには適用できなかったので飽くまで実験として、ですがorz)

            まず、変更したのはcomposer.jsonです。symfony/symfonyを"2.3.*"(2.3系列)から"~2.3"(2.3以上)に変更します。
            -        "symfony/symfony": "2.3.*"
            +        "symfony/symfony": "~2.3"
            

            サードパーティのバンドルを何も入れていない場合は、ここまででcomposerでsymfony/symfonyをupdateすればそれで完了です。
            $ composer update symfony/symfony
            あとは、自分の書いたバンドルについて2.4でエラーが出るようなら修正しましょう。

            私はKnpPaginatorBundleを入れていたので、デバッグモードでEventDispatcherが変更されたことによる影響をもろに受けて、paginatorを使う一覧系ページが軒並みエラーになりました…。
            Argument 1 passed to Knp¥Component¥Pager¥Paginator::__construct() must be an instance of Symfony¥Component¥EventDispatcher¥EventDispatcher, instance of Symfony¥Component¥HttpKernel¥Debug¥TraceableEventDispatcher given,

            エラーメッセージでGoogle検索すると割とすぐに「もう修正されてるよ」という情報が出てくるのですが、
            https://github.com/symfony/symfony/issues/9089
            https://github.com/KnpLabs/knp-components/pull/68
            https://github.com/KnpLabs/knp-components/pull/88
            https://github.com/KnpLabs/KnpPaginatorBundle/issues/223

            KnpPaginatorBundleとKnpComponentsの修正された部分がまだKnpLabsから正式版としてリリースされていないために、単にupdateしても「新しいリリースがないよ」という趣旨のエラーメッセージが出て終わってしまいます。
            色々試行錯誤したりソースを読んだりした末、KnpComponentsは1.2.4以上、KnpPaginatorBundleはmasterを使わなくてはいけないということがわかりました。
            そこで、composer.jsonにバージョンを指定します。
            -        "knplabs/knp-paginator-bundle": "2.3.*",
            +        "knplabs/knp-components": "~1.2.4",
            +        "knplabs/knp-paginator-bundle": "dev-master",
            

            knp-paginator-bundleのdev-masterだけ指定すると、knp-componentsのバージョンが自動的に1.2.3になってしまうのでknp-paginator-bundleより先にknp-componentsを1.2.4以上として指定しました。
            (私はこの組み合わせを見付けるのに2時間もかけてしまいました…orz)

            もしminimum-stabilityをstableにしている場合は、もう一点、composer.jsonの変更が必要です。
            knp-componentsがまだ安定版じゃないので、stable指定だとせっかくのベストな組み合わせが弾かれてしまいます。stableでなくdevを指定し、かつprefer-stableをtrueにすることで弾かれないようにします。
            -    "minimum-stability": "stable",
            +    "minimum-stability": "dev",
            +    "prefer-stable": true,
            

            ここまで来たら、後はcomposer updateするだけです。

            なお、KnpPaginatorBundleやKnpComponentsのリリース状況によっては、この指定は不要になる予定です。
            早く1.2.4を安定版にしてよ〜と叫んでる人がKnpComponentsのissueにたくさんいるので早晩リリースされるでしょう。たぶん。
            が、それまで待てないという方は、ぜひ上記の組み合わせをお試しください。


            icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法

            0
              Symfony2.3 standard editionから、新しいcomponentとしてSymfony¥Component¥Icuが必須になりました。ICUはInternational Components for Unicodeの略で、主に国際化関係のライブラリのようです。
              https://github.com/symfony/Icu

              ところが、このIcuコンポーネント、2013年夏現在で一つ問題があります。
              Symfony¥Component¥IcuはicuというC言語のパッケージ
              http://site.icu-project.org/home
              に依存しており、しかもバージョン4.4以上を要求しています。
              しかし、CentOS6.4の標準的なyumレポジトリでインストールできるicuはバージョンが4.2です。php開発者にとって最新版に素早く対応してくれることで有名なremiレポジトリにもありません。(※2013年夏現在)
              さらに、php-intlはicuに依存しており、yumで入れられるphp-intlは必然的に低いバージョンのicuにリンクされてしまいます。
              これでは、専用サーバー・VPSで、しかもphp-intlだけでもyumを使わずにphpizeしてビルドしないとせっかくのLTS版Symfonyが使えないことになってしまいます。こうなると共有サーバー(もともと、さくらやxrea,coreserverなどのsshがある程度自由に使えるサーバーじゃないと難しいですが)での利用は絶望的に思われます。

              先日、私がSymfony2.3で開発済みのとあるシステムを、用意されたサーバー(icuなどの環境が変更不可な環境)にデプロイしようとした際に、このicuのバージョン問題にひっかかって文字通り立ち往生でした。再枠の場合、Symfonyで作ったシステムをまるっと捨てて、別の依存ライブラリの少ないフレームワークで再構築まで半ば覚悟しました。
              が、その時、@hidenorigotoさんからありがたいアドバイスが!



              もう再構築のために他のフレームワークを選定する直前まで行っていたのですが、急遽composer.jsonでsymfony/symfonyの前にsymfony/icuをv1.0.0にバージョン指定して記述したうえで、再度composer.phar installを試みることにしました。
              (略)
              "symfony/icu" : "1.0.*",
              "symfony/symfony": "2.3.*",
              (略)
              
              結果、無事にicu4.2の環境でSymfony2.3のシステムを動かすことに成功しました!


              使うライブラリのcomposer.jsonはよく読みましょう、という教訓でしたorz


              Symfony2のControllerで@Templateを使うメリット

              0
                仕事が忙しくて予定してた実験ができなかったので小ネタ。

                symfony1.xに馴れた身でSymfony2を触ると、色々面倒なことってありますよね。
                例えば、sfDoctrineRoute。例えば、テンプレート名とアクション名が同じならわざわざ指定しなくていい。

                明示的で後から参加する人にもわかりやすくなったのは良いんですが、裏を返せばエキスパートにとってはわかりにくくなったと思うんです。(言いすぎ)

                symfony1.xの便利機能が恋しい〜。
                ところで、それ、Sensio¥Bundle¥FramewordExtraBundleにほとんどあります。

                Sensio¥Bundle¥FrameworkExtraBundle¥Configuration¥ParamConverter

                sfDoctrineRouteみたいなもんです。
                コントローラのアクションのメソッドにアノテーションにして使います。
                使い方はこの辺り
                一々$this->getDoctrine()->getManager()とかめんどくさくてやってられないでしょ。

                Sensio¥Bundle¥FrameworkExtraBundle¥Configuration¥Template

                通常$this->render()なんかで指定するテンプレート名が指定不要になって、更に、アクションのメソッドが配列を返すだけで良くなる設定。
                これをつけておくだけでAcmeDemoBundle:Foo:barコントローラのテンプレートはAcme/DemoBundle/Resources/views/Foo/bar.html.twigになります。
                @Template("AcmeDemoBundle:Foo:baz.html.twig")と書けばAcme/DemoBundle/Resources/views/Foo/baz.html.twigにも変更可。

                で。ここから、特に@Templateを使うと何が良いのかという話。

                @Templateをつけない場合、コントローラのメソッドの返り値はSymfony¥Component¥HttpFoundation¥Responseになります。
                普通のコントローラ作ってるだけなら別にいいのですが、汎用的なコントローラを作って、それを拡張した新しいコントローラを作ろうとした場合に拡張元が普通のアクションだとメソッドの返り値がResponseになってしまい、非常にカスタマイズしにくいです。

                @Templateなしの場合
                // src/Acme/DemoBundle/Controller/BaseController.php
                    public function barAction(Request $request)
                    {
                        //何かの処理
                
                        return $this->render('AcmeDemoBundle:Foo:bar.html.twig', 
                            array('name' => $name)
                        );
                    }
                // src/Acme/DemoBundle/Controller/FooController.php
                    public function barAction(Request $request)
                    {
                        //parent::barAction($request)だとResponseが返って来ちゃうから…
                        //何かの処理(BaseController::barAction()と同じ内容コピペ)
                        //追加の処理(カスタマイズ)
                
                        return $this->render('AcmeDemoBundle:Foo:bar.html.twig', 
                            array('name' => $name, 'tel' => $tel)
                        );
                    }
                

                @Templateありの場合
                // src/Acme/DemoBundle/Controller/BaseController.php
                    /**
                     * @Template()
                     */
                    public function barAction(Request $request)
                    {
                        //何かの処理
                
                        return array('name' => $name);
                    }
                // src/Acme/DemoBundle/Controller/FooController.php
                    /**
                     * @Template()
                     */
                    public function barAction(Request $request)
                    {
                        //返ってくるのは配列だからparent::barAction()使ってOK
                        $vars = parent::barAction($request);
                        //追加の処理(カスタマイズ)
                        $vars['tel'] = $tel;
                
                        return $vars;
                    }
                

                テンプレート指定の面倒がなくなるだけじゃないよ!という話でした。


                SonataAdminBundleでフォームや一覧表示を実用的にカスタマイズする方法(初歩編)

                0
                  SonataAdminBundleとは、"The missing Symfony2 Admin Generator"というキャッチフレーズの通り、symfony1時代のAdmin Generatorのような機能が使えるバンドルです。
                  つまり、いくつか決められた設定をするだけで、管理画面が自動で作られる使えるようになるバンドルというわけです。

                  インストール方法・基本的な使い方などはバンドルのドキュメント(本家の英語版@最新,@okaponさんによる日本語翻訳@少し古い?)を見てください。

                  さて、このSonataAdminBundle、便利な汎用バンドルにはありがちなことですが、少し変わったことをしようと思うと、工夫が必要になります。
                  例えば、顧客の性別データを1=男性,2=女性,3=その他で管理しているとします。
                  //src/Acme/DemoBundle/Entity/Customer.php
                  class Customer
                  {
                      /**
                       * @var integer
                       *
                       * @ORM¥Column(name="id", type="integer")
                       * @ORM¥Id
                       * @ORM¥GeneratedValue(strategy="AUTO")
                       */
                      private $id;
                  
                      /**
                       * @var string
                       *
                       * @ORM¥Column(name="name", type="string", length=255)
                       */
                      private $name;
                  
                      /**
                       * @var integer
                       *
                       * @ORM¥Column(name="seibetsu", type="integer")
                       */
                      private $seibetsu;
                  }
                  

                  (この実装が良いかどうかは別として)これを前提として、SonataAdminBundleで通常通りに管理画面設定をすると、下記のようになります。
                  // src/Acme/DemoBundle/Admin/CustomerAdmin.php
                  
                  use Sonata¥AdminBundle¥Admin¥Admin;
                  use Sonata¥AdminBundle¥Datagrid¥DatagridMapper;
                  use Sonata¥AdminBundle¥Datagrid¥ListMapper;
                  use Sonata¥AdminBundle¥Form¥FormMapper;
                  
                  class CustomerAdmin extends Admin
                  {
                      protected $baseRouteName = 'admin_customer';
                  
                      /**
                       * @param FormMapper $formMapper
                       */
                      protected function configureFormFields(FormMapper $formMapper)
                      {
                          $formMapper
                              ->add('name')
                              ->add('seibetsu')
                          ;
                      }
                  
                      /**
                       * @param DatagridMapper $datagridMapper
                       */
                      protected function configureDatagridFilters(DatagridMapper $datagridMapper)
                      {
                          $datagridMapper
                              ->add('name')
                              ->add('seibetsu')
                          ;
                      }
                  
                      /**
                       * @param ListMapper $listMapper
                       */
                      protected function configureListFields(ListMapper $listMapper)
                      {
                          $listMapper
                              ->addIdentifier('name')
                              ->add('seibetsu')
                          ;
                      }
                  }
                  

                  フォーム表示
                  一覧表示

                  フォーム(登録・編集、絞込み)でselect,radioを使えるようにする

                  まず、フォームで性別コード(1,2,3)を設定するのをやめたいと思います。
                  いくら管理画面とはいえ不親切すぎますし、間違いやすいです。
                  selectかradioで、男・女・その他のキャプションを見ながら選べるほうがいいですよね。

                  最初に、性別専用のフィールドタイプを作ります。サービスとして登録するところまでやっておきましょう。
                  作り方はこちら→カスタムフォームフィールドタイプの作成方法(日本語版)
                  ※昨日翻訳を更新しておいたので最新版になってます!

                  上記ドキュメントではm=Male,f=Femaleになっていますが、1=男,2=女,3=その他で作ります^^;
                  フィールドタイプを作り終わったら、CustomerAdmin::configureFormFields()とCustomerAdmin::configureDatagridFilters()でseibetsuをaddするときに、第二引数として専用フィールドタイプのサービス名、つまりgenderを指定します。
                      /**
                       * @param FormMapper $formMapper
                       */
                      protected function configureFormFields(FormMapper $formMapper)
                      {
                          $formMapper
                              ->add('name')
                              ->add('seibetsu', 'gender')
                          ;
                      }
                  
                      /**
                       * @param DatagridMapper $datagridMapper
                       */
                      protected function configureDatagridFilters(DatagridMapper $datagridMapper)
                      {
                          $datagridMapper
                              ->add('name')
                              ->add('seibetsu', 'gender')
                          ;
                      }
                  

                  これで、フォーム上の性別のウィジェットが性別選択専用のカスタムフィールドに変わりました。簡単でしたね:)
                  変更後のフォーム表示

                  一覧上で生の値でなくキャプションを表示させる

                  フォームが直ったのでもう充分な気もしますが、一覧で性別コード(1,2,3)が表示されるのも直したいと思います。
                  いくら管理画面とはいえ不親切(以下同文

                  今度は、カスタムTwigExtensionを用意します。
                  作り方はこちら→カスタムTwig拡張の書き方(日本語版)
                  ※昨日翻訳したてのほやほやです。

                  作るTwig拡張の内容は、【性別コード(1,2,3)を性別のキャプション(男,女,その他)に変換する】という機能をもつ関数にします。
                  // src/Acme/DemoBundle/Twig/Extension/GenderNameExtension.php
                  class GenderNameExtension extends ¥Twig_Extension
                  {
                      public function getName()
                      {
                          return 'acme_demo_gender_name';
                      }
                  
                      public function getFunctions()
                      {
                          return array(
                              'gender_name' => new ¥Twig_Function_Method($this, 'getGenderName'),
                          );
                      }
                      
                      /**
                       * @param string $value
                       * @return string
                       */
                      public function getGenderName($value)
                      {
                          $list = array(
                              1 => '男',
                              2 => '女',
                              3 => 'その他',
                          );
                          
                          return isset($list[$value]) ? $list[$value] : '-';
                      }
                  }
                  
                  専用TwigExtensionはサービスとして登録しておきます。
                  これで、twigテンプレート側で{{ gender_name(1) }}のような形で呼び出すことで、性別コード→性別キャプションの変換ができるようになりました。

                  専用のTwig関数ができたので、ここで、一覧表示の項目用のテンプレートを作ります。
                  {# src/Acme/DemoBundle/Resources/views/sonata_customer_list_gender.html.twig #}
                  {% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
                  {% block field %}
                      {% spaceless %}
                          {% if value is empty %}
                               
                          {% else %}
                              {{ gender_name(value) }}
                          {% endif %}
                      {% endspaceless %}
                  {% endblock %}
                  

                  このテンプレートをCustomerAdmin::configureListFields()でseibetsuのテンプレートとして指定したら、完了です。
                      /**
                       * @param ListMapper $listMapper
                       */
                      protected function configureListFields(ListMapper $listMapper)
                      {
                          $listMapper
                              ->addIdentifier('name')
                              ->add('seibetsu', 'string', array(
                                      'template' => 'AcmeDemoBundle::sonata_customer_list_gender.html.twig',
                                  ))
                          ;
                      }
                  

                  性別コードでなく性別キャプションのほうが表示されるようになりました。
                  変更後の一覧表示

                  実は私もまだ触り始めたばかりですが、SonataAdminBundleはちょっとした設定だけで色々カスタマイズができることがわかりました。現時点では、設定項目を網羅したリファレンス的なドキュメントが不足しているために、カスタマイズができるところがわかりにくいのが欠点かなと思います。
                  今後に期待したいところです!


                  Symfony2.3でsessionのflashデータの操作方法が変わった件

                  0
                    先週後半から、最初のLTS版Symfony2.3を早速使っているわけですが、2.2以前に比べて結構たくさん仕様変更があり、慣れたつもりの私でも「あれ?」という事態が発生しています。
                    日本語ドキュメントが全部追いつくまでかなり時間がかかると思われる(スミマセンスミマセン)ので、私がハマった部分だけ個別に書いておくことにしました。

                    早速ですが、セッションのflashメッセージの件。
                    セッションのflashメッセージは、一回限り(セットしたリクエストの次のリクエストまでの間だけ)保存されるセッションデータです。
                    主な用途は、postされたデータをサーバー側で処理した際の完了メッセージ等(たぶん)

                    (追記:ソースコードを追った結果この変更は2.3でなく2.1からの仕様変更で、2.2までは後方互換で使えていただけということがわかりました。)

                    2.2までの書き方

                    コントローラでflashメッセージをセットする:
                    $this->get('session')->setFlash('notice', '更新しました。');

                    twigテンプレートでflashメッセージが存在するかどうか確かめる:
                    {% if app.session.hasFlash('data') %}
                        {# dataというflashがある場合の処理 #}
                    {% else %}
                         {# dataというflashがない場合の処理 #}
                    {% endif %}

                    twigテンプレートでflashメッセージを表示:
                    {# hogeというflashを表示 #}
                    {{ app.session.flash('hoge') }}

                    2.3からの書き方

                    コントローラでflashメッセージをセットする:
                    $this->get('session')->getFlashBag()->set('notice', '更新しました。');

                    twigテンプレートでflashメッセージが存在するかどうか確かめる:
                    {% if app.session.flashBag.has('data') %}
                         {# dataというflashがある場合の処理 #}
                    {% else %}
                         {# dataというflashがない場合の処理 #}
                    {% endif %}

                    twigテンプレートでflashメッセージを表示:
                    {# hogeというflashを表示 #}
                    {% for flashMessage in app.session.flashBag.get('hoge') %}
                         {{ flashMessage }}
                    {% endfor %}
                    $this->get('session')->getFlashBag()->set('notice', '更新しました。');のように文字列を一つだけセットしてあっても、app.session.flashBag.get('notice')の中身は必ず配列になります。
                    一度$this->get('session')->getFlashBag()->set('notice', '更新しました。');としてから$this->get('session')->getFlashBag()->add('notice', 'もう一つ更新しました。');とすると、2つのメッセージを保存できます。
                    ただし、$this->get('session')->getFlashBag()->set('notice', '更新しました。');としてから$this->get('session')->getFlashBag()->set('notice', '再度更新しました。');とすると、後でセットしたメッセージのみが保存されます。(addだと追加されるがsetだと上書き)

                    #わかりづらくなって、正直言って改悪じゃないの?という気がしないでもないのですが(-_-;)


                    [バッドノウハウ]Symfony2で別テーブルの集計項目を一覧に含めたいとき

                    0
                      タイトルが日本語でおk状態ですが…。
                      [支店]にoneToManyで結びつく[売上]があるシステムで「支店一覧画面に、各支店の今月の売り上げ表示してよ」という類の要望が来たときとイメージしてくださいwww

                      symfony1+doctrine1なら簡単です。[支店]モデルに下記のようなメソッドを足して、
                      public function getMonthlyAmount($year, $month)
                      {
                        return Doctrine::getTable('売上')->getMonthlyAmount($this->getId(), $year, $month);
                      }
                      支店一覧テンプレートの各行で下記のように呼び出せばいい。
                      <?php foreach ($pager->getResults() as $branch): ?>
                          <tr>
                            <td><?php echo $branch->getName(); ?></td>
                            //略…ここに支店の所在地とか電話番号とか
                            <td><?php echo $branch->getMonthlyAmount(); ?></td>
                            //略…ここに編集ボタンとか詳細ボタンとか削除ボタン辺り。
                          </tr>
                      <?php endforeach; ?>


                      しかし、この手段はSymfony2+Doctrine2では取れません。
                      [支店]エンティティのメソッドからはエンティティマネージャや他のエンティティのレポジトリを呼び出すことができず、売上テーブルへの集計SQLを発行できないからです。
                      ※mappingの書き方次第では全部の売り上げのArrayCollectionなら持ってたりしますけど、一々全ての[売上]をforeachでループ廻して集計するのは現実的じゃないので却下しますw

                      さて、どうするか。
                      doctrineのイベント使ったら、[支店]エンティティからメソッド呼び出したタイミングで[売上]に対する集計ができたりするのかな?
                      コントローラ側でSQL発行するときにDBテーブルにない項目含めてもエンティティが取りだせたっけ?
                      ブログチュートリアルやったレベルのビギナーはここで完全に途方に暮れます(’・ω・`)
                      いっそsymfony1にフレームワークを変更しようか…まで考えます。

                      ところがどっこい、Symfony2をとことん勉強しないと不可能に思えた、各行ごとに別テーブル(同じテーブルに対してでもいいですが)からのデータを取り出す裏ワザ、TwigExtensionを使うと実現できちゃうんです。
                      カスタムTwig拡張の書き方(まだユーザー会翻訳がなくて英語版のみ)
                      http://symfony.com/doc/master/cookbook/templating/twig_extension.html

                      以下、簡単な書き方サンプル(タイプヒントとかちゃんと書いてない悪い例ですよ〜)

                      namespace My¥CoolBundle¥Twig¥Extension;

                      class BadKnowHowExtension extends ¥Twig_Extension
                      {
                          private $em;

                          public function __construct($doctrine)
                          {
                              $this->em = $doctrine->getEntityManager();
                          }

                          public function getName()
                          {
                              //他と被らなければ何を入れてもOK
                              return 'bad_knowhow';
                          }

                          public function getFunctions()
                          {
                              return array(
                                  'monthly_uriage' => new ¥Twig_Function_Method($this, 'calculateMonthlyAmount'),
                              );
                          }

                          public function calculateMonthlyAmount($branch, $year, $month)
                         {
                              return $em->getRepository('MyCoolBundle:売上')->calculateMonthlyAmountForBranch($branch->getId(), $year, $month);
                          }
                      }
                      #My/CoolBundle/Resources/config/services.yml
                      services:
                          my.cool_bundle.twig.bad_knowhow_extension:
                              class: My¥CoolBundle¥Twig¥Extension¥BadKnowHowExtension
                              arguments:
                                  - @doctrine
                              tags:
                                  - { name: twig.extension }

                      ポイントはservices.ymlで自作のTwigExtensionを登録するときにargumentsとして@doctrineを入れておくこと。
                      これで自作TwigExtensionのコンストラクタでentityManagerを取得することができ、好き勝手にSQLを発行させることができます。Ψ(`∀´)Ψ

                      あとはtwig側から呼び出すだけ!
                      {% for branch in list %}
                        <tr>
                          <td>{{ branch.name }}</td>
                          //略
                          <td>{{ monthly_uriage(branch, year, month) }}</td>
                          //略
                        </tr>
                      {% endfor %}


                      #ホントのホントにバッドノウハウもいいところですが、これを読んだら「そんな酷い書き方しちゃいかん」とユーザー会のすごい人たちが出てきて正しい方法を教えてくれるかもしれない、という期待を込めて書いてみた(キリッ


                      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



                        PR

                        calendar

                        S M T W T F S
                              1
                        2345678
                        9101112131415
                        16171819202122
                        23242526272829
                        30      
                        << April 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