2012-04-27 5 views
12

He hecho varias migraciones de Doctrine2 (https://github.com/doctrine/migrations) pero tengo una pregunta para una nueva migración que estoy intentando que hacer.

He estado cavando un poco en la biblioteca y veo que $this->addSql() se usa para crear una lista de SQL para ejecutar y luego se ejecuta más tarde.

Quería hacer algo en donde seleccione algunos datos, itere sobre las filas, inserte nuevos datos basados ​​en eso, y luego elimine los datos que seleccioné. Esto se presta a la biblioteca DBAL con bastante facilidad, pero me pregunto si puedo usar el protected $connection en una migración de forma segura. ¿O es malo porque se ejecutarían las sentencias antes de que se ejecute cualquiera de mis $this->addSql() SQL? También parece que esto rompería la configuración dry-run según lo que he visto en el código. ¿Alguien ha tenido alguna experiencia con este tipo de migración? ¿Hay algunas mejores prácticas?

La siguiente es la migración que quiero hacer, pero no estoy seguro de que este es apoyada por la doctrina Migraciones:

public function up(Schema $schema) 
{ 
    // this up() migration is autogenerated, please modify it to your needs 
    $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql"); 

    $this->addSql("ALTER TABLE article_enclosures ADD is_scrape TINYINT(1) NOT NULL"); 
    $this->addSql("ALTER TABLE images DROP FOREIGN KEY FK_E01FBE6AA536AAC7"); 

    // now lets take all images with a scrape and convert the scrape to an enclosure 
    // 
    // Select all images where not scrape_id is null (join on article_image_scrape) 
    // for each image: 
    //  insert into article_enclosures 
    //  update image set enclosure_id = new ID 
    //  delete from article_image_scrape where id... 
    // 
    // insert into article_enclosures select article_image_scrapes... 

    $sql = "SELECT i.id img_id, e.* FROM images i JOIN article_image_scrapes e ON i.scrape_id = e.id"; 
    $stmt = $this->connection->prepare($sql); 
    $stmt->execute(); 
    $scrapesToDelete = array(); 
    while ($row = $stmt->fetch()) { 
     $scrapeArticle = $row['article_id']; 
     $scrapeOldId = $row['id']; 
     $scrapeUrl = $row['url']; 
     $scrapeExtension = $row['extension']; 
     $scrapeUrlHash = $row['url_hash']; 
     $imageId = $row['image_id']; 

     $this->connection->insert('article_enclosures', array(
      'url' => $scrapeUrl, 
      'extension' => $scrapeExtension, 
      'url_hash' => $scrapeUrlHash 
     )); 

     $scrapeNewId = $this->connection->lastInsertId(); 

     $this->connection->update('images', array(
      'enclosure_id' => $scrapeNewId, 
      'scrape_id' => null 
     ), array(
      'id' => $imageId 
     )); 

     $scrapesToDelete[] = $scrapeOldId; 
    } 

    foreach ($scrapesToDelete as $id) { 
     $this->connection->delete('article_image_scrapes', array('id' => $id)); 
    } 

    $this->addSql("INSERT INTO article_scrapes (article_id, url, extension, url_hash) " 
      ."SELECT s.id, s.url, s.extension, s.url_hash" 
      ."FROM article_image_scrapes s"); 

    $this->addSql("DROP INDEX IDX_E01FBE6AA536AAC7 ON images"); 
    $this->addSql("ALTER TABLE images DROP scrape_id, CHANGE enclosure_id enclosure_id INT NOT NULL"); 
} 
+0

decidí simplemente hacer migraciones separadas antes y después de éste con la necesaria 'addSql' Llama para que el orden sea correcto. – Matt

+0

¿Lo has probado? Me parece bien – eddy147

+0

Creo que sí, pero esto fue hace un año. Creo que el problema original sigue en pie. Es decir, usando las llamadas '-> addSql()', esas se ejecutarán al final. Y el 'funcionamiento en seco' todavía ejecutará su manipulación directa. Por lo tanto, todavía parece hacky (pero podría estar equivocado, nunca obtuve una respuesta y ya no recuerdo mucho sobre esto). – Matt

Respuesta

11

Puede utilizar el $connection como esto

$result = $this->connection->fetchAssoc('SELECT id, name FROM table1 WHERE id = 1'); 
$this->abortIf(!$result, 'row with id not found'); 
$this->abortIf($result['name'] != 'jo', 'id 1 is not jo'); 
// etc.. 

Usted solo debe leer la base de datos y no usar la conexión para realizar la actualización/eliminación para que no rompa la opción de ejecución en seco.

En su ejemplo, debe hacer dos migraciones. El primero hará las dos alter table. El segundo hará la rutina de "imágenes con un raspado y convertir el raspado en un cerramiento". Usar la migración múltiple es más fácil revertirlos si algo sale mal.

8

FYI, últimos documentos muestran que este ejemplo es aún mejor con un método "Postup"

http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

// ... 
use Symfony\Component\DependencyInjection\ContainerAwareInterface; 
use Symfony\Component\DependencyInjection\ContainerInterface; 

class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface 
{ 

    private $container; 

    public function setContainer(ContainerInterface $container = null) 
    { 
     $this->container = $container; 
    } 

    public function up(Schema $schema) 
    { 
     // ... migration content 
    } 

    public function postUp(Schema $schema) 
    { 
     $em = $this->container->get('doctrine.orm.entity_manager'); 
     // ... update the entities 
    } 
} 
+5

Me gustaría agregar que debe tener cuidado al tratar con 'EntityManager' en el entorno Doctrine Migrations. EntityManager usará definiciones de entidades actuales que podrían ser diferentes de las utilizadas en la migración original. Me abstendría de usar EntityManager en migraciones. – SteveB

+0

@SteveB quizás usar la capa 'DBAL' puede ser una solución. Tenemos acceso a la 'conexión 'de la propiedad, que es una' \ Doctrine \ DBAL \ Connection' –

+1

@AdrienG Sí, mi comentario es más sobre el tema de usar 'EntityManager' como un acceso directo. Si bien se ve perfectamente bien, no lo es. Causará problemas en el futuro cuando uno actualice la definición de la entidad eventualmente. Usar cualquier cosa que no esté directamente relacionado con el estado actual de la aplicación está perfectamente bien. – SteveB

Cuestiones relacionadas