sql >> Base de Datos >  >> RDS >> Mysql

Uso de eventos preRemove/postRemove para obtener qué consultas se pueden ejecutar y cuáles no

Así es como lo haría. No digo que este sea el mejor enfoque, si alguien sabe algo más fácil o mejor, yo sería el primero en interesarme en aprenderlo.

En primer lugar, estos son los Eventos de doctrina que puedes usar. En aras de la simplicidad, voy a explicar cómo lo haría para las eliminaciones. También para simplificar, voy a usar una matriz estática (se podría hacer de otras formas, me gusta esta) y devoluciones de llamada del ciclo de vida . En este caso, las devoluciones de llamada serán métodos muy simples (es por eso que está bien usarlos en lugar de implementar un oyente o suscriptor ).

Digamos que tenemos esta entidad:

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]

Como puede ver, he definido dos devoluciones de llamada que se activarán con el evento preRemove y el evento postRemove.

Luego el código php de la entidad:

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

Tenga en cuenta las devoluciones de llamada y las matrices y métodos estáticos. Cada vez que se solicita una eliminación sobre un Car entidad, el preRemove la devolución de llamada almacenará la identificación de la entidad en la matriz $preDeletedEntities . Cuando se elimina la entidad, postRemove el evento almacenará la identificación en $entityDeleted . El preRemove El evento es importante porque queremos saber qué entidad hizo que la transacción fallara.

Y ahora, en el controlador podemos hacer esto:

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

Espero eso ayude. Es un poco más engorroso de implementar que el primer enfoque, pero mucho mejor en términos de rendimiento.

ACTUALIZAR

Voy a tratar de explicar un poco más lo que sucede dentro del catch bloque:

En este punto, la transacción ha fallado. Se ha generado una excepción debido al hecho de que la eliminación de alguna entidad no es posible (debido, por ejemplo, a una restricción fk).

La transacción se revirtió y no se eliminó ninguna entidad de la base de datos.

$deletedCars es una variable que contiene los identificadores de aquellas entidades que podrían haber sido eliminadas (no generaron ninguna excepción) pero no lo son (debido a la reversión).

$failToDeleteCar contiene la identificación de la entidad cuya eliminación provocó la excepción.

$notDeletedCars contiene el resto de los ID de las entidades que estaban en la transacción pero que no sabemos si habría tenido éxito o no.

En este punto, puede restablecer el administrador de entidades (está cerrado), iniciar otra consulta con los ID que no causaron problemas y eliminarlos (si lo desea) y enviar un mensaje para informar al usuario que eliminó esas entidades y que $failToDeleteCar falló y no se eliminó y $notDeletedCars tampoco fueron borrados. Depende de usted decidir qué hacer.

No puedo reproducir el problema que mencionas sobre Entity::getDeletedEntities() , está funcionando bien aquí.

Puede refinar su código para que no necesite agregar estos métodos a sus entidades (ni siquiera las devoluciones de llamada del ciclo de vida). Podría, por ejemplo, hacer uso de un suscriptor para capturar eventos y una clase especial con métodos estáticos para realizar un seguimiento de aquellas entidades que no fallaron, la que falló y las que no tuvieron la oportunidad de ser eliminadas/ actualizado/insertado. Te remito a la documentación que proporcioné. Es un poco más complicado de lo que parece, no puedo darte una respuesta genérica en unas pocas líneas de código, lo siento, tendrás que investigar más.

Mi sugerencia es que pruebe el código que proporcioné con una entidad falsa y haga algunas pruebas para comprender completamente cómo funciona. Luego puede intentar aplicarlo a sus entidades.

¡Buena suerte!