Intereting Posts
yii cgridview ajaxlink работает только на первой странице Издевательствование вызова с помощью цепочечных методов и аргументов Как исправить ошибку с xml2-config не найден при установке PHP из источников? При удалении каскада – там, где я должен добавить его Howto цепочки объектов в PHP5: $ this-> foo-> bar-> baz () Проблема обратного вызова жизненного цикла при расширении пользовательского объекта FOSUserBundle Извлечение данных из корзины и отправка по почте Неопределенное смещение 1, массив в php Какой лучший способ определить язык? Использование GD для изменения цвета одной цветовой формы на прозрачном фоне при сохранении прозрачности Как тестировать контроллеры с помощью CodeIgniter? Извлечение данных из строки json со структурным языком Codeigniter SMTP не удается подключиться Как установить gettext на MacOS X Отладка ошибки PHP в IIS (как это связано с вызовами com-объектов)

Использование событий preRemove / postRemove для получения того, какие запросы могут быть выполнены и которые не могут

У меня этот вопрос в голове на некоторое время, и теперь мне нужен какой-то совет вокруг событий 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() , здесь она работает нормально.

Вы можете уточнить свой код, чтобы вам не нужно добавлять эти методы к своим объектам (даже не к обратным вызовам жизненного цикла). Например, вы можете использовать подписчика для захвата событий и специального класса со статическими методами для отслеживания тех объектов, которые не сбой, тот, который не прошел, и те, которые не имели возможности быть удалены / дополненное / вставить. Я отсылаю вас к документации, которую я предоставил. Это немного сложнее, чем кажется, не в состоянии дать вам общий ответ в нескольких строках кода, извините, вам придется исследовать дальше.

Мое предложение состоит в том, что вы пытаетесь использовать код, который я предоставил поддельной сущности, и сделайте несколько тестов, чтобы полностью понять, как это работает. Затем вы можете попробовать применить его к своим объектам.

Удачи!