Mysql
 sql >> Database >  >> RDS >> Mysql

Utilizzo degli eventi preRemove/postRemove per ottenere quali query possono essere eseguite e quali no

Ecco come lo farei. Non sto dicendo che questo sia l'approccio migliore, se qualcuno conosce qualcosa di più semplice o migliore, sarei il primo interessato ad impararlo.

Prima di tutto, questi sono i Eventi dottrinali che puoi usare. Per semplicità, spiegherò come lo farei per le eliminazioni. Anche per semplicità, userò un array statico (potrebbe essere fatto in altri modi, mi piace questo) e richiamate del ciclo di vita . In questo caso i callback saranno metodi molto semplici (ecco perché va bene usarli invece di implementare un ascoltatore o abbonato ).

Diciamo che abbiamo questa entità:

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]

Come puoi vedere, ho definito due callback che verranno attivate con l'evento preRemove e l'evento postRemove.

Quindi il codice php dell'entità:

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();
    }
}

Nota i callback e gli array e i metodi statici. Ogni volta che viene chiamata una rimozione su un Car entità, il preRemove callback memorizzerà l'id dell'entità nell'array $preDeletedEntities . Quando l'entità viene eliminata, il postRemove l'evento memorizzerà l'id in $entityDeleted . Il preRemove l'evento è importante perché vogliamo sapere quale entità ha fatto fallire la transazione.

E ora, nel controller possiamo fare questo:

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,
    ));
}

Spero che sia d'aiuto. È un po' più macchinoso da implementare rispetto al primo approccio, ma molto meglio in termini di prestazioni.

AGGIORNAMENTO

Proverò a spiegare un po' di più cosa sta succedendo all'interno del catch blocco:

A questo punto la transazione è fallita. È stata sollevata un'eccezione a causa del fatto che l'eliminazione di alcune entità non è possibile (a causa ad esempio di un vincolo fk).

La transazione è stata annullata e nessuna entità è stata effettivamente rimossa dal database.

$deletedCars è una variabile che contiene gli ID di quelle entità che avrebbero potuto essere eliminate (non hanno sollevato eccezioni) ma non lo sono (a causa del rollback).

$failToDeleteCar contiene l'ID dell'entità la cui eliminazione ha sollevato l'eccezione.

$notDeletedCars contiene il resto degli ID entità che erano nella transazione ma che non sappiamo se sarebbero riusciti o meno.

A questo punto puoi resettare l'entitymanager (è chiuso), lanciare un'altra query con gli id ​​che non hanno causato problemi ed eliminarli (se vuoi) e rispedire un messaggio per far sapere all'utente che hai cancellato quelle entità e che $failToDeleteCar non è riuscito e non è stato eliminato e $notDeletedCars neanche sono stati cancellati Sta a te decidere cosa fare.

Non riesco a riprodurre il problema che menzioni su Entity::getDeletedEntities() , qui funziona bene.

Potresti perfezionare il tuo codice in modo da non dover aggiungere questi metodi alle tue entità (nemmeno i callback del ciclo di vita). Potresti, ad esempio, utilizzare un abbonato per acquisire eventi e una classe speciale con metodi statici per tenere traccia di quelle entità che non hanno fallito, quella che ha fallito e quelle che non hanno avuto la possibilità di essere cancellate/ aggiornato/inserito. Vi rimando alla documentazione che ho fornito. È un po' più complicato di quanto sembri, non in grado di darti una risposta generica in poche righe di codice, scusa, dovrai indagare ulteriormente.

Il mio suggerimento è di provare il codice che ho fornito con un'entità falsa e fare alcuni test per capire appieno come funziona. Quindi puoi provare ad applicarlo alle tue entità.

Buona fortuna!