Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Usando eventos preRemove/postRemove para obter quais consultas podem ser executadas e quais não podem


Aqui está como eu faria isso. Não estou dizendo que esta é a melhor abordagem, se alguém souber algo mais fácil ou melhor, eu seria o primeiro interessado em aprender.

Primeiramente, estes são os Eventos de doutrina que você pode usar. Para simplificar, vou explicar como eu faria isso para exclusões. Também para simplificar, vou usar um array estático (pode ser feito de outras maneiras, gosto deste) e retornos de chamada do ciclo de vida . Nesse caso, os retornos de chamada serão métodos muito simples (é por isso que não há problema em usá-los em vez de implementar um ouvinte ou assinante ).

Digamos que temos esta entidade:
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 você pode ver, defini dois callbacks que serão acionados com o evento preRemove e o evento postRemove.

Em seguida, o código php da entidade:
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();
    }
}

Observe os retornos de chamada e as matrizes e métodos estáticos. Toda vez que uma remoção é chamada em um Car entidade, o preRemove callback irá armazenar o id da entidade no array $preDeletedEntities . Quando a entidade é excluída, o postRemove O evento armazenará o id em $entityDeleted . O preRemove O evento é importante porque queremos saber qual entidade fez a transação falhar.

E agora, no controlador, podemos fazer isso:
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 que ajude. É um pouco mais complicado de implementar do que a primeira abordagem, mas muito melhor em termos de desempenho.

ATUALIZAÇÃO

Vou tentar explicar um pouco mais o que está acontecendo dentro do catch quadra:

Neste ponto, a transação falhou. Uma exceção foi levantada devido ao fato de que a exclusão de alguma entidade não é possível (devido, por exemplo, a uma restrição fk).

A transação foi revertida e nenhuma entidade foi realmente removida do banco de dados.

$deletedCars é uma variável que contém os ids dessas entidades que poderiam ter sido excluídas (elas não geraram nenhuma exceção), mas não são (por causa da reversão).

$failToDeleteCar contém o id da entidade cuja exclusão gerou a exceção.

$notDeletedCars contém o restante dos IDs de entidades que estavam na transação, mas que não sabemos se teriam sucesso ou não.

Neste ponto, você pode redefinir o entitymanager (está fechado), iniciar outra consulta com os ids que não causaram problema e excluí-los (se desejar) e enviar de volta uma mensagem informando ao usuário que você excluiu essas entidades e que $failToDeleteCar falhou e não foi excluído e $notDeletedCars também não foram deletados. Cabe a você decidir o que fazer.

Não consigo reproduzir o problema que você mencionou sobre Entity::getDeletedEntities() , está funcionando bem aqui.

Você pode refinar seu código para não precisar adicionar esses métodos às suas entidades (nem mesmo os retornos de chamada do ciclo de vida). Você poderia, por exemplo, fazer uso de um assinante para capturar eventos e uma classe especial com métodos estáticos para acompanhar as entidades que não falharam, as que falharam e as que não tiveram a oportunidade de serem excluídas/ atualizado/inserido. Indico a documentação que forneci. É um pouco mais complicado do que parece, não é capaz de lhe dar uma resposta genérica em algumas linhas de código, desculpe, você terá que investigar mais.

Minha sugestão é que você experimente o código que forneci com uma entidade falsa e faça alguns testes para entender completamente como funciona. Então você pode tentar aplicá-lo às suas entidades.

Boa sorte!