Sonata管理バンドルのフォームでDoctrineエンティティとDoctrineドキュメントをリンクします

オンラインストアの開発プロセスでは、承認されたユーザーのアドレス帳を実装するようにタスクが設定されました。 そのため、ユーザーはmysqlに保存され、それに関連付けられたアドレスはmongoDBに保存されます。 このタスクは、SonataAdminBundleに基づく管理パネルからユーザーとそのアドレス帳を管理するという点で特に注目に値します。



ソースデータ:



DoctrineエンティティユーザーとDoctrineドキュメントアドレスがあります。 それらの間には1対多の関係を確立する必要があります。 これらはすべて、ソナタに基づいて管理パネルのユーザー追加フォームから制御する必要があります。 1人のユーザーは多くのアドレスを持つことができるため、ユーザーを追加するフォームには、関連するアドレスのフィールドを編集して「追加」、「削除」、インラインのボタンでアドレスを追加するフォームのコレクションを実装する必要があります。 これが次に行うことです。



必要なもの:



1)@Gedmo \ References doctrine-extensionをインストールします


これは、mongoから特定のユーザーの関連アドレスのコレクションを取得するために必要です。逆も同様です。mysqlから各アドレスへのバインドされたユーザーです。



composer.jsonに書き込みます。

"gedmo/doctrine-extensions": "dev-master"







依存関係を更新します。



すべてのdoctrine-extensionsがインストールされますが、必要なものは1つだけです。具体的には、エンティティとドキュメント間で通信するように設計されたReferencesです。

詳細はこちら: github.com/Atlantic18/DoctrineExtensions/blob/master/doc/references.md



次に、リンクの両側を処理するconfig.yml 2サービスに登録する必要があります。

これらの構成を別のファイル、たとえばdoctrine_extensions.ymlに配置し、他のDoctrine拡張機能を使用する場合はconfig.ymlに接続できます。



 services: gedmo.listener.reference: class: Gedmo\References\ReferencesListener tags: - { name: doctrine_mongodb.odm.event_subscriber } calls: - [ setAnnotationReader, [ "@annotation_reader" ] ] - [ registerManager, [ 'entity', "@doctrine.orm.default_entity_manager" ] ] utils.listener.reference: class: Utils\ReferenceBundle\Listener\ReferencesListener arguments: ["@service_container"] tags: - { name: doctrine.event_subscriber, connection: default }
      
      







最初のサービスはベンダーリスナーを設定します。 ManyToOne側はそれで動作します。 (アドレス文書のgetUser()メソッド)。 また、oneToMany側には、カスタムリスナーを備えた2番目のサービスが必要です。



以下はUtils \ ReferenceBundle \ Listener \ ReferencesListenerクラスです。これは、グローバルヘルパーとユーティリティが置かれているバンドルに配置する必要があります。



 <?php namespace Utils\ReferenceBundle\Listener; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Class ReferencesListener * * @package Utils\ReferenceBundle\Listener */ class ReferencesListener extends \Gedmo\References\ReferencesListener { /** * @var \Symfony\Component\DependencyInjection\ContainerInterface */ private $container; /** * @var array */ protected $managers = [ 'document' => 'doctrine.odm.mongodb.document_manager', 'entity' => 'doctrine.orm.default_entity_manager' ]; /** * @param ContainerInterface $container * @param array $managers */ public function __construct(ContainerInterface $container, array $managers = array()) { $this->container = $container; parent::__construct($managers); } /** * @param $type * * @return object */ public function getManager($type) { return $this->container->get($this->managers[$type]); } }
      
      







注: Doctrine拡張リスナーのサービスを作成する便利なバンドルがあります。これはStof \ DoctrineExtensionsBundle( github.com/stof/StofDoctrineExtensionsBundle )ですが、参照専用の実装がないため、自分で作成する必要がありますここでは使いません



ここで、エンティティとドキュメントのフィールドに適切な注釈を登録する必要があります。 この場合、mongoのフィールドに外部キーのuser_idを提供する必要があります。このフィールドは、mongo自体では作成されないためです。



 /*Entity\User:*/ /** * @var ArrayCollection * * @Gedmo\ReferenceMany(type="document", class="\Application\Sonata\UserBundle\Document\Address", mappedBy="user") */ protected $addresses;
      
      







 /*Document\Address:*/ /** * @Gedmo\ReferenceOne(type="entity", class="\Application\Sonata\UserBundle\Entity\User", inversedBy="addresses", identifier="user_id", mappedBy="user_id") */ protected $user; /** * @var int $user_id */ protected $user_id;
      
      





これらのクラスのセッター\ゲッター、私はまだ持っていません、彼らは後で説明します。 yaml configでマップしたフィールドのタイプは、まだgedmo参照をyamlに登録する方法を理解していません。 コメントでこれを示していただければ幸いです。



上記の設定の後、そのようなコードが機能しないことを除いて、2つのエンティティまたはドキュメント間の通常の1対多の関係があるかのように、すべてが機能します。

 $user = new User(); $address = new Address(); $address->setAddress(«aaa»); $address->setUser($user); $user->getAddresses()->add($address); $em->persist($user); $em->flush();
      
      







代わりに、ドクトリンドキュメントマネージャーで各アドレスを明示的に永続化する必要があります。 この問題はまだ解決していません。



2.関連付けられたアドレスのコレクションを持つユーザーを追加するためのフォームのレンダリングに進みます。




UserAdminクラス内:

 protected function configureFormFields(FormMapper $formMapper) { $formMapper ->with('General') // …  ->add('addresses', 'collection', array('type' => new AddressType(), 'allow_add' => true, 'by_reference' => false, 'allow_delete' => true)) ->end(); }
      
      







ここでは、 sonata_type_collectionの代わりに通常のsymphonyコレクション(詳細についてはsymfony.com/doc/current/cookbook/form/form_collections.html )を使用します。これはmongoにまったくアタッチできません。



コレクション型を使用するには、フォームオブジェクト(この場合はAddressType)が必ず必要です。 フォームを作りましょう。 通常のシンフォニー形式。



 class AddressType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstname') ->add('lastname') ->add('address') ; } /** * @param OptionsResolverInterface $resolver */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Application\Sonata\UserBundle\Document\Address' )); } /** * @return string */ public function getName() { return 'application_sonata_userbundle_address'; //   .... */
      
      







すべてのネームスペースを持つAddressクラスの完全修飾名を使用して、必ずデフォルトのdata_class設定を設定してください。



その結果、ソナタでユーザーを追加/編集するフォームに次の要素が表示されます:(現在のユーザーに関連付けられたアドレスが既にいくつかある場合)



画像



ボタン「+」-アドレスブロックを追加、「-」-それぞれ、フォームからブロックを削除します。



3.フォームを処理します。




次に、送信するエンティティのセッターを処理して、フォームからの内容に応じてアドレスコレクションへの要素の追加/削除が機能するようにします。

アドレスコレクションをレンダリングする場合、by_reference = falseパラメーターを指定する必要があります。setAddresses()セッターが呼び出されるか、getAddress()-> addなどの文字列を使用してレコードの追加/削除が内部で行われるかどうかに依存するためです()、getAddress()->削除()。 これは必要ありません。セッターを呼び出す必要があり、その動作を再定義できます。



セッター自体は次のとおりです。



 public function setAddresses($addresses) { foreach ($this->addresses as $orig_address) { //     -    —    if (false === $addresses->contains($orig_address)) { //     $this->addresses->removeElement($orig_address); } } //   ,    ,      . foreach($addresses as $passed_address) { if(!$this->addresses->contains($passed_address)) { $passed_address->setUser($this); $this->addresses->add($passed_address); } } }
      
      







現在のユーザーを参照して既存のコレクションに1つのアドレスを追加するには、addAddressメソッドも必要です。

 public function addAddress($addresses) { $addresses->setUser($this); $this->addresses[] = $addresses; return $this; }
      
      







ここで、デバッグモードを有効にすると、アドレスコレクション内ですべてが正常であることがわかりますが、Mongoのアドレスはまだ書き込まれていません。 これは、コレクションがmongoで永続的ではないように、上記のバグが原因です。 mongoにアドレスを手動で記述し、そこから不要なアドレスを削除するには、UserAdminクラスのpostUpdate()イベントにアタッチします。

 public function postUpdate($user) { $dm = $this->container->get("doctrine_mongodb")->getManager(); $dbAddresses = $dm->getRepository('Application\Sonata\UserBundle\Document\Address')->findBy(array('user_id'=>$user->getId())); foreach($dbAddresses as $dbAddress) { if(!$user->getAddresses()->contains($dbAddress)) { echo $dbAddress->getFirstName(); $dm->remove($dbAddress); } } foreach($user->getAddresses() as $address) { $address->setUser($user); $dm->persist($address); } $dm->flush(); }
      
      







最後の問題は残っています-UserAdminクラスのコンテキストでは、doctrine_mongodbのdocumentManagerを取得する場所はありません。 これは、初期化中にSonataサービスからコンテナセッターを呼び出して、サービスコンテナをUserAdminクラスに挿入することで解決されます。



管理クラスのサービス設定で:



 sonata.user.admin.user: class: %sonata.user.admin.user.class% tags: - { name: sonata.admin, manager_type: orm, group: %sonata.user.admin.groupname%, label: users, label_catalogue: SonataUserBundle, label_translator_strategy: sonata.admin.label.strategy.underscore } arguments: - ~ - %sonata.user.admin.user.entity% - %sonata.user.admin.user.controller% calls: - [ setUserManager, [@fos_user.user_manager]] - [ setTranslationDomain, [%sonata.user.admin.user.translation_domain%]] - [ setContainer, [@service_container]]</code>    <code>- [ setContainer, [@service_container]]
      
      







次に、クラス管理内で、新しいオブジェクトフィールドを宣言し、セッターを作成します。これは、クラスが初期化されるときにサービスによって呼び出されます。

 /** @var \Symfony\Component\DependencyInjection\ContainerInterface */ private $container; public function setContainer (\Symfony\Component\DependencyInjection\ContainerInterface $container) { $this->container = $container; }
      
      







それがすべてのようです。 アドレスは、mysqlの2つの通常のエンティティまたはmongoの2つの通常のドキュメントであるかのように追加、編集、削除する必要があります。



All Articles