2012-04-29 5 views
13

Tôi muốn thêm mục Nguồn cấp dữ liệu mới vào thực thể tồn tại và cập nhật. Tôi viết nghe sự kiện này (postUpdate là giống nhau):Học thuyết chèn vào sự kiện postPersist

public function postPersist(LifecycleEventArgs $args) 
{ 
    $entity = $args->getEntity(); 
    $em = $args->getEntityManager(); 

    if ($entity instanceof FeedItemInterface) { 
     $feed = new FeedEntity(); 
     $feed->setTitle($entity->getFeedTitle()); 
     $feed->setEntity($entity->getFeedEntityId()); 
     $feed->setType($entity->getFeedType()); 
     if($entity->isFeedTranslatable()) { 
      $feed->getEnTranslation()->setTitle($entity->getFeedTitle('en')); 
     } 
     $em->persist($feed); 
     $em->flush(); 
    } 
} 

Nhưng tôi đã

Liêm vi phạm chế: 1062 entry Duplicate '30 -2' cho khóa 'TIỂU'

và trong nhật ký có hai lần chèn:

INSERT INTO interview_scientificdire ction (interview_id, scientificdirection_id) VALUES (?,?) ([30,2]) INSERT INTO interview_scientificdirection (interview_id, scientificdirection_id) VALUES (?,?) ([30,2])

scientificdirection là nhiều đến nhiều bảng quan hệ cho thực thể những gì chúng ta muốn tồn tại. Trong ứng dụng lối vào, mọi thứ đều hoạt động tốt, nhưng trong Quản trị Sonata tôi gặp vấn đề này: (

Trả lời

1

Kể từ Học thuyết 2.2 bạn có thể đính kèm người nghe đến một sự kiện postFlush:

function postFlush($args) { 
    $em = $args->getEntityManager(); 
    foreach ($em->getUnitOfWork()->getScheduledEntityInsertions() as $entity) { 
     if ($entity instanceof FeedItemInterface) { 
      $feed = new FeedEntity; 
      ... 
      $em->persist($feed); 
     } 
    } 
    $em->flush(); 
} 
+0

Thx! Bạn có thể cho tôi liên kết tới tài liệu về sự kiện này không? Là tuôn ra thực sự được phép trong phương pháp này? – nucleartux

+0

Tôi không thể tìm thấy bất kỳ tham chiếu nào trong tài liệu, tôi đã tìm thấy nó trong mã ([UnitOfWork :: commit()] (https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/ UnitOfWork.php # L245)). Những sự kiện này được kích hoạt ngay từ đầu và cuối của tuôn ra để an toàn của nó tuôn ra một lần nữa ở đó (tôi đã thử). –

+1

PostFlush có bất kỳ điều gì trong getScheduledEntityInsertions() hay không. Nếu tôi không tìm thấy nó. Nó trống rỗng đối với tôi; Tôi đang suy nghĩ bởi vì chèn đã xảy ra. –

26

Nếu bạn cần thêm các đối tượng bổ sung, trình xử lý postPersist hoặc postUpdate trong Doctrine thật đáng buồn, không phải là nơi thích hợp. với cùng một vấn đề ngày hôm nay, khi tôi cần tạo một số mục tin nhắn trong trình xử lý đó.

Vấn đề tại điểm này là xử lý postPersist được gọi là trong sự kiện tuôn ra, chứ không phải sau đó. Ngoài ra, bạn không thể gọi tuôn ra trong một trình xử lý postPersist, vì điều này có thể dẫn đến các mục nhập ducplicate (như bạn đã từng trải qua)

Một cách để đi là sử dụng bộ xử lý onFlush từ học thuyết, tài liệu ở đây: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush

Đây chỉ là vấn đề nếu bạn cần id chèn của đối tượng cơ sở dữ liệu, như là thực thể chưa được ghi vào cơ sở dữ liệu trong trình xử lý đó. Nếu bạn không cần những id đó, bạn sẽ ổn với sự kiện ofFlush trong học thuyết.

Đối với tôi, giải pháp hơi khác một chút. Tôi hiện đang làm việc trên một dự án symfony2 và cần các id của các đối tượng cơ sở dữ liệu được chèn (đối với các cuộc gọi lại và các bản cập nhật sau này).

Tôi đã tạo một dịch vụ mới trong symfony2, cơ bản chỉ hoạt động như một hàng đợi cho thư của tôi. Trong bản cập nhật postPersist, tôi chỉ cần điền vào các mục trong hàng đợi. Tôi có một trình xử lý khác đã đăng ký trên kernel.response, sau đó lấy các mục đó và tiếp tục chúng vào cơ sở dữ liệu. (Một cái gì đó dọc theo dòng này: http://symfony.com/doc/current/cookbook/service_container/event_listener.html)

Tôi hy vọng tôi không phân loại quá nhiều từ chủ đề ở đây, nhưng vì nó là thứ tôi thực sự gặp khó khăn, tôi hy vọng một số người có thể thấy điều này hữu ích.

Các mục dịch vụ cho điều này là:

amq_messages_chain: 
    class: Acme\StoreBundle\Listener\AmqMessagesChain 

amqflush: 
    class: Acme\StoreBundle\Listener\AmqFlush 
    arguments: [ @doctrine.orm.entity_manager, @amq_messages_chain, @logger ] 
    tags: 
    - { name: kernel.event_listener, event: kernel.response, method: onResponse, priority: 5 } 

doctrine.listener: 
    class: Acme\StoreBundle\Listener\AmqListener 
    arguments: [ @logger, @amq_messages_chain ] 
    tags: 
    - { name: doctrine.event_listener, event: postPersist } 
    - { name: doctrine.event_listener, event: postUpdate } 
    - { name: doctrine.event_listener, event: prePersist } 

Bạn không thể sử dụng doctrine.listener cho điều này, vì điều này dẫn đến một sự phụ thuộc hình tròn (như bạn cần người quản lý tổ chức cho các dịch vụ, nhưng người quản lý thực thể cần dịch vụ ....)

Điều đó hoạt động như một sự quyến rũ. Nếu bạn cần thêm thông tin về điều đó, đừng ngần ngại hỏi, tôi rất vui khi thêm một số ví dụ vào điều này.

+0

Bạn muốn nói để tạo ra một kỷ lục trong sự kiện này và biết nó ID hàng đợi thông điệp cần thiết? – nucleartux

+0

Không, tôi cần các ID của thực thể sắp được lưu giữ trong trình xử lý onFlush. Và khi đơn vị công việc đang được chuẩn bị, bạn không có id của các thực thể sẽ bị xóa sau khi trình xử lý onFlush kết thúc. Hoặc tôi có hiểu sai câu hỏi của bạn không? – jhoffrichter

+0

Vâng, bạn hiểu lầm tôi :) Tôi có thể tạo thực thể mới trong trường hợp nếu tôi cần Id của thực thể những gì xảy ra sự kiện này mà không có phần mềm xếp hàng thông báo không? – nucleartux

26

Câu trả lời từ Francesc là sai, là changesets trong trường hợp postFlush đã trống rỗng. Câu trả lời thứ hai của jhoffrichter có thể hoạt động, nhưng là quá mức cần thiết. Cách thích hợp để tiếp tục là duy trì thực thể trong sự kiện postPersist và gọi lại lệnh flush trong sự kiện postFlush. Nhưng bạn phải làm điều này chỉ khi bạn thay đổi một cái gì đó trong sự kiện postPersist, nếu không bạn tạo ra một vòng lặp vô tận.

public function postPersist(LifecycleEventArgs $args) { 

    $entity = $args->getEntity(); 
    $em = $args->getEntityManager(); 

    if($entity instanceof FeedItemInterface) { 
     $feed = new FeedEntity(); 
     $feed->setTitle($entity->getFeedTitle()); 
     $feed->setEntity($entity->getFeedEntityId()); 
     $feed->setType($entity->getFeedType()); 
     if($entity->isFeedTranslatable()) { 
      $feed->getEnTranslation()->setTitle($entity->getFeedTitle('en')); 
     } 
     $em->persist($feed); 
     $this->needsFlush = true; 
    } 
} 

public function postFlush(PostFlushEventArgs $eventArgs) 
{ 
    if ($this->needsFlush) { 
     $this->needsFlush = false; 
     $eventArgs->getEntityManager()->flush(); 
    } 
} 
+4

Điều này hoạt động tốt - cảm ơn bạn đã chia sẻ! – caponica

+0

Điều này không tốt, bởi vì cách này - bạn sẽ thực hiện hai giao dịch riêng biệt. Do đó chỉ một trong số họ có thể thất bại chỉ để lại một thực thể. Tức là - trừ khi bạn làm rõ ràng $ em-> beginTransaction(); –

+9

Hey @chris, tôi không cố gắng trở thành một người ghét, nhưng Doctrine [documentation] (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html# postflush) nói rằng "EntityManager # flush() KHÔNG thể được gọi an toàn bên trong trình nghe của [postFlush's]." Bạn nghĩ gì về điều này? –

2

Vâng, heres làm thế nào tôi đã làm trong SF 2.0 và 2.2: lớp

Thính giả:

<?php 
namespace YourNamespace\EventListener; 

use Doctrine\ORM\Mapping\PostPersist; 


/* 
* ORMListener class 
* 
* @author:  Marco Aurélio Simão 
* @description: Listener para realizar operações em qualquer objeto manipulado pelo Doctrine 2.2 
*/ 

use Doctrine\ORM\UnitOfWork; 

use Doctrine\ORM\Event\OnFlushEventArgs; 
use Doctrine\Common\EventArgs; 
use Doctrine\ORM\Mapping\PrePersist; 
use Doctrine\ORM\Event\PostFlushEventArgs; 
use Doctrine\ORM\Mapping\PostUpdate; 
use Doctrine\ORM\Event\PreUpdateEventArgs; 
use Doctrine\ORM\EntityManager; 
use Doctrine\ORM\Event\PreFlushEventArgs; 
use Enova\EntitiesBundle\Entity\Entidades; 

use Doctrine\ORM\Event\LifecycleEventArgs; 

use Enova\EntitiesBundle\Entity\Tagged; 
use Enova\EntitiesBundle\Entity\Tags; 

class ORMListener 
{ 
    protected $extra_update; 

    public function __construct($container) 
    { 
     $this->container = $container; 
     $this->extra_update = false; 
    } 

    public function onFlush(OnFlushEventArgs $args) 
    { 
     $securityContext = $this->container->get('security.context'); 
     $em    = $args->getEntityManager(); 

     $uow    = $em->getUnitOfWork(); 
     $cmf    = $em->getMetadataFactory(); 

     foreach ($uow->getScheduledEntityInsertions() AS $entity) 
     { 
      $meta = $cmf->getMetadataFor(get_class($entity)); 

      $this->updateTagged($em, $entity); 
     } 

     foreach ($uow->getScheduledEntityUpdates() as $entity) 
     { 
      $meta = $cmf->getMetadataFor(get_class($entity)); 

      $this->updateTagged($em, $entity); 
     } 
    } 

    public function updateTagged($em, $entity) 
    { 
     $entityTags = $entity->getTags(); 

     $a = array_shift($entityTags); 
     //in my case, i have already sent the object from the form, but you could just replace this part for new Object() etc 

     $uow  = $em->getUnitOfWork(); 
     $cmf  = $em->getMetadataFactory(); 
     $meta  = $cmf->getMetadataFor(get_class($a)); 

     $em->persist($a); 

     $uow->computeChangeSet($meta, $a); 
    } 

} 

Config.yml:

services: 
    updated_by.listener: 
     class: YourNamespace\EventListener\ORMListener 
     arguments: [@service_container] 
     tags: 
      - { name: doctrine.event_listener, event: onFlush, method: onFlush } 

Hy vọng nó giúp;)

2

Giải pháp từ jhoff richter đang làm việc rất tốt. Nếu bạn sử dụng Lệnh điều khiển, bạn nên thêm thẻ cho lệnh event.terminate. Nếu không, nó không hoạt động bên trong các lệnh Console. Xem https://stackoverflow.com/a/19737608/1526162

config.yml

amq_messages_chain: 
    class: Acme\StoreBundle\Listener\AmqMessagesChain 

amqflush: 
    class: Acme\StoreBundle\Listener\AmqFlush 
    arguments: [ @doctrine.orm.entity_manager, @amq_messages_chain, @logger ] 
    tags: 
    - { name: kernel.event_listener, event: kernel.response, method: onResponse, priority: 5 } 
    - { name: kernel.event_listener, event: command.terminate, method: onResponse } 

doctrine.listener: 
    class: Acme\StoreBundle\Listener\AmqListener 
    arguments: [ @logger, @amq_messages_chain ] 
    tags: 
    - { name: doctrine.event_listener, event: postPersist } 
    - { name: doctrine.event_listener, event: postUpdate } 
    - { name: doctrine.event_listener, event: prePersist }