2012年5月6日日曜日

Form CollectionType ~ prototype

Formにcollection type ってのがあります。このprototypeについての説明 詳しくは コチラ(How to Embed a Collection of Forms)を見てもらえればよいのですが、簡単にここでもまとめておこうかと思います。

そもそも collection type ってなんなのか

まぁ、これは名前のままなんですが、typeの集合ですね。 例えば、「あなたの好きな曲はなんですか?」って項目に、TextType(テキストフィールド)が複数ぶら下がる場合を考えれば、 そのTextTypeを集めるのがCollectionTypeとなります。

もうちょっとわかりやすく説明しますと、UserにSongがぶら下がるとしましょう。これはOneToManyでUserがすきな曲は複数もてます。
こんなとき、UserTypeのEmbedでSongTypeを埋め込みたいですよね。でも1つのUserに対してSongTypeのフィールドたちをいっぱいもたなくてはならない。それってどうすんの? => ここで使うのが CollectionTypeなわけです。
EntityでもOneToManyの場合、Doctrine\Common\Collections\Collection が使用されますが、このCollectionと同じことなのかな。このCollectionを扱うためのTypeがCollectionType的な。

動的に項目増やしたい場合ってあるよね?

Collectionはなんとなくわかった。ちょっと疑問なんだけど、一般的にSongが上図のように3つしかぶら下がらないとは限らないよね? サービスによっては、Javacript とかで項目を動的に追加したりしたい場合ってあるとおもう。
その場合もCollection使うと思うんだけど、どうなのよ?

このために、prototypeがあるわけです。prototypeを使えばフォームの項目のHTML内容をプロトタイプとして持ってくれるので、あとはJavascriptでそれをベースにどんどん増やしてね☆ って話。
// src/Hoge/UserBundle/Form/Type/UserType.php
// ...

public function buildForm(FormBuilder $builder, array $options)
{
    $builder->add('username');

    $builder->add('songs', 'collection', array(
        'type' => new SongType(),
        'allow_add' => true,
        'allow_delete' => true,
        'by_reference' => false,
        'prototype' => true,
    ));
}
以下のようにするとdata-prototype に、エスケープされたプロトタイプのHTMLが入ります
<ul class="songs" data-prototype="{{ form_widget(form.songs.get('prototype')) | e }}">
<li></li>
</ul>
で、javascriptで以下のように$$name$$を数字に置き換えれば、動的に追加できます。 例えば、ボタンのクリックイベントで addTagForm() を呼べばOK
$(function(){
    var collectionHolder = $('ul.songs');

    var addTagForm = function() {
        // Get the data-prototype we explained earlier
        var prototype = collectionHolder.attr('data-prototype');

        // Replace '$$name$$' in the prototype's HTML to
        // instead be a number based on the current collection's length.
        var newForm = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);

        collectionHolder.append('<li>'+newForm+'</li>');
    };

});
ざっくりだけど、こんなかんじです。

2012年5月2日水曜日

Formのwidget個々のカスタマイズ

Formのwidgetには、choice, time, number ..... のように、いくつか用意されています。 詳しくは コチラ (symfony日本語ドキュメント) で, templateで出力する際は
{{ form_widget(form.fieldname) }} 
みたいにやります。 そうすると、
みたいな感じで表示されます。 ですが、
みたく、縦並びにしたいとかって、レイアウトをwidgetごとに変更したいときってありますよね。 そんなときは、
{% form_theme form _self %}
{% block _[formtype名]_[フィールド名]_widget %}
{% spaceless %}
    <div {{ block('widget_container_attributes') }}>
    {% for child in form %}
     <div> {# 追加!!!!!! #}
            {{ form_widget(child) }}
            {{ form_label(child) }}
     </div> {# 追加!!!!!! #}
    {% endfor %}
    </div>
{% endspaceless %}
{% endblock _recallItem_is_new_widget %}
みたいな感じで、テンプレートをカスタマイズできます。 これは、テンプレートのデフォルトのテーマをカスタマイズって形をとっていて、その元となるテンプレートは vendor/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig にありますので、これを見ながらカスタマイズすればOK.

2012年4月1日日曜日

独自のアノテーションを作成する

独自のアノテーションを作成したいっ!ってなったことありませんか? ( @Route, @Template のように )
例えば、Entity で CreatedAt を Annotation につけると現在時刻を付加 (→ http://www.scandio.de/2012/02/symfony2-custom-annotations/ で紹介されています)等のことが楽にできるようになります。


この独自アノテーションの作成方法を簡単にまとめておきます。

※ 以下のように、param フィールド をもつ Custom というコントローラーのアノテーションを作ることにします。
<?php

namespace Application\MogeraBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Core\HogeBundle\Annotations\Custom;

class DefaultController extends Controller
{
   /**
     * @Custom(param="hogehoge")
     */
    public function indexAction()
    {
  exit();
    }
}


① まずこの Custom クラスを作成
<?php
namespace Core\HogeBundle\Annotations;

/**
* @Annotation
*/
class Custom
{
    // fields
    private $param = "";
    
    // constructor
    public function __construct(array $data)
    {
        // アノテーションに記述した値が $data の中に入ってる
        $this->param = $data['param'];
    }

    // get value of param
    public function getParam(){
         return $this->param;
    }
}


② アノテーションを処理するDriver部分を作成
この例では、コントローラーが呼ばれるタイミングの、onKernelController で、アノテーションを処理させます。
<?php

namespace Core\HogeBundle\Annotations;

use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class CustomAnnotationDriver{

    private $reader;

    public function __construct(Reader $reader)
    {
        // set annotations reader
        $this->reader = $reader;
    }

    /**
     * onKernelController
     */
    public function onKernelController(FilterControllerEvent $event)
    {
        if (!is_array($controller = $event->getController())) {
            return;
        }

        $object = new \ReflectionObject($controller[0]);// get controller
        $method = $object->getMethod($controller[1]);// get method
        
        // getMethodAnnotation は 第二引数に指定したアノテーションタイプにマッチしたものを返します
        // annotation reader には、アノテーションの種類によってもいくつかメソッドが用意されてるので、ソースは見ておくと良いかも
        $custom = $this->reader->getMethodAnnotation( $method, 'Core\\HogeBundle\\Annotations\\Custom' );
        if( !empty($custom) ){
            print $custom->getParam(); // hogehoge と出力される
        }
    }
}


③ service に登録 (今回は onKernelController で アノテーションを処理するので以下のように。)
また、CustomAnnotationDriver にはコンストラクタで annotation_reader で 必要とするので、arguments にそれを記述。
services:
    hoge_annotation_driver:
        class: Core\HogeBundle\Annotations\CustomAnnotationDriver
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
        arguments: [@annotation_reader]



こんな感じ。ただ、今回は print しか処理をしてないです。
参考になれば嬉しいです。

2012年3月18日日曜日

FormでEntityにないフィールドを使用する場合

Symfony2のフォームフレームワークでは、EntityとForm定義を結びつけることができます。

その方法は、http://symfony.com/doc/current/book/forms.htmlとかを見てもらえば分かるのですが、

AbstractTypeを継承したクラスで
public function getDefaultOptions(array $options)
 {
    return array(
        'data_class' => 'Acme\TaskBundle\Entity\Category',
    );
 }
とするか、

formインスタンスを作成する時に、
use Acme\TaskBundle\Form\Type\TaskType;

public function newAction()
{
    $task = new Task(); // Task Entityのインスタンスを作成
    $form = $this->createForm(new TaskType(), $task); // それを第二引数でわたす

    // ...
}
とするかです。


このようにすると、Entityにデータをbindすることができるのですが、Entiryに存在しないフィールド名をフォームに定義してしまうと、エラーが出る。
例えば、TaskEntiryに urlというパラメータが存在しないのに、formで $builder->add('url','text') のように定義してしまうと、TaskEntityにはgetUrlってメソッドが無いよ!!と、怒られるわけです。


この場合は、以下のようにすればOK

$builder->add('url','text'
    array(
        "property_path" => false,
    )
);
property_pathというパラメータにfalseを与えるだけ。


ちなみに、bindRequestした場合は、
$form->bindRequest($request);
$url = $form['url']->getData();

こんな感じでパラメータを取得できます。



ただ、一つの方法ってだけで、このソリューションを使いまくるのは、それはそれでぐちゃぐちゃになりそうなので、あくまで一つの解決策ってかんじですね。

2011年12月8日木曜日

consoleコマンドリスト

$ php app/console list
のコマンドで、コンソールのコマンドリストを見れます。
別にコンソール上で見ればいいけど、一応一覧をメモっておく。

Symfony version 2.0.6 - app/dev/debug

Usage:
  [options] command [arguments]

Options:
  --help                    -h Display this help message.
  --quiet                  -q Do not output any message.
  --verbose              -v Increase verbosity of messages.
  --version               -V Display this program version.
  --ansi                         Force ANSI output.
  --no-ansi                    Disable ANSI output.
  --no-interaction     -n Do not ask any interactive question.
  --shell                     -s Launch the shell.
  --env                      -e The Environment name.
  --no-debug                 Switches off debug mode.

Available commands:
  help                                  Displays help for a command
  list                                  Lists commands
assetic
  assetic:dump                          Dumps all assets to the filesystem
assets
  assets:install                        Install bundles web assets under a public web directory
cache
  cache:clear                           Clear the cache
  cache:warmup                          Warms up an empty cache
container
  container:debug                       Displays current services for an application
doctrine
  doctrine:cache:clear-metadata         Clear all metadata cache for a entity manager
  doctrine:cache:clear-query            Clear all query cache for a entity manager
  doctrine:cache:clear-result           Clear result cache for a entity manager
  doctrine:database:create              Create the configured databases
  doctrine:database:drop                Drop the configured databases
  doctrine:ensure-production-settings   Verify that Doctrine is properly configured for a production environment.
  doctrine:generate:crud                Generates a CRUD based on a Doctrine entity
  doctrine:generate:entities            Generate entity classes and method stubs from your mapping information
  doctrine:generate:entity              Generates a new Doctrine entity inside a bundle
  doctrine:generate:form                Generates a form type class based on a Doctrine entity
  doctrine:mapping:convert              Convert mapping information between supported formats.
  doctrine:mapping:import               Import mapping information from an existing database
  doctrine:mapping:info                 Show basic information about all mapped entities
  doctrine:query:dql                    Executes arbitrary DQL directly from the command line.
  doctrine:query:sql                    Executes arbitrary SQL directly from the command line.
  doctrine:schema:create                Executes (or dumps) the SQL needed to generate the database schema
  doctrine:schema:drop                  Executes (or dumps) the SQL needed to drop the current database schema
  doctrine:schema:update                Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata
generate
  generate:bundle                       Generates a bundle
  generate:doctrine:crud                Generates a CRUD based on a Doctrine entity
  generate:doctrine:entities            Generate entity classes and method stubs from your mapping information
  generate:doctrine:entity              Generates a new Doctrine entity inside a bundle
  generate:doctrine:form                Generates a form type class based on a Doctrine entity
init
  init:acl                              Mounts ACL tables in the database
router
  router:debug                          Displays current routes for an application
  router:dump-apache                    Dumps all routes as Apache rewrite rules
swiftmailer
  swiftmailer:spool:send                Send emails from the spool

既存のテーブルからEntityを作成!

タイトルのように、既存のテーブルからEntityを作成したい場合は、どうするのかなーと思ってた所、こんな記事がありました。→ How to generate Entities from an Existing Database

2011年12月6日火曜日

controllerに書くアノテーションについて

SensioFrameworkExtraBundle

Symfony2 では、アノテーションとして Routing の情報等を書くけど、そのアノテーションに関する情報がまとまってなくて、どっかにないかなーと思っていたら、ドキュメントにあったので、紹介 + 自分なりにまとめてみます。

とりあえず、アノテーションに以下の5つの機能があるっぽい
  • @Route and @Method : ルーティングの情報や、ルートに許可されるHTTPメソッドを指定
  • @ParamConverter : リクエストの内容をオブジェクトに変換する機能を使用することを指定
  • @Template : テンプレート名を指定する
  • @Cache : HTTPキャッシュを作成するためのアノテーション


@Route and @Method

ドキュメントはコチラ → @Route and @Method

/**
 * @Route("/{id}", requirements={"id" = "\d+"}, defaults={"foo" = "bar"})
  */
public function showAction($id)
{
}
  • @Route() では、第一引数で pattern 情報を指定
  • requirements には, 正規表現などで、Route 情報に制限をかけられる
  • defaults で、デフォルトで渡されるパラメータの値を設定可能
  • @Routeを複数指定することもできる

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;

/**
 * @Route("/blog")
 */
class PostController extends Controller
{
    /**
     * @Route("/edit/{id}")
     * @Method({"GET", "POST"})
     */
    public function editAction($id)
    {
    }
}
  • クラスに対して @Route 情報を記述することで、全アクションに対して機能させることも可能
  • @Method({"GET", "POST"}) のように、@Method() を使用して、HTTPメソッドに制限をかけることができる。



@ParamConverter

ドキュメントはコチラ → @ParamConverter

リクエストの内容をオブジェクトに変換する機能を使用。

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
 * @Route("/blog/{id}")
 * @ParamConverter("post", class="SensioBlogBundle:Post")
 */
public function showAction(Post $post)
{
}

  • SensioBlogBundle:Post でPostオブジェクトがなかったら 404NotFoundが返される。
  • 全てのコンバーターはParamConverterInterfaceを実装してつくる

詳細はドキュメントを参照。


@Template

ドキュメントはコチラ → @Template

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

:

/**
 * @Template("SensioBlogBundle:Post:show")
 */
public function showAction1($id)
{
    // get the Post
    $post = ...;

    return array('post' => $post);
}

/**
 * @ParamConverter("post", class="SensioBlogBundle:Post")
 * @Template("SensioBlogBundle:Post:show", vars={"post"})
 */
public function showAction2(Post $post)
{
}
  • @Template("SensioBlogBundle:Post:show")のように、テンプレートを指定することが可能
  • vars を指定することで、テンプレートにデータを渡すことも可能


@Cache

ドキュメントはコチラ → @Cache

/**
 * @Cache(expires="tomorrow")
 */
class BlogController extends Controller
{
    /**
     * @Cache(expires="+2 days")
     */
    public function indexAction()
    {
    }
}

このようにして、expiresでキャッシュの有効期限を指定して、キャッシュを簡単に作成可能


詳しくは、ドキュメント参照でお願いします。
とりあえず、アノテーションに関してはこんな感じ!

Share

Twitter Delicious Facebook Digg Stumbleupon Favorites More