web-dev-qa-db-ja.com

symfony2:複数のエンティティが1つの形式

私は2つのエンティティを持っています:

ADS\LinkBundle\Entity\Link:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\LinkRepository
id:
    id:
        type: integer
        id: true
        generator:
            strategy: AUTO
fields:
    dateAdded:
        type: datetime
    expirationDate:
        type: datetime
        nullable: true
    designator:
        type: string
        length: 255
        nullable: false
        unique: true
    slug:
        type: string
        length: 255
        nullable: true
        unique: true
manyToOne:
    company:
        targetEntity: ADS\UserBundle\Entity\Company
        inversedBy: link
        joinColumn:
            name: company_id
            referencedColumnName: id
        nullable: true
    createdBy:
        targetEntity: ADS\UserBundle\Entity\User
        inversedBy: link
        joinColumn:
            name: createdBy_id
            referencedColumnName: id
    domain:
        targetEntity: ADS\DomainBundle\Entity\Domain
        inversedBy: link
        joinColumn:
            name: domain_id
            referencedColumnNames: id
oneToMany:
        paths:
            targetEntity: ADS\LinkBundle\Entity\Path
            mappedBy: link
            cascade: [persist]
lifecycleCallbacks: {  }

そして

ADS\LinkBundle\Entity\Path:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\PathRepository
id:
    id:
        type: integer
        id: true
        generator:
            strategy: AUTO
fields:
    pathAddress:
        type: string
        length: 255
    pathWeight:
        type: string
        length: 255
manyToOne:
    link:
        targetEntity: ADS\LinkBundle\Entity\Link
        inversedBy: paths
        joinColumn:
            name: link_id
            referencedColumnName: id
lifecycleCallbacks: {  }

エンティティのパス部分を除いて、すべてを把握しました。これはA/B分割テスト用であるため、各リンクは2つのパスを持つことができます。各パスは、Webアドレスと番号(0〜100)で構成されます。

これが現在の状態の私のフォームです:

<?php
namespace ADS\LinkBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PathType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('pathAddress')
        ->add('pathWeight')
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_link'; }
}

そして

<?php
namespace ADS\LinkBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

    class LinkType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('designator')
        ->add('domain', 'entity', array(
            'class' => 'ADS\DomainBundle\Entity\Domain',
            'property' => 'domainAddress'
        ))
        ->add('paths', 'collection', array('type' => new PathType(), 'allow_add' => true))
        ->add('Submit', 'submit')
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}

私が理解する必要があるのは、リンクを作成するときに、それに合わせて正しいパスと重みを作成できる必要があるということです。リンクが作成されるまで、パスはデータベースに存在しません。

これが私のコントローラー用に持っているものです:

 public function newAction(Request $request) {
    $entity = new Link();
    $form = $this->createForm(new LinkType(), $entity);
    if ($request->isMethod('POST')) {
        $form->handleRequest($request);
        if ($form->isValid()) {
            $code = $this->get('ads.default');
            $em = $this->getDoctrine()->getManager();
            $user = $this->getUser();
            $entity->setDateAdded(new \DateTime("now"));
            $entity->setCreatedBy($user);
            $entity->setSlug($code->generateToken(5));
            $entity->setCompany($user->getParentCompany());
            $em->persist($entity);
            $em->flush();
            return new Response(json_encode(array('error' => '0', 'success' => '1')));
        }
        return new Response(json_encode(array('error' => count($form->getErrors()), 'success' => '0')));
    }

    return $this->render('ADSLinkBundle:Default:form.html.twig', array(
        'entity' => $entity,
        'saction' => $this->generateUrl('ads.link.new'),
        'form' => $form->createView()
    ));
}
7
Justin

@Onemaのおかげで(上記のコメントを読んでください)、私はこれを理解しました。 http://symfony.com/doc/current/cookbook/form/form_collections.html のドキュメントを読むことで、これを行うために必要な情報が得られました。

私がする必要があることを行うための最初のステップは、_form type_に関連付けられたフィールドを格納する_PathsType.php_と呼ばれる新しい_Paths Entity_を作成することでした。

_<?php
namespace ADS\LinkBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PathType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('pathAddress')
        ->add('pathWeight')
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_path'; }
}
_

次に、この新しいフォームを利用するように_LinkType.php_を変更します

_<?php
namespace ADS\LinkBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class LinkType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('designator')
        ->add('domain', 'entity', array(
            'class' => 'ADS\DomainBundle\Entity\Domain',
            'property' => 'domainAddress'
        ))
        ->add('paths', 'collection', array(
                'type' => new PathType(), 
                 'allow_add' => true,))
        ->add('Submit', 'submit')
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
_

_allow_add_を追加すると、そのフォームの複数のインスタンスを追加できるようになります。

ビュー内で、_data-prototype_属性を利用するようになりました。ドキュメントには、リストアイテムを使用した例があります-それで私は始めました。

<ul class="tags" data-prototype="{{ form_widget(form.paths.vars.prototype)|e }}"></ul>

次に、jQuery関数が登場しました(上記のドキュメントリンクにリストされています。単純なコピー/貼り付けが機能します)

これによりシステムが機能し、1つの小さな問題が発生し、_paths entity_で_Link entity_との関係がありましたが、この関係に気づかず、_link_id_フィールドがnull

これに対抗するために、もう一度_LinkType.php_を編集し、collection定義に_by_reference = false_を追加します。次に、エンティティ内のaddPathメソッドを次のように編集します。

_public function addPath(\ADS\LinkBundle\Entity\Path $paths)
{
    $paths->setLink($this);
    $this->paths->add($paths);
}
_

これにより、パスが関連付けられているリンクとして、現在のリンクオブジェクトが設定されます。

この時点で、システムは問題なく動作しています。ディスプレイを少し調整するだけで、必要なものがすべて作成されます。私は個人的に_twig macro_を使用して_data-prototype_に含まれるhtml出力を変更することを選択しました

form.html.twigの先頭に追加した現在のマクロ(不完全ですが機能しています)

_{% macro path_prototype(paths) %}
    <div class="form-group col-md-10">
        <div class="col-md-3">
            <label class="control-label">Address</label>
        </div>
        <div class="col-md-9">
            {{ form_widget(paths.pathAddress, { 'attr' : { 'class' : 'form-control required' }}) }}
        </div>
    </div>
{% endmacro %}
_

フォーム自体のHTMLで、listの作成を削除し、次のように置き換えました。

_<div class="form-group">
        {{ form_label(form.paths,'Destination(s)', { 'label_attr' : {'class' : 'col-md-12 control-label align-left text-left' }}) }}
        <div class="tags" data-prototype="{{ _self.path_prototype(form.paths.vars.prototype)|e }}">
        </div>
    </div>
_

次に、例のdivの代わりにulを開始点として使用するようにJavaScriptを変更しました。

_<script type="text/javascript">
    var $collectionHolder;

    // setup an "add a tag" link
    var $addTagLink = $('<a href="#" class="add_tag_link btn btn-xs btn-success">Add Another Destination</a>');
    var $newLinkLi = $('<div></div>').append($addTagLink);

    jQuery(document).ready(function() {
        // Get the ul that holds the collection of tags
        $collectionHolder = $('div.tags');

        // add the "add a tag" anchor and li to the tags ul
        $collectionHolder.append($newLinkLi);

        // count the current form inputs we have (e.g. 2), use that as the new
        // index when inserting a new item (e.g. 2)
        $collectionHolder.data('index', $collectionHolder.find(':input').length);
        addTagForm($collectionHolder, $newLinkLi);

        $addTagLink.on('click', function(e) {
            // prevent the link from creating a "#" on the URL
            e.preventDefault();

            // add a new tag form (see next code block)
            addTagForm($collectionHolder, $newLinkLi);
        });
    });

    function addTagForm($collectionHolder, $newLinkLi) {
        // Get the data-prototype explained earlier
        var prototype = $collectionHolder.data('prototype');

        // get the new index
        var index = $collectionHolder.data('index');

        // Replace '__name__' in the prototype's HTML to
        // instead be a number based on how many items we have
        var newForm = prototype.replace(/__name__/g, index);
        // increase the index with one for the next item
        $collectionHolder.data('index', index + 1);
        console.log(index);
        if (index == 1) {
            console.log('something');
            $('a.add_tag_link').remove();
        }
        // Display the form in the page in an li, before the "Add a tag" link li
        var $newFormLi = newForm;
        $newLinkLi.before($newFormLi);
    }
</script>
_

これらのpathsは、マーケティングアプリ内のA/B分割テストの宛先アドレスであるため、パスをリンクごとに2つに制限することにしました。これで、collections型を使用するフォームを正常にセットアップできました。

7
Justin