У меня этот вопрос в голове на некоторое время, и теперь мне нужен какой-то совет вокруг событий preRemove / postRemove, поскольку запросы, которые я буду выполнять в основном, будут DELETE
но это должно также применяться к prePersist / postPersist и preUpdate / postUpdate (не знает, действительно ли эти последние существуют).
У меня есть две возможные ситуации для выполнения DELETE в нескольких объектах (см. Цикл foreach
):
// First approach $itemsRemoved = $itemsNonRemoved = []; foreach($someVar as $item) { $item = $em->getRepository('someEntity')->find($item['value']); try { $em->remove($item); $em->flush(); array_push($itemsRemoved, $item['value']); } catch (Exception $e) { dump($e->getMessage()); array_push($itemsNonRemoved, $item['value']); } } // Second approach $itemsRemoved = $itemsNonRemoved = []; foreach($someVar as $item) { $item = $em->getRepository('someEntity')->find($item['value']); $em->remove($item); } $em->flush();
Первый подход не рекомендуется, а также как пользователь @acontell сказал об этом ответе. Выполнение flush()
– это антипатер, а также будет влиять на производительность приложения, поскольку каждый раз нужно будет выполнять несколько запросов, но используя этот подход, я могу получить, какой из них был вставлен а другой нет.
Используя второй подход, я избегу antipatern и улучшу производительность, но как узнать, какой элемент был вставлен, а что нет? Также, если какой-либо запрос завершился неудачно, по умолчанию Doctrine выполнит откат, так что никто не будет вставлен.
Итак, могу ли я использовать события preRemove / postRemove, чтобы получить, какие запросы могут быть выполнены и которые не могут означать, какие значения вставлены или нет?
Этот вопрос имеет замкнутое отношение к этому и этому .
Пример реальной жизни
Поскольку @acontell дает мне еще один отличный ответ, мне понадобятся советы, чтобы узнать, все ли у меня есть, или я все еще потерян, поэтому вот пример реальной жизни:
foreach ($request->request->get( 'items' ) as $item) { $relacion = $this->get( 'database_connection' )->fetchColumn( 'SELECT COUNT(fabricante_producto_solicitud_id) AS cnt FROM negocio.fabricante_modelo_marca_producto WHERE fabricante_producto_solicitud_id = ?', array( $item['value'] ) ); if ($relacion === 0) { $entFabricanteProductoSolicitud = $em->getRepository( "AppBundle:FabricanteProductoSolicitud" )->find( $item['value'] ); try { $em->remove( $entFabricanteProductoSolicitud ); $em->flush(); array_push( $itemsRemoved, $item['value'] ); $response['success'] = true; $status = 200; } catch ( \Exception $e ) { $status = 400; dump( $e->getMessage() ); return new JsonResponse( $response, $status ?: 200 ); } } $response['itemsRemoved'] = $itemsRemoved; } }
Если я получу его, то LifeCycleCallbacks
должен войти в AppBundle:FabricanteProductoSolicitud
где выполняется DELETE, правильно?
EDIT: Также мне нравится лучше всего использовать код для нескольких объектов, так как у меня будет такое поведение в большинстве из них, тогда должно быть хорошо определить Trait для этой цели? Должен быть определен как любой другой показатель?
На это я ответил сам, используя комментарии пользователей в качестве входных данных, надеюсь, что он может помочь другим
Выполнение некоторого теста на код by @acontell
Вот как выглядит мой код в данный момент:
public function eliminarNormasAction(Request $request) { if ($request->isXmlHttpRequest()) { $em = $this->getDoctrine()->getManager(); $response['success'] = false; $entProducto = $em->getRepository('AppBundle:Producto')->find($request->request->get('producto')); $response['success'] = false; $status = null; $ids = []; foreach($request->request->get( 'items' ) as $item) { array_push( $ids, $item['value'] ); } $qb = $em->createQueryBuilder(); $entNorma = $qb ->select("q") ->from('AppBundle:Norma', 'q') ->add('where', $qb->expr()->in('q.id', ':ids')) ->setParameter('ids', $ids) ->getQuery() ->getResult(); // Initialize arrays (useful to reset them also) Entity\Producto::prepareArrays(); foreach($entNorma as $norma) { // here entities are persisted since rows there is not more at DB $entProducto->removeProductoNorma( $norma ); } try { $em->flush(); $response['success'] = true; } catch (\Exception $e) { $status = 400; } $response['itemsRemoved'] = Entity\Producto::getDeletedEntities(); $response['itemsNonRemoved'] = Entity\Producto::getNotDeletedEntities(); } else { $response['error'] = $this->get('translator')->trans('mensajes.msgPeticionXMLHttpRequestInvalida'); } return new JsonResponse($response, $status ?: 200); }
Проблема Entity\Producto::getDeletedEntities()
возвращает пустой массив без удаленных значений, почему?
Вот как я это сделаю. Я не говорю, что это лучший подход, если кто-то знает что-то проще или лучше, я бы первым заинтересовался его изучением.
Прежде всего, это события Doctrine, которые вы можете использовать. Для простоты я объясню, как это сделать для удаления. Также для простоты я собираюсь использовать статический массив (это может быть сделано другими способами, мне нравится этот) и обратные вызовы жизненного цикла . В этом случае обратные вызовы будут очень простыми методами (именно поэтому их можно использовать вместо реализации прослушивателя или подписчика ).
Допустим, у нас есть этот объект:
Acme\MyBundle\Entity\Car: type: entity table: cars id: id: type: integer id: true generator: strategy: AUTO fields: name: type: string length: '25' unique: true color: type: string length: '64' lifecycleCallbacks: preRemove: [entityDueToDeletion] postRemove: [entityDeleted]
Как вы можете видеть, я определил два обратных вызова, которые будут запускаться с событием preRemove и событием postRemove.
preRemove – Событие preRemove происходит для данного объекта до того, как будет выполнена операция удаления EntityManager для этого объекта. Он не вызывается для оператора DQL DELETE.
postRemove – событие postRemove происходит для объекта после удаления объекта. Он будет вызываться после операций удаления базы данных. Он не вызывается для оператора DQL DELETE.
Затем php-код объекта:
class Car { // Getters & setters and so on, not going to copy them here for simplicity private static $preDeletedEntities;// static array that will contain entities due to deletion. private static $deletedEntities;// static array that will contain entities that were deleted (well, at least the SQL was thrown). public function entityDueToDeletion() {// This callback will be called on the preRemove event self::$preDeletedEntities[] = $this->getId();// This entity is due to be deleted though not deleted yet. } public function entityDeleted() {// This callback will be called in the postRemove event self::$deletedEntities[] = $this->getId();// The SQL to delete the entity has been issued. Could fail and trigger the rollback in which case the id doesn't get stored in the array. } public static function getDeletedEntities() { return array_slice(self::$preDeletedEntities, 0, count(self::$deletedEntities)); } public static function getNotDeletedEntities() { return array_slice(self::$preDeletedEntities, count(self::$deletedEntities)+1, count(self::$preDeletedEntities)); } public static function getFailedToDeleteEntity() { if(count(self::$preDeletedEntities) == count(self::$deletedEntities)) { return NULL; // Everything went ok } return self::$preDeletedEntities[count(self::$deletedEntities)]; // We return the id of the entity that failed. } public static function prepareArrays() { self::$preDeletedEntities = array(); self::$deletedEntities = array(); } }
Обратите внимание на обратные вызовы и статические массивы и методы. Каждый раз, когда удаление вызывается над объектом Car
, preRemove
вызов preRemove
будет хранить идентификатор объекта в массиве $preDeletedEntities
. Когда объект удаляется, событие postRemove
будет хранить идентификатор в $entityDeleted
. Событие preRemove
важно, потому что мы хотим знать, какой объект совершил транзакцию.
И теперь, в контроллере, мы можем это сделать:
use Acme\MyBundle\Entity\Car; $qb = $em->createQueryBuilder(); $ret = $qb ->select("c") ->from('AcmeMyBundle:Car', 'c') ->add('where', $qb->expr()->in('c.id', ':ids')) ->setParameter('ids', $arrayOfIds) ->getQuery() ->getResult(); Car::prepareArrays();// Initialize arrays (useful to reset them also) foreach ($ret as $car) {// Second approach $em->remove($car); } try { $em->flush(); } catch (\Exception $e) { $couldBeDeleted = Car::getDeletedEntities(); $entityThatFailed = Car::getFailedToDeleteEntity(); $notDeletedCars = Car::getNotDeletedEntities(); // Do what you please, you can delete those entities that didn't fail though you'll have to reset the entitymanager (it'll be closed by now due to the exception). return $this->render('AcmeMyBundle:Car:errors.html.twig', array(// I'm going to respond with the ids that could've succeded, the id that failed and those entities that we don't know whether they could've succeeded or not. 'deletedCars' => $couldBeDeleted, 'failToDeleteCar' => $entityThatFailed, 'notDeletedCars' => $notDeletedCars, )); }
Надеюсь, поможет. Это немного более громоздко реализовать, чем первый подход, но гораздо лучше с точки зрения производительности.
ОБНОВИТЬ
Я попытаюсь объяснить немного больше, что происходит в блоке catch
:
На этом этапе транзакция не удалась. Исключение было вызвано тем фактом, что удаление какого-либо объекта невозможно (например, с помощью ограничения fk).
Транзакция была отброшена, и никакие entites не были фактически удалены из базы данных.
$deletedCars
– это переменная, которая содержит идентификаторы тех объектов, которые могли быть удалены (они не вызывали каких-либо исключений), но не являются (из-за откат).
$failToDeleteCar
содержит идентификатор объекта, удаление которого вызвало исключение.
$notDeletedCars
содержит остальные идентификаторы сущностей, которые были в транзакции, но мы не знаем, что у вас было бы успешное выполнение или нет.
На этом этапе вы можете сбросить entitymanager (он закрыт), запустить другой запрос с идентификаторами, которые не вызвали проблем и не удалили их (если хотите) и отправить сообщение, позволяющее пользователю узнать, что вы удалили эти объекты и что $failToDeleteCar
и не была удалена, и $notDeletedCars
не были удалены. Вам решать, что делать.
Я не могу воспроизвести проблему, которую вы упоминаете о Entity::getDeletedEntities()
, здесь она работает нормально.
Вы можете уточнить свой код, чтобы вам не нужно добавлять эти методы к своим объектам (даже не к обратным вызовам жизненного цикла). Например, вы можете использовать подписчика для захвата событий и специального класса со статическими методами для отслеживания тех объектов, которые не сбой, тот, который не прошел, и те, которые не имели возможности быть удалены / дополненное / вставить. Я отсылаю вас к документации, которую я предоставил. Это немного сложнее, чем кажется, не в состоянии дать вам общий ответ в нескольких строках кода, извините, вам придется исследовать дальше.
Мое предложение состоит в том, что вы пытаетесь использовать код, который я предоставил поддельной сущности, и сделайте несколько тестов, чтобы полностью понять, как это работает. Затем вы можете попробовать применить его к своим объектам.
Удачи!